en
· 4 min de leitura

Log estruturado ou nada

Linha de log que você não consegue consultar é linha que você vai ler uma vez, no incidente, tarde demais.

infraobservability

Existem dois tipos de logging nos sistemas que audito. O primeiro é texto: 'Usuário 4412 finalizou compra, total 189,90, levou 340ms'. O segundo é dado: um objeto JSON com user_id, event, total_cents, duration_ms e uma dúzia de campos de contexto. Os dois contêm a mesma informação. Só um deles é útil às 3 da manhã com um cliente no telefone.

O primeiro tipo exige um humano para ler. O segundo pode ser filtrado, agrupado, contado, cruzado e alertado por uma máquina. Depois que você trabalha com o segundo, o primeiro parece escrever um banco de dados em forma de romance.

O que estruturado realmente significa

Log estruturado não é 'a gente emite JSON'. É um contrato sobre campos. Toda linha tem o mesmo conjunto base: timestamp, level, service, version, environment, um id de requisição ou correlação e um nome curto de evento que identifica o que aconteceu. Em cima dessa base, cada evento adiciona os próprios campos, nomeados de forma consistente por todo o código.

O nome do evento é o campo que as pessoas esquecem. 'message' é texto livre e vai mudar toda vez que alguém encostar no código. 'event' é um identificador como order.paid ou auth.login_failed. É por ele que você agrupa, alerta e busca quando o texto livre já foi reescrito três vezes.

Consistência de nomes é a outra metade. Se um módulo loga user_id, outro loga userId e um terceiro loga uid, a query que cruza eles vai ser escrita errada, e a investigação vai concluir que o usuário nunca apareceu. Decida os nomes dos campos uma vez, coloque num módulo de logger compartilhado e faça o logger rejeitar campos desconhecidos no nível raiz se a plataforma permitir.

O logger é um módulo só

Num código TypeScript eu insisto que o logging passe por exatamente um módulo que encapsula a biblioteca que o time escolheu. Esse módulo é dono dos campos base, anexa o correlation id do contexto assíncrono para ninguém precisar passar na mão, redige chaves sensíveis conhecidas antes de emitir e expõe uma API pequena: um child logger com contexto amarrado e métodos de nível que recebem um nome de evento e um objeto de campos.

Esse ponto único de controle é o que torna o resto possível. Quando a plataforma de observabilidade muda, um arquivo muda. Quando um campo novo precisa ser redigido, um arquivo muda. Quando um id de requisição precisa fluir de um middleware do Next.js por uma server action até uma chamada de banco, o módulo anexa ele do async local storage e o resto do código nunca fica sabendo.

O que logar e o que deixar de fora

Logue toda transição de estado que importa para o negócio, com os identificadores necessários para encontrar de novo. Logue toda chamada a sistema externo com duração e resultado. Logue toda decisão que um humano possa questionar depois: por que uma requisição foi limitada, por que um pagamento foi retentado, por que um job foi pulado.

Deixe de fora qualquer coisa que seja secret, corpo completo de requisição, campo de senha, token, número de cartão ou dado pessoal que não seja necessário para achar o registro. Redija no logger, não no ponto de chamada, porque pontos de chamada são escritos com pressa. Deixe de fora o ruído em loops quentes: faça sampling, ou agregue num contador, mas não emita uma linha por iteração.

Regras que aplico em revisão

  • Toda linha de log tem um nome de evento que é identificador estável, não frase.
  • Toda linha de log dentro de uma requisição carrega o correlation id automaticamente.
  • Nomes de campos são consistentes por todo o código e documentados num lugar só.
  • Campos sensíveis são redigidos pelo logger, e a lista é testada.
  • Níveis significam algo: error é 'um humano deveria olhar', warn é 'esperado mas incomum', info é 'a linha do tempo do negócio', debug fica desligado em produção.

Nada disso é caro. É um dia para montar o módulo e um hábito para manter. O retorno é que o próximo incidente é investigado com queries em vez de scroll, e a pergunta do suporte 'o que aconteceu com meu pedido' tem resposta no tempo que leva para digitar o id.