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

Recomendado

structlog é 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.

Python: structlog
import structlog
# Configura structlog para saída JSONL
structlog.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 JSONL
log.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ápido

pino é 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.

Node.js: pino
import pino from 'pino';
// pino produz JSONL por padrão
const log = pino({
level: 'info',
timestamp: pino.stdTimeFunctions.isoTime,
});
// Cada chamada produz uma linha JSONL
log.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 contexto
const reqLog = log.child({ requestId: 'req-abc-123' });
reqLog.info({ path: '/api/users' }, 'request.start');

Go: zerolog

Zero Alocação

zerolog é 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.

Go: zerolog
package main
import (
"os"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
// Configura saída JSONL para stdout
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger()
// Cada chamada produz uma linha JSONL
log.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.

Configuração do Filebeat
# filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/app/*.jsonl
json.keys_under_root: true
json.add_error_key: true
json.message_key: message
json.overwrite_keys: true
output.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.

Pipeline do Logstash
# logstash.conf
input {
beats {
port => 5044
codec => json
}
}
filter {
# Parseia timestamp do campo JSON
date {
match => ["timestamp", "ISO8601"]
target => "@timestamp"
}
# Adiciona tag de ambiente
mutate {
add_field => { "environment" => "production" }
remove_field => ["agent", "ecs", "host"]
}
# Roteia por nível de log
if [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.

Configuração do Fluent Bit
[SERVICE]
Flush 5
Daemon Off
Log_Level info
Parsers_File parsers.conf
[INPUT]
Name tail
Path /var/log/app/*.jsonl
Parser json
Tag app.*
Refresh_Interval 5
[FILTER]
Name modify
Match app.*
Add cluster production-us-east-1
Add service my-api
[OUTPUT]
Name es
Match app.*
Host elasticsearch
Port 9200
Index app-logs
Type _doc
Logstash_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.

Configuração do Fluentd
# fluentd.conf
<source>
@type tail
path /var/log/app/*.jsonl
pos_file /var/log/fluentd/app.pos
tag app.logs
<parse>
@type json
time_key timestamp
time_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 elasticsearch
host elasticsearch
port 9200
index_name app-logs
logstash_format true
<buffer>
@type memory
flush_interval 5s
chunk_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

AWS

CloudWatch 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.

AWS CloudWatch Logs
# Exemplos de consultas CloudWatch Logs Insights
# Encontrar todos os erros na última hora
fields @timestamp, level, event, user_id
| filter level = "error"
| sort @timestamp desc
| limit 100
# Agregar contagem de erros por tipo de evento
fields event
| filter level = "error"
| stats count(*) as error_count by event
| sort error_count desc
# Latência P99 por endpoint
fields path, duration_ms
| filter ispresent(duration_ms)
| stats pct(duration_ms, 99) as p99 by path
| sort p99 desc

GCP Cloud Logging

GCP

Google 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.

GCP Cloud Logging
# Exemplo Python para Cloud Run / Cloud Functions
import json
import sys
def 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 estruturados
log("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

Azure

Azure 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.

Azure Monitor Logs
// Consultas KQL para logs JSONL no Azure Monitor
// Encontrar erros recentes
AppTraces
| 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 erro
AppTraces
| 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.

Use Níveis de Log Consistentes
{"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.

Padronize Nomes de Campos
// 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.

Inspecione Logs JSONL no Seu Navegador

Visualize, pesquise e valide arquivos de log JSONL de até 1GB. Carregamento instantâneo, processamento no cliente, 100% privado.

Perguntas Frequentes

Logging JSONL — Logs Estruturados, ELK/Fluentd e Ingestão...