Logs estruturados e Loki
Log em texto livre é legível para uma pessoa lendo uma instância. Com trinta réplicas e
milhões de linhas por hora, ele vira um arquivo grande onde você faz grep torcendo pelo
melhor. Log estruturado é a mesma informação em campos — e a diferença aparece no dia em
que você precisa de “todos os erros de pagamento do cliente X na última hora”.
Campos estáveis desde o primeiro dia
Seção intitulada “Campos estáveis desde o primeiro dia”Emita JSON com um conjunto pequeno de campos que nunca mudam de nome:
{"ts":"2026-09-04T09:12:44.812Z","level":"error","service":"pedidos","env":"prod", "trace_id":"4bf92f3577b34da6a3ce929d0e0e4736","msg":"falha ao autorizar pagamento", "pedido_id":"9f3a","gateway":"cielo","http_status":502,"duracao_ms":1841}Três regras que evitam retrabalho:
- Uma mensagem por evento, com
msgestável e o que varia em campos.msgcom o valor interpolado impede agrupar por tipo de erro. trace_idem todo log de requisição. É o que liga log e trace sem adivinhação.- Nada de segredo ou dado pessoal. Log costuma ir para mais lugares e durar mais que o
banco; token, CPF e cabeçalho
Authorizationnão entram. Redija na origem, não no destino.
Níveis, com critério
Seção intitulada “Níveis, com critério”ERROR é o que exige alguém agir, e por isso não pode ser o nível padrão de exceção
esperada. WARN é degradação com recuperação automática. INFO marca transição de estado
do sistema (subiu, migrou, recarregou config), não cada passo do fluxo. DEBUG fica
desligado em produção, com chave para ligar por serviço quando precisar.
Se ERROR aparece mil vezes por minuto em operação normal, ele deixou de significar algo
— e o alerta baseado nele já foi silenciado por alguém.
Loki: índice de labels, não de conteúdo
Seção intitulada “Loki: índice de labels, não de conteúdo”Loki não indexa o texto. Ele indexa labels e guarda o resto comprimido, o que o torna barato — desde que os labels sejam poucos e de baixa cardinalidade.
{namespace="loja", app="pedidos"} |= "pagamento" # filtro de linha | json # extrai campos do JSON | http_status >= 500 # filtra por campo extraído | line_format "{{.pedido_id}} {{.gateway}} {{.msg}}"
# taxa de erro por gateway, a partir do logsum by (gateway) (rate({app="pedidos"} | json | level="error" [5m]))O erro clássico é promover pedido_id, trace_id ou pod_ip a label: cada valor cria um
fluxo novo e o índice explode. Label é namespace, app, env, cluster. O resto é
campo, extraído na consulta.
Correlação com trace
Seção intitulada “Correlação com trace”Com o trace_id no log e no span, a navegação vira um clique nos dois sentidos: do trace
lento para os logs daquela requisição, e do log de erro para o trace que o produziu.
Configure isso no datasource do Grafana (derivedFields no Loki) uma vez e ninguém mais
copia identificador à mão.
Retenção e custo
Seção intitulada “Retenção e custo”Log é o pilar que mais cresce e o que mais desperdiça. Reduza na origem, nesta ordem:
- Amostre o repetitivo. Log de acesso com sucesso pode ir a 1 em 10; erro vai sempre.
- Corte o inútil. Health check, heartbeat e stack trace duplicado de biblioteca.
- Retenção por classe. 7 a 14 dias para depuração, mais tempo só para o que tem exigência legal ou de auditoria — e esse vai para armazenamento frio.
- Meça quem gera. Volume por serviço é conversa objetiva com o time dono.
topk(10, sum by (app) (rate({namespace="loja"}[1h]))) # quem produz mais linhasO que você quer contar (quantos erros por minuto) é métrica, não log. Contar linha em consulta a cada painel custa caro; um counter na aplicação custa quase nada.
Próximo passo: quando o log diz “demorou 1841 ms” mas não diz onde, siga para Tracing distribuído.