La prima volta che un ingegnere mette un console.log su un lavoratore di produzione e non vede apparire nulla da nessuna parte, l'istinto è quello di sospettare un bug nella distribuzione. Il registro semplicemente scompare. Non esiste un server delle applicazioni con stdout persistente, non esiste un file di registro su disco, non esiste un processo che accumuli l'output tra le invocazioni. Ogni esecuzione di Worker avviene all'interno di un isolato V8 che nasce, elabora la richiesta e scompare, senza lasciare traccia a meno che tu non lo stia catturando attivamente. Comprendere questo modello è il prerequisito per qualsiasi strategia di osservabilità che funzioni davvero all’avanguardia.
##wrangler tail: cos'è e cosa non è
wrangler tail apre una connessione in streaming all'infrastruttura Cloudflare e ritrasmette in tempo reale gli eventi delle tue invocazioni di Workers: metadati di ogni richiesta (metodo, URL, stato della risposta, durata della CPU, paese di origine), l'intero output di console.log e console.error, eccezioni non catturate con stack trace e il risultato di waitUntil() al termine.
Cosa non fa wrangler tail: persistere. Quando chiudi la sessione, gli eventi non ancora arrivati vengono persi. Anche gli eventi accaduti prima dell'apertura della sessione non esistono per te. wrangler tail è uno strumento di indagine dal vivo, utile per riprodurre un problema mentre lo osservi, inutile per ricostruire cosa è successo un'ora fa.
Puoi filtrare l'output con --filter-status 500 per visualizzare solo gli errori oppure con --filter-sampling-rate 0.1 per campionare il 10% del traffico e non essere inondato da un carico elevato. Nei servizi con centinaia di richieste al secondo, la coda non filtrata rende rapidamente illeggibile il terminale.
Il trucco delle eccezioni silenziose
Esiste un comportamento in fase di runtime che sorprende gli ingegneri esperti: un Worker può fornire una risposta 200 al client e lanciare comunque un'eccezione, purché l'eccezione sia stata lanciata dopo l'invio di Response.
Lo scenario concreto: chiami il numero ctx.waitUntil(minhaFuncaoAssincrona()) per svolgere il lavoro in background dopo la risposta. Se viene lanciato minhaFuncaoAssincrona, il cliente ha già ricevuto 200 ed è soddisfatto. L'eccezione appare in wrangler tail come un evento di errore associato alla richiesta, ma non modifica lo stato HTTP visto dal client. Senza la coda aperta l'errore non si vede mai.
Lo stesso vale per qualsiasi promessa di non fare await all'interno del conduttore. Se lanci un fetch() senza attesa e senza gestione degli errori, e questo rifiuta, il rifiuto non catturato appare nella coda ma non influenza la risposta ricevuta dal client. In un ambiente locale con wrangler dev, questo comportamento potrebbe essere diverso: gli avvisi sono più visibili. Nella produzione c'è il silenzio più totale.
La difesa è sistematica: ogni gestore deve avere un try/catch al livello più alto che acquisisce, registra con console.error e restituisce un HTTP 500 esplicito. Ogni promessa passata a waitUntil() deve avere una gestione degli errori interni. Non perché il runtime si ricorderà di te, non lo farà.
wrangler dev locale rispetto a --remote
wrangler dev senza flag esegue un server locale utilizzando Miniflare, un'implementazione Node.js del runtime Workers. Per la maggior parte dei casi di sviluppo, questo è sufficiente: i collegamenti KV, D1, R2 e Queue funzionano con implementazioni locali in memoria o SQLite e il ciclo di feedback è immediato senza coinvolgere la rete Cloudflare.
wrangler dev --remote è diverso: invia il Worker all'infrastruttura Cloudflare reale e instrada le richieste di sviluppo attraverso il perimetro reale. Ciò è necessario quando è necessario testare comportamenti che Miniflare non può simulare fedelmente: oggetti durevoli in produzione con stato reale, comportamenti di memorizzazione nella cache CDN o funzionalità di runtime che la versione corrente di Miniflare non ha ancora implementato.
Il compromesso è ovvio: --remote richiede [40 autenticazione, ha una latenza di rete e qualsiasi operazione di scrittura su KV o D1 va alle risorse reali (a meno che non si imposti spazi dei nomi/banchi di staging separati, che dovrebbero essere standard). Utilizzare --remote senza isolare l'ambiente di staging è il percorso più breve per corrompere i dati di produzione durante lo sviluppo.
##Logpush: dalla coda effimera alla fidelizzazione reale
Per qualsiasi servizio che abbia bisogno di ricostruire ciò che è accaduto dopo il fatto (auditing, debug post-mortem, analisi dei modelli di errore) il 23 non è sufficiente. Logpush risolve questo problema esportando gli eventi Workers in una destinazione di archiviazione configurata.
Le destinazioni supportate includono R2 (lo spazio di archiviazione di Cloudflare, a 0,05 dollari per milione di righe esportate), S3, Datadog, Splunk e alcune altre. La configurazione avviene tramite dashboard o API: scegli il set di dati (workers-trace-events), la destinazione e i campi che desideri esportare: timestamp, event.request.url, event.response.status, event.exceptions, event.logs (che include l'output di console.log).
La cosa importante del campo event.logs: cattura ciò che hai passato a console.log, ma come testo serializzato. Se hai registrato un oggetto JavaScript complesso, ciò che arriva in Logpush è la rappresentazione di stringa di quell'oggetto, non JSON strutturato. Per i log che devono essere analizzati e filtrati a destinazione (per creare avvisi in Datadog o query in Splunk) serializzarli esplicitamente in JSON prima della registrazione: console.log(JSON.stringify({ level: 'error', message, requestId, stack })). Logpush consegna la stringa; la destinazione analizza il JSON.
Cosa monitorare in produzione e cosa non fornisce il runtime
I lavoratori non emettono intervalli di traccia distribuiti in modo nativo. Non esiste alcuna integrazione automatica con OpenTelemetry, non esiste propagazione del contesto di traccia tra richieste secondarie senza implementazione manuale. Se un lavoratore chiama tre servizi tramite fetch() e uno di essi impiega 800 ms, wrangler tail mostra la durata totale ma non scompone dove è stato trascorso il tempo. Per avere questa visibilità, cronometralo manualmente: const t0 = Date.now() prima del recupero, Date.now() - t0 dopo, e registra il risultato strutturato con il nome del servizio.
Cloudflare ha acquisito Baselime e sta integrando le sue capacità sotto il nome Cloudflare Observability: analisi di log e metriche con più ergonomia rispetto alla coda manuale, ma il vero tracciamento distribuito richiede ancora la strumentazione nel codice o un SDK che si propaga attraverso le sottorichieste.
Le metriche più utili per rilevare il degrado prima che il client se ne accorga: distribuzione percentile della durata della CPU (p50, p95, p99), tasso di errore segmentato per percorso e conteggio delle sottorichieste per invocazione. Nessuno di essi è esente dal runtime: devono essere creati, esportati tramite Logpush e trasformati in avvisi a destinazione. La differenza tra un servizio che gestisci con sicurezza e uno che temi di mettere in produzione è quasi sempre la qualità della strumentazione che hai costruito prima del primo incidente.
Leggi anche
- Operatori Cloudflare in produzione: cosa cambia dopo hello world
- Osservabilità nei sistemi distribuiti: log, metriche e tracciamento
- Osservabilità oltre i log: cosa cambia nella pratica OpenTelemetry
- Operai + D1 + KV + R2: comporre associazioni nello stesso servizio
- Lavoratori: limiti di CPU e memoria - che la documentazione non spiega bene
- Testing Workers: unità, integrazione e come simulare il runtime senza dipendere da Cloudflare
