Logging Estruturado com JSONL
Um guia completo sobre logging estruturado usando o formato JSONL (JSON Lines). Aprenda a integrar com ELK Stack, Fluentd, CloudWatch, GCP Logging e Azure Monitor com exemplos de código prontos para produção.
Última atualização: fevereiro de 2026
Por que Logging Estruturado com JSONL?
Logs tradicionais em texto puro são legíveis por humanos, mas praticamente impossíveis para máquinas parsearem de forma confiável. Uma linha como '2024-01-15 ERROR User login failed for admin' esconde a severidade, o tipo de evento e o nome de usuário dentro de uma string não estruturada. Toda ferramenta de agregação de logs precisa recorrer a expressões regulares frágeis para extrair significado, e qualquer mudança no formato do log quebra o pipeline.
Logging estruturado resolve esse problema emitindo cada entrada de log como um objeto JSON com campos bem definidos. JSONL (JSON Lines) leva isso um passo adiante: um objeto JSON por linha, sem array envolvente, sem vírgulas finais. Isso torna logs JSONL amigáveis para append, transmitíveis por streaming e trivialmente parsáveis por toda plataforma de log importante. Neste guia você aprenderá como produzir logs JSONL em Python, Node.js e Go, e como enviá-los para ELK, Fluentd e os três principais provedores de nuvem.
Comparado a outros formatos estruturados como CSV ou XML, JSONL oferece o melhor equilíbrio de flexibilidade, suporte de ferramentas e legibilidade humana. Campos podem ser aninhados, tipos são preservados, e você nunca precisa definir um schema fixo antecipadamente. É por isso que JSONL se tornou o padrão de fato para logging estruturado na infraestrutura moderna.
Bibliotecas de Logging por Linguagem
A maioria das bibliotecas modernas de logging suporta saída JSON estruturada nativamente ou via uma simples mudança de configuração. Abaixo estão as bibliotecas recomendadas para Python, Node.js e Go, cada uma produzindo saída compatível com JSONL.
Python: structlog
Recomendadostructlog é a principal biblioteca de logging estruturado para Python. Ela encapsula o módulo logging padrão e produz saída JSON por padrão. Seu pipeline de processadores permite enriquecer, filtrar e transformar entradas de log antes da serialização.
import structlog# Configura structlog para saída JSONLstructlog.configure(processors=[structlog.processors.TimeStamper(fmt="iso"),structlog.processors.add_log_level,structlog.processors.StackInfoRenderer(),structlog.processors.JSONRenderer(),],wrapper_class=structlog.BoundLogger,context_class=dict,logger_factory=structlog.PrintLoggerFactory(),)log = structlog.get_logger()# Cada chamada produz uma linha JSONLlog.info("user.login", user_id="u-42", ip="10.0.0.1")# {"event":"user.login","user_id":"u-42","ip":"10.0.0.1","level":"info","timestamp":"2026-02-15T08:30:00Z"}log.error("db.connection_failed", host="db-primary", retries=3)# {"event":"db.connection_failed","host":"db-primary","retries":3,"level":"error","timestamp":"2026-02-15T08:30:01Z"}
Node.js: pino
Mais Rápidopino é o logger Node.js mais rápido, produzindo saída JSONL por padrão sem nenhuma configuração. Ele alcança baixo overhead através de escrita assíncrona e um caminho mínimo de serialização. O pacote complementar pino-pretty fornece saída legível por humanos para desenvolvimento local.
import pino from 'pino';// pino produz JSONL por padrãoconst log = pino({level: 'info',timestamp: pino.stdTimeFunctions.isoTime,});// Cada chamada produz uma linha JSONLlog.info({ userId: 'u-42', ip: '10.0.0.1' }, 'user.login');// {"level":30,"time":"2026-02-15T08:30:00.000Z","userId":"u-42","ip":"10.0.0.1","msg":"user.login"}log.error({ host: 'db-primary', retries: 3 }, 'db.connection_failed');// {"level":50,"time":"2026-02-15T08:30:01.000Z","host":"db-primary","retries":3,"msg":"db.connection_failed"}// Loggers filhos herdam o contextoconst reqLog = log.child({ requestId: 'req-abc-123' });reqLog.info({ path: '/api/users' }, 'request.start');
Go: zerolog
Zero Alocaçãozerolog é um logger JSON de alocação zero para Go, projetado para máximo desempenho em serviços de alto throughput. Ele escreve diretamente em um io.Writer sem alocações intermediárias, tornando-o ideal para aplicações sensíveis à latência. Sua API encadeada é tanto ergonômica quanto eficiente.
package mainimport ("os""github.com/rs/zerolog""github.com/rs/zerolog/log")func main() {// Configura saída JSONL para stdoutzerolog.TimeFieldFormat = zerolog.TimeFormatUnixlog.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger()// Cada chamada produz uma linha JSONLlog.Info().Str("user_id", "u-42").Str("ip", "10.0.0.1").Msg("user.login")// {"level":"info","user_id":"u-42","ip":"10.0.0.1","time":1739608200,"message":"user.login"}log.Error().Str("host", "db-primary").Int("retries", 3).Msg("db.connection_failed")}
Integração com ELK Stack
O ELK Stack (Elasticsearch, Logstash, Kibana) é a plataforma de gerenciamento de logs open-source mais amplamente implantada. Logs JSONL se integram perfeitamente porque Logstash e Filebeat podem parsear JSON nativamente, eliminando a necessidade de padrões grok complexos.
Filebeat é o shipper de logs leve que monitora seus arquivos de log JSONL e os encaminha para Logstash ou Elasticsearch. Definir json.keys_under_root como true promove os campos JSON para campos de nível superior no Elasticsearch, tornando-os diretamente pesquisáveis no Kibana.
# filebeat.ymlfilebeat.inputs:- type: logenabled: truepaths:- /var/log/app/*.jsonljson.keys_under_root: truejson.add_error_key: truejson.message_key: messagejson.overwrite_keys: trueoutput.elasticsearch:hosts: ["http://elasticsearch:9200"]index: "app-logs-%{+yyyy.MM.dd}"# Opcional: enviar para Logstash# output.logstash:# hosts: ["logstash:5044"]
Se você precisa transformar ou enriquecer logs antes da indexação, Logstash fica entre Filebeat e Elasticsearch. O codec json parseia cada linha JSONL automaticamente. Use o filtro mutate para renomear campos, adicionar tags ou remover dados sensíveis antes do armazenamento.
# logstash.confinput {beats {port => 5044codec => json}}filter {# Parseia timestamp do campo JSONdate {match => ["timestamp", "ISO8601"]target => "@timestamp"}# Adiciona tag de ambientemutate {add_field => { "environment" => "production" }remove_field => ["agent", "ecs", "host"]}# Roteia por nível de logif [level] == "error" or [level] == "fatal" {mutate { add_tag => ["alert"] }}}output {elasticsearch {hosts => ["http://elasticsearch:9200"]index => "app-logs-%{+YYYY.MM.dd}"}}
Fluentd e Fluent Bit
Fluentd e seu irmão mais leve Fluent Bit são projetos graduados da CNCF amplamente usados em ambientes Kubernetes. Ambos lidam com logs JSONL nativamente e podem encaminhá-los para praticamente qualquer destino, de Elasticsearch a S3 a serviços de log nativos da nuvem.
Fluent Bit é o coletor de logs preferido para Kubernetes e ambientes com recursos limitados. Ele consome aproximadamente 450KB de memória e pode processar centenas de milhares de registros por segundo. O parser json lida com entrada JSONL sem nenhuma configuração personalizada.
[SERVICE]Flush 5Daemon OffLog_Level infoParsers_File parsers.conf[INPUT]Name tailPath /var/log/app/*.jsonlParser jsonTag app.*Refresh_Interval 5[FILTER]Name modifyMatch app.*Add cluster production-us-east-1Add service my-api[OUTPUT]Name esMatch app.*Host elasticsearchPort 9200Index app-logsType _docLogstash_Format On
Fluentd oferece um ecossistema de plugins mais rico comparado ao Fluent Bit, com mais de 500 plugins da comunidade. Use-o quando precisar de roteamento avançado, buffering ou transformações. O plugin in_tail com format json lê arquivos JSONL nativamente.
# fluentd.conf<source>@type tailpath /var/log/app/*.jsonlpos_file /var/log/fluentd/app.postag app.logs<parse>@type jsontime_key timestamptime_format %Y-%m-%dT%H:%M:%S%z</parse></source><filter app.logs>@type record_transformer<record>hostname "#{Socket.gethostname}"environment production</record></filter><match app.logs>@type elasticsearchhost elasticsearchport 9200index_name app-logslogstash_format true<buffer>@type memoryflush_interval 5schunk_limit_size 5m</buffer></match>
Plataformas de Logging na Nuvem
Todos os três principais provedores de nuvem aceitam logs formatados em JSONL e automaticamente parseiam campos JSON em atributos pesquisáveis e filtráveis. Isso elimina a necessidade de executar sua própria infraestrutura de logs, enquanto oferece as mesmas capacidades de consulta estruturada.
AWS CloudWatch Logs
AWSCloudWatch Logs automaticamente parseia entradas de log JSON, tornando cada campo consultável com CloudWatch Logs Insights. Quando sua aplicação roda no ECS, Lambda ou EC2 com o agente CloudWatch, a saída JSONL é indexada como dados estruturados sem configuração extra. Use a sintaxe tipo SQL do Logs Insights para consultar milhões de entradas de log em segundos.
# Exemplos de consultas CloudWatch Logs Insights# Encontrar todos os erros na última horafields @timestamp, level, event, user_id| filter level = "error"| sort @timestamp desc| limit 100# Agregar contagem de erros por tipo de eventofields event| filter level = "error"| stats count(*) as error_count by event| sort error_count desc# Latência P99 por endpointfields path, duration_ms| filter ispresent(duration_ms)| stats pct(duration_ms, 99) as p99 by path| sort p99 desc
GCP Cloud Logging
GCPGoogle Cloud Logging (anteriormente Stackdriver) trata payloads JSON como entradas de log estruturadas com suporte de primeira classe. Quando você escreve JSONL para stdout no Cloud Run, GKE ou Cloud Functions, o agente de logging parseia cada linha em um LogEntry estruturado. O campo severity mapeia diretamente para os níveis de severidade do Cloud Logging, e campos do jsonPayload se tornam indexados e pesquisáveis.
# Exemplo Python para Cloud Run / Cloud Functionsimport jsonimport sysdef log(severity: str, message: str, **fields):"""Escreve uma entrada de log JSONL compatível com GCP Cloud Logging."""entry = {"severity": severity.upper(), # Mapeia para severidade do Cloud Logging"message": message,**fields,}print(json.dumps(entry), file=sys.stdout, flush=True)# GCP automaticamente parseia como logs estruturadoslog("INFO", "user.login", user_id="u-42", ip="10.0.0.1")log("ERROR", "db.timeout", host="db-primary", latency_ms=5200)# Consulta no Cloud Logging:# jsonPayload.user_id = "u-42"# severity >= ERROR
Azure Monitor Logs
AzureAzure Monitor ingere logs JSONL através do Azure Monitor Agent ou ingestão direta via API. Application Insights automaticamente parseia traces JSON e eventos personalizados. KQL (Kusto Query Language) fornece consultas poderosas em dados de log estruturados com suporte para agregação, análise de séries temporais e detecção de anomalias.
// Consultas KQL para logs JSONL no Azure Monitor// Encontrar erros recentesAppTraces| where SeverityLevel >= 3| extend parsed = parse_json(Message)| project TimeGenerated, parsed.event, parsed.user_id,parsed.error_message| order by TimeGenerated desc| take 100// Taxa de erros ao longo do tempo (intervalos de 5 minutos)AppTraces| where SeverityLevel >= 3| summarize ErrorCount = count() by bin(TimeGenerated, 5m)| render timechart// Principais eventos de erroAppTraces| where SeverityLevel >= 3| extend parsed = parse_json(Message)| summarize Count = count() by tostring(parsed.event)| top 10 by Count
Boas Práticas para Logging JSONL
Seguir convenções consistentes entre seus serviços torna logs JSONL mais fáceis de consultar, correlacionar e criar alertas. Estas boas práticas são extraídas de sistemas de produção processando bilhões de entradas de log por dia.
Use Níveis de Log Consistentes
Adote uma escala de severidade padrão e use-a consistentemente em todos os serviços. A convenção mais comum usa cinco níveis: DEBUG para diagnósticos de desenvolvimento, INFO para operações normais, WARN para problemas recuperáveis, ERROR para falhas que requerem atenção e FATAL para crashes irrecuperáveis. Mapeie níveis numéricos (como a escala 10-60 do pino) para esses nomes de string no momento da ingestão para consultas uniformes.
{"level":"debug","event":"cache.miss","key":"user:42","timestamp":"2026-02-15T08:30:00Z"}{"level":"info","event":"request.completed","method":"GET","path":"/api/users","status":200,"duration_ms":45,"timestamp":"2026-02-15T08:30:01Z"}{"level":"warn","event":"rate_limit.approaching","client_id":"c-99","current":950,"limit":1000,"timestamp":"2026-02-15T08:30:02Z"}{"level":"error","event":"payment.failed","order_id":"ord-123","error":"card_declined","timestamp":"2026-02-15T08:30:03Z"}{"level":"fatal","event":"startup.failed","reason":"missing DATABASE_URL","timestamp":"2026-02-15T08:30:04Z"}
Padronize Nomes de Campos
Defina um schema compartilhado para campos comuns em todos os serviços. Use snake_case para nomes de campos, ISO 8601 para timestamps e nomenclatura consistente para identificadores de correlação. No mínimo, toda entrada de log deve conter: timestamp, level, event (um nome de evento legível por máquina) e service. Entradas com escopo de request devem incluir request_id e user_id para rastreamento.
// Schema de campos recomendado{"timestamp": "2026-02-15T08:30:00.000Z", // ISO 8601, sempre UTC"level": "info", // debug|info|warn|error|fatal"event": "order.created", // nome do evento em notação de ponto"service": "order-api", // serviço emissor"version": "1.4.2", // versão do serviço"request_id": "req-abc-123", // ID de correlação"trace_id": "4bf92f3577b34da6", // ID de trace distribuído"user_id": "u-42", // usuário autenticado"duration_ms": 120, // numérico, não string"order_id": "ord-789", // contexto específico do domínio"items_count": 3 // contexto específico do domínio}
Considerações de Desempenho
Logging estruturado adiciona overhead de serialização a cada chamada de log. Em serviços de alto throughput, isso pode se tornar significativo. Use logging assíncrono (pino, zerolog) para mover a serialização para fora do caminho crítico. Evite logar objetos grandes ou corpos de request/response no nível INFO. Use amostragem para logs de debug de alto volume. Escreva em arquivos locais e deixe um sidecar (Filebeat, Fluent Bit) lidar com o envio, em vez de enviar logs pela rede de forma síncrona.
Use loggers assíncronos (pino, zerolog) para evitar bloquear a thread principal durante a serialização JSON.
Nunca logue corpos completos de request ou response no nível INFO. Use o nível DEBUG e habilite-o apenas ao investigar problemas.
Amostre eventos de alto volume. Logue 1 a cada 100 requests de health-check em vez de todos eles.
Escreva logs em arquivos locais e envie-os de forma assíncrona com Filebeat ou Fluent Bit. Evite chamadas de rede síncronas no caminho de log.
Defina um tamanho máximo de linha de log (ex: 16KB) para evitar que entradas superdimensionadas congestionem o pipeline.
Faça rotação de arquivos de log com logrotate ou rotação integrada da biblioteca para prevenir esgotamento de disco.
Valide Seus Logs JSONL Online
Use nossas ferramentas gratuitas no navegador para inspecionar, validar e converter arquivos de log JSONL. Sem uploads necessários, todo o processamento acontece localmente.