Laravel Telescope in produzione: debugging avanzato senza impatto sulle prestazioni

Laravel Telescope in produzione: debugging avanzato senza impatto sulle prestazioni

Nell'aprile 2025 un cliente del settore e-commerce mi ha segnalato un bug intermittente che appariva solo in produzione: circa il 2% delle richieste alla pagina di checkout restituiva un errore 500 senza pattern apparente - non dipendeva dall'orario, dal browser, dal prodotto nel carrello né dal metodo di pagamento. I log applicativi registravano un generico Illuminate\Database\QueryException senza dettagli sufficienti per capire quale query fallisse e perché. L'applicazione girava su Laravel 11 con MySQL 8.0 su un VPS Hetzner, e il team di sviluppo non riusciva a riprodurre il bug in locale o in staging perché il database di staging aveva un dataset ridotto da 10.000 record contro i 2,4 milioni della produzione. Serviva un modo per vedere esattamente cosa succedeva all'interno di ogni singola richiesta HTTP che falliva - la query precisa, i binding, il tempo di esecuzione, il contesto dell'utente, i middleware attraversati, le eccezioni lanciate. Serviva Laravel Telescope in produzione. Ma abilitare Telescope in produzione senza le precauzioni adeguate è come lasciare un microfono acceso in una sala riunioni riservata: registra tutto, compresi dati che non dovrebbe vedere nessuno.

Nel mio lavoro con applicazioni Laravel in produzione, ho sviluppato una configurazione di Telescope che permette di usarlo come strumento di debugging chirurgico senza i rischi che normalmente lo rendono inadatto alla produzione: filtraggio dei dati sensibili, accesso limitato per IP, pruning automatico aggressivo e attivazione condizionale solo per le sessioni di debugging. Questa configurazione mi ha permesso di identificare il bug del checkout in 45 minuti - era una query che andava in deadlock quando due utenti modificavano contemporaneamente lo stesso articolo a giacenza limitata, un pattern visibile solo con dataset reali e carico concorrente.

Perché Telescope in produzione è considerato pericoloso (e perché queste paure sono gestibili)?

I rischi sono reali, non sono paure infondate. Telescope, nella configurazione di default, registra: il body completo di ogni richiesta HTTP (inclusi password, token, numeri di carta di credito nei form di pagamento), tutte le query SQL con i binding (inclusi i dati personali passati come parametri), tutte le eccezioni con lo stack trace completo (che può esporre path di file, nomi di classi interne e versioni dei pacchetti), tutti i log scritti dall'applicazione, tutte le email inviate (incluso il contenuto HTML con link di reset password), e le risposte delle chiamate HTTP verso servizi esterni (che possono contenere token API e dati di integrazione). Su un'applicazione che gestisce dati di utenti europei sotto GDPR, lasciare Telescope attivo senza filtri è potenzialmente una violazione dell'articolo 25 del Regolamento (protezione dei dati fin dalla progettazione e per impostazione predefinita).

Il secondo rischio è prestazionale. Telescope scrive un record nel database per ogni evento che traccia - e in un'applicazione ad alto traffico, questo significa centinaia di scritture aggiuntive al minuto sulle tabelle telescope_entries e telescope_entries_tags. Su un VPS con un singolo disco condiviso tra applicazione e database, l'overhead di I/O può diventare significativo e degradare le prestazioni dell'applicazione stessa.

La buona notizia è che entrambi i rischi sono completamente gestibili con la configurazione giusta. Nel mio profilo professionale trovi l'esperienza che porto nella gestione sicura di strumenti di debugging in produzione - è un'area dove la differenza tra un consulente che "ha letto il tutorial" e uno che ha gestito incidenti reali si vede nella configurazione dei filtri, non nell'installazione del pacchetto.

La configurazione che uso in produzione: filtri, gate e pruning

Il primo livello di protezione è il gate di accesso. Telescope deve essere visibile solo al team di sviluppo, mai agli utenti dell'applicazione:

// app/Providers/TelescopeServiceProvider.php
protected function gate(): void
{
    Gate::define('viewTelescope', function ($user) {
        // Solo admin con IP nel whitelist
        $allowedIps = ['203.0.113.50', '203.0.113.51']; // IP ufficio e VPN
        $currentIp = request()->ip();

        return $user->is_admin && in_array($currentIp, $allowedIps);
    });
}

Il secondo livello è il filtraggio dei dati sensibili. Telescope permette di definire quali richieste registrare e quali campi mascherare:

// app/Providers/TelescopeServiceProvider.php
public function register(): void
{
    // Non registrare in produzione a meno che non sia un debug attivo
    Telescope::filter(function (IncomingEntry $entry) {
        // In produzione: registra SOLO errori, query lente e richieste lente
        if ($this->app->environment('production')) {
            return $entry->isReportableException()
                || $entry->isSlowQuery()
                || $entry->isFailedRequest()
                || $entry->isFailedJob()
                || $entry->isScheduledTaskFailed()
                || $entry->type === EntryType::LOG
                    && $entry->content['level'] >= 'error';
        }

        return true; // In sviluppo/staging, registra tutto
    });

    // Maschera i campi sensibili nei request body
    Telescope::hideSensitiveRequestDetails();

    // Campi aggiuntivi da nascondere
    Telescope::tag(function (IncomingEntry $entry) {
        return [];
    });
}

Il metodo hideSensitiveRequestDetails() maschera automaticamente i campi password, password_confirmation e _token nei body delle richieste. Ma non basta: nelle applicazioni di e-commerce, devi mascherare anche i campi delle carte di credito, i token di pagamento e qualsiasi dato che il processore di pagamento restituisce. La mia configurazione aggiunge un middleware custom che sanitizza i dati prima che Telescope li registri:

// Campi da mascherare nel request body di Telescope
// Configurazione in config/telescope.php
'hide_request_parameters' => [
    'password',
    'password_confirmation',
    'card_number',
    'cvv',
    'cc_number',
    'cc_exp',
    'payment_token',
    'stripe_token',
    'api_key',
    'secret',
],

// Campi da mascherare nelle risposte delle chiamate HTTP
'hide_response_parameters' => [
    'access_token',
    'refresh_token',
    'client_secret',
],

Il terzo livello è il pruning aggressivo. In produzione, i dati di Telescope non devono accumularsi per settimane - servono solo per il debugging della sessione corrente. Il pruning automatico si configura nel task scheduler di Laravel:

// app/Console/Kernel.php - pruning Telescope in produzione
$schedule->command('telescope:prune --hours=24')->dailyAt('04:00');

Con questa configurazione, i dati di Telescope vengono eliminati dopo 24 ore. Abbastanza tempo per diagnosticare un bug segnalato il giorno precedente, troppo poco per accumulare dati sensibili. Sul portale e-commerce del cliente, questo pruning mantiene la tabella telescope_entries sotto le 50.000 righe - un volume gestibile senza impatto misurabile sulle prestazioni del database.

Il bug del checkout: come Telescope ha risolto in 45 minuti un problema di tre settimane

Con Telescope configurato in produzione secondo le regole appena descritte, ho atteso che il bug si ripresentasse. Non ho dovuto aspettare molto: in meno di un'ora, tre richieste alla pagina checkout hanno restituito errore 500. Telescope ha registrato per ciascuna: la query SQL esatta che ha generato l'eccezione (un UPDATE inventario SET quantita = quantita - 1 WHERE prodotto_id = ? AND quantita > 0 che andava in deadlock), i binding con l'ID del prodotto, il timestamp preciso, e l'utente che ha eseguito la richiesta.

Il pattern era chiaro: quando due utenti cercavano di acquistare l'ultimo pezzo di un prodotto a giacenza limitata nello stesso secondo, MySQL generava un deadlock sulla riga dell'inventario. La soluzione è stata una retry strategy con DB::transaction() e un catch specifico per i deadlock, più un lock esplicito con SELECT ... FOR UPDATE sulla riga dell'inventario prima del decremento. Un fix di 15 righe di codice per un bug che il team cercava da tre settimane.

Senza Telescope, quel bug sarebbe stato diagnosticabile solo con il slow_query_log di MySQL (che non cattura i deadlock automaticamente) o con un profiler esterno come Blackfire (che richiede l'installazione di un'estensione PHP e non è sempre disponibile sui VPS dei clienti). Telescope mi ha dato la stessa visibilità con zero setup aggiuntivo - era già installato, bastava attivare temporaneamente la registrazione completa per le richieste al checkout.

La lezione operativa che traggo da questo caso è duplice. La prima: i bug intermittenti in produzione sono quasi sempre legati a condizioni di concorrenza o a caratteristiche del dataset che non esistono in staging. Nessuna quantità di test unitari o di integration test catturerà un deadlock MySQL che si verifica solo quando due utenti reali cliccano "Acquista" nello stesso secondo sullo stesso prodotto con giacenza 1 - servono strumenti che operano in produzione, sul traffico reale, con i dati reali. La seconda: il costo di non avere visibilità in produzione è enormemente superiore al costo di configurare uno strumento come Telescope in modo sicuro. Tre settimane di debugging alla cieca da parte del team interno, con tentativi di riproduzione in locale, lettura di log generici e ipotesi non verificabili, hanno consumato circa 40 ore di lavoro sviluppatore - l'equivalente di cinque giornate uomo che avrebbero potuto essere spese su feature a valore. L'alternativa - Telescope configurato correttamente - ha risolto lo stesso problema in 45 minuti di analisi mirata. Il rapporto è 1 a 53 in termini di efficienza diagnostica, e questo è il tipo di ritorno sull'investimento che giustifica il tempo speso nella configurazione sicura di Telescope anche per le applicazioni che "sembrano funzionare bene" - perché ogni applicazione ha bug intermittenti che nessuno ha ancora notato.

Telescope vs Pulse: non sono alternativi, sono complementari

La distinzione tra Telescope e Pulse è fondamentale perché i team li confondono regolarmente. Pulse è uno strumento di monitoring - mostra metriche aggregate nel tempo, trend, anomalie. Ti dice "le richieste lente sono aumentate del 30% questa settimana" e "il job di sincronizzazione fallisce 15 volte al giorno". Telescope è uno strumento di debugging - mostra il dettaglio di ogni singola richiesta, query, evento. Ti dice "questa specifica richiesta alle 14:32 ha eseguito 47 query in 3.200 ms perché l'eager loading mancava sulla relazione orderLines".

Nel mio stack di produzione per i clienti Laravel, il pattern è: Pulse always-on per il monitoring quotidiano (zero impatto prestazionale, dati aggregati, dashboard consultabile dal team), Telescope disponibile ma filtrato (registra solo errori e anomalie), attivazione completa di Telescope on-demand quando serve diagnosticare un bug specifico. L'attivazione completa la faccio con una variabile d'ambiente (TELESCOPE_FULL_DEBUG=true) che il comando Artisan legge dal .env - cambio la variabile, ricarico FPM, debuggo, rimetto la variabile a false. L'intero ciclo dura il tempo necessario per riprodurre e diagnosticare il bug, tipicamente 30-60 minuti. Un pattern che trovo particolarmente efficace è combinare i due strumenti in modo sequenziale: Pulse mi segnala un'anomalia (ad esempio "il tempo medio delle richieste al checkout è salito del 40% nelle ultime 2 ore"), e a quel punto attivo la modalità di registrazione completa di Telescope specificamente sugli endpoint coinvolti per capire cosa sta causando la regressione. Senza Pulse, non saprei nemmeno che c'è un problema fino a quando un utente non chiama. Senza Telescope, saprei che c'è un problema ma non avrei gli strumenti per diagnosticarlo senza collegare un debugger o aggiungere istruzioni dd() temporanee nel codice di produzione - una pratica che in un contesto professionale è inaccettabile.

Ho documentato l'approccio di osservabilità minima per applicazioni PHP legacy in un articolo dedicato che copre lo stesso principio - monitoring always-on leggero, debugging on-demand profondo - applicato a codebase che non usano Laravel e non hanno accesso a Telescope. Il concetto è lo stesso: la diagnostica in produzione deve essere proporzionata al problema. Un termometro (Pulse) per la febbre quotidiana, un'ecografia (Telescope) quando serve capire cosa c'è dentro.

Questa distinzione non è solo tecnica - ha un impatto diretto sui costi di manutenzione. Un team che usa Telescope come unico strumento di monitoring in produzione accumula gigabyte di dati nel database, degrada le prestazioni, e crea un rischio GDPR permanente. Un team che usa Pulse per il monitoring e Telescope solo quando serve mantiene un'infrastruttura leggera, conforme e diagnosticabile. Se gestisci applicazioni Laravel in produzione e il tuo unico strumento di visibilità è tail -f storage/logs/laravel.log, contattami per configurare insieme uno stack di osservabilità proporzionato alla complessità della tua applicazione: in una giornata di lavoro installiamo Pulse, configuriamo Telescope in modalità sicura, definiamo le soglie di alerting e facciamo un primo audit delle query lente e delle eccezioni ricorrenti che probabilmente non sai di avere.

Ultima modifica: