Os logs da aplicação são essenciais para manter a visibilidade do que está ocorrendo internamente. Erros, mensagens informativas e de alerta podem ser facilmente lidas nas máquinas em que a aplicação é executada. Diagnosticar problemas ou simplesmente obter informações se torna simples. Mas às vezes podem não ajudar:

  • Não há uma forma de rastrear logs da mesma requisição
  • Muito ruído gerado por logs duplicados
  • Difícil de encontrar entradas específicas
  • Não existem mensagens relevantes

Podemos usar algumas técnicas para ajudar na qualidade dos logs, os tornando mais fáceis de entender.

O contexto conta

Como em muitas outras coisas, o contexto é importante para os logs. Assim podemos entender mais facilmente as mensagens, ainda mais em sistemas concorrentes, como em APIs. Nestes casos, se não tivermos uma forma de saber de qual request o log pertence, temos a seguinte situação:

2025-07-02T14:37:45.123Z [INFO]  Processing /users
2025-07-02T14:37:45.456Z [INFO]  Processing /users
2025-07-02T14:37:45.789Z [INFO]  Processing /orders
2025-07-02T14:37:46.250Z [INFO]  Finished /users (200 OK, 125 ms)
2025-07-02T14:37:46.300Z [ERROR] /users failed: Internal error
2025-07-02T14:37:46.550Z [ERROR] /orders failed: timeout

Temos um erro no log, mas não sabemos a qual entrada anterior ele se refere. Não há como saber apenas olhando os logs, apesar de existir o timestamp. Como é a natureza de APIs receber diferentes requisições, se não há como relacionar um log ao outro e as mensagens se tornam inúteis. Neste caso podemos contextualizar, relacionando mensagens diferentes em grupos:

2025-07-02T14:37:45.123Z [INFO]  [request_id:1000] Processing /users
2025-07-02T14:37:45.456Z [INFO]  [request_id:2001] Processing /users
2025-07-02T14:37:45.789Z [INFO]  [request_id:0100] Processing /orders
2025-07-02T14:37:46.250Z [INFO]  [request_id:1000] Finished /users (200 OK, 125 ms)
2025-07-02T14:37:46.300Z [ERROR] [request_id:2001] /users failed: Internal error
2025-07-02T14:37:46.550Z [ERROR] [request_id:0100] /orders failed: timeout

Agora sabemos que foi o request_id:2001.

É claro que os IDs são ilustrativos, podemos usar um simples UUID em cada um deles para gerar esse trace. Agora sabemos quem pertence a um request em específico, nos dado um fluxo cronológico mais fácil de entender.

Trate os logs como fluxo da aplicação

Para um debug real, saber qual a ordem das entradas não é o suficiente. Considere:

2025-07-02T14:37:45.123Z [INFO]  [request_id:200] Processing /users
2025-07-02T14:37:46.300Z [ERROR] [request_id:200] /users failed: Internal error

Sabemos qual chamada em /users foi completada com erro. Mas não sabemos exatamente o que ocorreu, não há mais informações. Requisições fazem várias tarefas como validar, salvar e executar lógica. Não sabemos em qual tarefa o erro foi emitido, apenas que algo de errado ocorreu. É difícil chegar a uma conclusão com esses dados. Em um cenário real, produtivo, ter o máximo de informações é importante. Assim podemos adicionar mais informações no log do que ocorreu no processo completo:

2025-07-02T14:37:45.123Z [INFO]  [request_id:200] Processing /users
2025-07-02T14:37:45.200Z [INFO]  [request_id:200] Validated with success
2025-07-02T14:37:45.200Z [INFO]  [request_id:200] User is admin, allow changes
2025-07-02T14:37:46.300Z [ERROR] [request_id:200] Repository error: query="SELECT id, name FROM users WHERE id = $1" args=[42] err="pq: relation \"users\" does not exist"
2025-07-02T14:37:46.350Z [ERROR] [request_id:200] /users failed: Internal error

Assim fica mais fácil de entender que o Banco de Dados foi o problema. Temos até a query problemática! Mas ainda temos mais um problema, está verboso demais e sem informações cruciais. Afinal, quem foi o user problemático?

Seja minimalista

O log acima está detalhado, mas há uma questão, a seguinte entrada não adiciona muito:

2025-07-02T14:37:45.200Z [INFO]  [request_id:200] Validated with success

Em uma API com RPS médio esse tipo de mensagem acaba gerando muita interferência no que realmente é importante. Logo podemos ignorar esse tipo de entrada. Podemos manter a entrada:

2025-07-02T14:37:45.200Z [INFO]  [request_id:200] User is admin, allow changes

Pois ela é significativa no processo e para o contexto essa informação pode ser importante.

Adicione logs que sejam úteis para o entendimento do fluxo.

Crie logs estruturados

Os logs que mostramos são difíceis de buscar, pois dependemos de texto interpolado. Buscas em texto não são muito produtivas e também adicionam muita verbosidade. Podemos estruturar em campos separados, adicionando mais detalhe:

2025-07-02T14:37:45.123Z [INFO] [request_id:200] [user:maria] Processing /users
2025-07-02T14:37:45.200Z [INFO] [request_id:200] [user:maria] [role:admin] Change allowed for role
2025-07-02T14:37:46.300Z [ERROR] [request_id:200] [user:maria] Repository error: query="SELECT id, name FROM users WHERE id = $1" args=[42] err="pq: relation \"users\" does not exist"
2025-07-02T14:37:46.350Z [ERROR] [request_id:200] [user:maria] /users failed: Internal error

De acordo com o log sabemos que o usuário maria possui privilégios administrativos. A regra permite alterações para este role. Assim temos mais detalhes da operação, temos mais insumos para verificar no código. Em ferramentas como o Kibana é mais fácil de filtrar e entender o que ocorreu em vez de depender de mensagens.

Concluindo

Logs contextualizados transformam a experiência de debug de “procurar agulha no palheiro” para “seguir um rastro claro”.

Com request_id, user_id e demais campos estruturados, você não apenas vê o que aconteceu, mas entende o fluxo completo da aplicação. É a diferença entre logs que confundem e logs que resolvem problemas.