Cloudflare Workers
Debugging
Logs
Workers Tail
Observabilidade

Workers: debugging, logs e Workers Tail — observabilidade no edge sem servidor de log

Não há processo persistente, não há filesystem, e console.log vai para lugar nenhum sem uma sessão de tail ativa — debugar Workers exige um modelo mental diferente.

A primeira vez que um engenheiro coloca um console.log num Worker de produção e não vê nada aparecer em lugar nenhum, o instinto é suspeitar de um bug no deploy. O log simplesmente desaparece. Não há servidor de aplicação com stdout persistente, não há arquivo de log em disco, não há processo que acumule saída entre invocações. Cada execução de Worker acontece dentro de um isolate V8 que nasce, processa a requisição, e some — sem deixar rastro a menos que você esteja ativamente capturando. Entender esse modelo é o pré-requisito para qualquer estratégia de observabilidade que funcione de verdade no edge.

wrangler tail: o que é e o que não é

O wrangler tail abre uma conexão de streaming com a infraestrutura da Cloudflare e retransmite em tempo real os eventos das suas Workers invocações: metadados de cada requisição (método, URL, status de resposta, duração de CPU, país de origem), toda a saída de console.log e console.error, exceções não capturadas com stack trace, e o resultado de waitUntil() quando termina.

O que o wrangler tail não faz: persistir. Quando você fecha a sessão, os eventos que ainda não chegaram se perdem. Eventos que aconteceram antes de você abrir a sessão também não existem para você. O wrangler tail é uma ferramenta de investigação ao vivo — útil para reproduzir um problema enquanto você o observa, inútil para reconstruir o que aconteceu uma hora atrás.

Você pode filtrar a saída com --filter-status 500 para ver apenas erros, ou --filter-sampling-rate 0.1 para amostrar 10% do tráfego e não ser inundado em alta carga. Em serviços com centenas de requisições por segundo, o tail sem filtro torna o terminal ilegível rapidamente.

O gotcha das exceções silenciosas

Existe um comportamento do runtime que pega engenheiros experientes: um Worker pode entregar uma resposta 200 ao cliente e ainda assim ter lançado uma exceção — desde que a exceção tenha sido lançada depois que o Response foi enviado.

O cenário concreto: você chama ctx.waitUntil(minhaFuncaoAssincrona()) para fazer trabalho de background após a resposta. Se minhaFuncaoAssincrona lança, o cliente já recebeu o 200 e está feliz. A exceção aparece no wrangler tail como um evento de erro associado à requisição, mas não muda o status HTTP que o cliente viu. Sem o tail aberto, o erro nunca é visto.

O mesmo vale para qualquer promise que você não await dentro do handler. Se você dispara um fetch() sem await e sem tratamento de erro, e ele rejeita, a rejeição não capturada aparece no tail mas não afeta a resposta que o cliente recebeu. Em ambiente local com wrangler dev, esse comportamento pode ser diferente — warnings são mais visíveis. Em produção, o silêncio é total.

A defesa é sistemática: todo handler deve ter um try/catch no nível mais alto que captura, loga com console.error, e retorna um HTTP 500 explícito. Toda promise passada para waitUntil() deve ter tratamento de erro interno. Não porque o runtime vai lembrar você — ele não vai.

wrangler dev local versus --remote

O wrangler dev sem flags sobe um servidor local usando Miniflare — uma implementação em Node.js do runtime de Workers. Para a maioria dos casos de desenvolvimento, é suficiente: bindings de KV, D1, R2 e Queue funcionam com implementações locais em memória ou SQLite, e o ciclo de feedback é imediato sem envolver a rede da Cloudflare.

O wrangler dev --remote é diferente: ele envia o Worker para a infraestrutura real da Cloudflare e rota as requisições de desenvolvimento através do edge de verdade. Isso é necessário quando você precisa testar comportamentos que o Miniflare não consegue simular fielmente — Durable Objects em produção com estado real, comportamentos de cache do CDN, ou features de runtime que a versão atual do Miniflare ainda não implementou.

O trade-off é óbvio: --remote exige autenticação, tem latência de rede, e qualquer operação de escrita em KV ou D1 vai para os recursos reais (a menos que você configure namespaces/bancos de staging separados, o que deve ser padrão). Usar --remote sem isolamento de ambiente de staging é o caminho mais curto para corromper dados de produção durante desenvolvimento.

Logpush: do tail efêmero para retenção real

Para qualquer serviço que precisa reconstituir o que aconteceu depois do fato — auditoria, debugging pós-mortem, análise de padrões de erro — o wrangler tail é insuficiente. O Logpush resolve isso exportando os eventos de Workers para um destino de armazenamento configurado.

Os destinos suportados incluem R2 (o storage da própria Cloudflare, a US$ 0,05 por milhão de linhas exportadas), S3, Datadog, Splunk, e alguns outros. A configuração é feita via dashboard ou API: você escolhe o dataset (workers-trace-events), o destino, e os campos que quer exportar — timestamp, event.request.url, event.response.status, event.exceptions, event.logs (que inclui a saída de console.log).

O detalhe importante sobre o campo event.logs: ele captura o que você passou para console.log, mas como texto serializado. Se você logou um objeto JavaScript complexo, o que chega no Logpush é a representação string desse objeto, não JSON estruturado. Para logs que precisam ser parseados e filtrados no destino — para criar alertas no Datadog ou queries no Splunk — serialize explicitamente para JSON antes de logar: console.log(JSON.stringify({ level: 'error', message, requestId, stack })). O Logpush entrega a string; o destino parseia o JSON.

O que monitorar em produção — e o que o runtime não entrega

Workers não emitem spans de tracing distribuído nativamente. Não há integração automática com OpenTelemetry, não há propagação de trace context entre subrequisições sem implementação manual. Se um Worker chama três serviços via fetch() e um deles leva 800ms, o wrangler tail mostra a duração total mas não decompõe onde o tempo foi gasto. Para ter essa visibilidade, você cronometra manualmente: const t0 = Date.now() antes do fetch, Date.now() - t0 depois, e loga o resultado estruturado com o nome do serviço.

A Cloudflare adquiriu o Baselime e está integrando suas capacidades sob o nome Cloudflare Observability — análise de logs e métricas com mais ergonomia que o tail manual, mas tracing distribuído real ainda requer instrumentação no código ou um SDK que propague traceparent nas subrequisições.

As métricas mais úteis para detectar degradação antes do cliente perceber: distribuição de percentis de duração de CPU (p50, p95, p99), taxa de erros segmentada por rota, e contagem de subrequisições por invocação. Nenhuma vem grátis do runtime — precisam ser construídas, exportadas via Logpush, e transformadas em alertas no destino. A diferença entre um serviço que você opera com confiança e um que você teme colocar em produção é quase sempre a qualidade da instrumentação que você construiu antes do primeiro incidente.

Leia também

Workers: debugging, logs e Workers Tail — observabilidade no edge sem servidor de log | Matheus Breguêz