Logging estruturado em JSON: facilitando análise em produção

1. Por que logging estruturado é essencial em produção

Logs textuais tradicionais — como 2025-01-15 10:30:45 ERROR usuário não encontrado — são frágeis e difíceis de analisar em escala. Depender de grep e expressões regulares para extrair informações de logs não estruturados é ineficiente e propenso a erros. Cada linha exige parsing customizado, e metadados importantes (como ID da requisição, tempo de resposta ou ambiente) ficam perdidos ou exigem padrões de formatação inconsistentes.

O logging estruturado em JSON resolve esses problemas ao padronizar a saída. Cada entrada de log torna-se um objeto JSON legível por máquinas, permitindo consultas programáticas, correlação entre sistemas e integração direta com ferramentas de observabilidade. Em produção, onde milhares de logs são gerados por segundo, essa estruturação é a diferença entre conseguir ou não diagnosticar um incidente rapidamente.

2. Estrutura básica de um log em JSON

Um log JSON bem formado deve conter campos obrigatórios que garantam rastreabilidade mínima:

{
  "timestamp": "2025-01-15T10:30:45.123Z",
  "level": "ERROR",
  "message": "Usuário não encontrado",
  "service": "auth-service",
  "version": "1.2.3"
}

Campos enriquecidos adicionam contexto valioso para análise:

{
  "timestamp": "2025-01-15T10:30:45.123Z",
  "level": "WARN",
  "message": "Timeout na chamada externa",
  "service": "payment-service",
  "request_id": "req-abc123",
  "user_id": "usr-456",
  "duration_ms": 5023,
  "environment": "production",
  "region": "us-east-1"
}

Boas práticas de nomenclatura incluem o uso consistente de snake_case (adotado por ferramentas como Elasticsearch) ou camelCase (comum em Node.js). O importante é manter a padronização em toda a organização.

3. Implementação prática em diferentes linguagens

Python com python-json-logger

import logging
from pythonjsonlogger import jsonlogger

logger = logging.getLogger()
handler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter(
    fmt='%(timestamp)s %(level)s %(name)s %(message)s %(request_id)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)

logger.info("Pedido criado", extra={"request_id": "req-789", "amount": 150.00})

Node.js com pino

const pino = require('pino');
const logger = pino({
  level: 'info',
  base: { service: 'api-gateway' },
  timestamp: pino.stdTimeFunctions.isoTime
});

logger.info({ requestId: 'req-abc', userId: 'usr-123' }, 'Login realizado');

Go com zap

import "go.uber.org/zap"

logger, _ := zap.NewProduction()
defer logger.Sync()

logger.Info("Conexão estabelecida",
  zap.String("service", "database"),
  zap.Int("port", 5432),
  zap.Duration("latency", 2*time.Second),
)

4. Enriquecimento de logs com contexto distribuído

Em arquiteturas de microsserviços, um mesmo fluxo de usuário atravessa múltiplos serviços. Para correlacionar logs entre eles, propague um correlation_id via headers HTTP ou mensagens em filas:

{
  "timestamp": "2025-01-15T10:30:45.123Z",
  "level": "INFO",
  "message": "Pagamento processado",
  "service": "payment-service",
  "correlation_id": "corr-xyz-789",
  "trace_id": "trace-0a1b2c3d",
  "span_id": "span-4e5f6a7b"
}

A integração com OpenTelemetry permite que logs, traces e métricas compartilhem o mesmo trace_id, criando uma visão unificada do sistema. Middlewares em gateways ou frameworks web podem injetar esses IDs automaticamente em cada requisição.

5. Ferramentas de coleta e análise de logs JSON

Ferramentas de agregação como Fluentd, Logstash e Vector reconhecem automaticamente logs JSON, eliminando a necessidade de parsers customizados. Basta configurar o input para aceitar JSON e o output para o destino desejado.

Para armazenamento e consulta, duas stacks dominam o mercado:

  • ELK (Elasticsearch + Logstash + Kibana): Elasticsearch indexa cada campo JSON automaticamente. Kibana permite criar dashboards e alertas com queries como level:ERROR AND service:auth-service.
  • Loki + Grafana: Loki é otimizado para logs, indexando apenas metadados. Grafana consulta logs com LogQL, similar ao PromQL para métricas.

Dica importante: defina mapeamentos explícitos no Elasticsearch para campos como duration_ms (tipo long) e timestamp (tipo date), evitando inferências incorretas que podem degradar a performance de consultas.

6. Boas práticas e armadilhas comuns

Controle de volume: logs DEBUG em produção podem gerar terabytes desnecessários. Use níveis adequados — ERROR para falhas, WARN para anomalias, INFO para eventos importantes. Configure amostragem para logs de alta frequência.

Sanitização de dados sensíveis: nunca logue senhas, tokens JWT, números de cartão ou PII (dados pessoais identificáveis). Crie filtros no logger para mascarar ou omitir campos como password, credit_card e ssn.

Tratamento de exceções: ao capturar exceções, registre o stack trace como campo JSON estruturado, não como string solta:

{
  "level": "ERROR",
  "message": "Falha ao conectar ao banco",
  "error": {
    "type": "ConnectionRefused",
    "message": "ECONNREFUSED 127.0.0.1:5432",
    "stack": "Error: connect ECONNREFUSED\n    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1159:16)"
  },
  "service": "user-service"
}

7. Monitoramento e alertas baseados em logs JSON

Logs JSON permitem criar métricas acionáveis sem instrumentar código adicional. No Kibana, uma query como level:ERROR AND service:payment-service pode gerar alertas quando a contagem ultrapassa um limiar em 5 minutos.

Exemplo de query para detectar picos de erro 5xx:

kubernetes.namespace:"production" AND http.status_code:[500 TO 599]

No Grafana com Loki, use LogQL para agregar:

sum(count_over_time({service="api-gateway"} |= `"level":"ERROR"` [5m])) > 10

Esses alertas devem ser acionáveis: direcionados ao canal certo (Slack, PagerDuty) e com contexto suficiente (service, região, trace_id) para acelerar a investigação.

8. Evolução: logs JSON como base para observabilidade moderna

Logs estruturados são a espinha dorsal da observabilidade moderna. Com OpenTelemetry, logs, traces e métricas convergem em um único formato, permitindo debugging profundo: um log de erro pode ser correlacionado ao trace completo da requisição e às métricas de latência do serviço.

Tendências emergentes incluem:

  • Schema registry para logs: definir schemas JSON válidos para cada tipo de log, garantindo consistência entre equipes.
  • Logging assíncrono: buffers em memória que escrevem logs em lote, reduzindo impacto na performance da aplicação.
  • High-performance logging: bibliotecas como zap (Go) e pino (Node.js) priorizam baixa alocação de memória e alta taxa de throughput.

Empresas que adotam logs JSON desde o início do projeto reduzem drasticamente o tempo médio de resolução de incidentes e ganham visibilidade real sobre o comportamento de seus sistemas em produção.

Referências