1. Estrutura da mensagem de log
Vamos imaginar que logs não são apenas um “fluxo de consciência” do seu programa, mas um diário valioso no qual, daqui a um mês ou um ano, você ou um colega poderão encontrar a resposta para a pergunta: “O que diabos aconteceu aqui?”. Para que isso seja possível, cada mensagem de log deve ser estruturada. Normalmente (e isso é padrão na maioria das bibliotecas) cada mensagem contém:
- Hora do evento — quando isso aconteceu.
- Nível — o quão importante é (INFO, ERROR etc.).
- Nome do logger — geralmente o nome da classe ou do componente.
- Texto da mensagem — o que exatamente aconteceu.
- Stack trace (se houver erro) — para entender onde e por quê.
Aqui está um exemplo de uma linha de log bem formatada (exemplo com Log4j/SLF4J):
2024-06-16 18:42:07,123 INFO com.example.MainApp - Usuário entrou no sistema: username=vasya
E se ocorrer um erro:
2024-06-16 18:42:10,456 ERROR com.example.LoginService - Erro de autenticação do usuário: vasya
java.lang.IllegalArgumentException: Senha inválida
at com.example.LoginService.checkPassword(LoginService.java:42)
...
Por que isso é importante?
Quando o aplicativo fica em execução por muito tempo, os logs podem ocupar gigabytes. Se as mensagens não forem estruturadas, encontrar um problema vira um desafio do nível “adivinhe a melodia pelo barulho do ventilador”.
2. Formatação de mensagens
Por que não fazer assim:
logger.info("Usuário " + username + " entrou no sistema");
Parece simples, mas há uma pegadinha: mesmo que o nível de log atualmente esteja em ERROR, a string dentro dos parênteses ainda será montada (a concatenação acontece), e isso é gasto desnecessário de recursos. Em sistemas grandes, com milhares de linhas de log por segundo, isso pode resultar em atrasos reais.
Como fazer corretamente: templates e parâmetros
Bibliotecas modernas (por exemplo, SLF4J e Log4j 2) suportam templates com parâmetros:
logger.info("Usuário {} entrou no sistema", username);
Aqui a string só será montada se o nível de log permitir que a mensagem seja exibida. Se, por exemplo, estiver WARN, a string nem será montada — economia de recursos e de nervos.
Bônus: se você passar vários parâmetros, eles serão inseridos na ordem:
logger.info("Usuário {} executou a ação {} no objeto {}", username, action, objectId);
Registro de exceções (stack trace)
Se você captura uma exceção, não adicione manualmente o stack trace à mensagem:
// NÃO FAÇA ISSO:
logger.error("Erro: " + ex.getMessage() + "\n" + Arrays.toString(ex.getStackTrace()));
Correto:
logger.error("Erro ao processar a solicitação", ex);
SLF4J e Log4j adicionam o stack trace ao log de forma adequada por conta própria.
Exemplo: comparação de abordagens
// Ruim (a concatenação sempre é executada)
logger.debug("Objeto: " + expensiveToString(obj));
// Bom (formação preguiçosa)
logger.debug("Objeto: {}", obj);
3. Escolha dos níveis de log
Se no seu log tudo estiver no nível ERROR, isso já não é log — é “luz vermelha”. Se tudo estiver em DEBUG, você vai se afogar em detalhes. Vamos entender quando usar cada nível.
| Nível | Para que é usado | Exemplo de mensagem |
|---|---|---|
|
Falhas críticas que fazem o sistema funcionar de forma errada ou parar totalmente | “Erro de conexão com o banco de dados” |
|
Avisos importantes que não são críticos, mas requerem atenção | “Não foi possível encontrar o usuário, usando guest” |
|
Eventos normais que refletem o funcionamento esperado do aplicativo | “Usuário registrado: vasya” |
|
Informações detalhadas para depuração; não é necessário em produção | “Método checkPassword chamado com parâmetros ...” |
|
Informação mais detalhada, normalmente para diagnóstico profundo | “Início do loop de processamento: i=0” |
Exemplos de mensagens típicas
- ERROR — falha ao gravar arquivo, exceção não tratada capturada, serviço indisponível.
- WARN — API obsoleto, comportamento suspeito do usuário, limite de tentativas excedido.
- INFO — usuário entrou/saiu, processamento do pedido concluído, inicialização do aplicativo.
- DEBUG — parâmetros da solicitação, valores de variáveis, resultados intermediários de cálculos.
- TRACE — entrada/saída de métodos, loops internos, detalhes do funcionamento dos algoritmos.
Dica:
Em produção geralmente se habilita apenas INFO e acima, às vezes WARN e ERROR. DEBUG e TRACE — somente ao procurar bugs complexos.
4. Boas práticas de logging
Não faça log de dados sensíveis
Senhas, tokens, números de cartão de crédito — nada disso deve ir para os logs. Mesmo que pareça que “o arquivo de log é só para mim”, lembre-se do GDPR e do colega que pode enviar o log para o chat geral por engano.
// Ruim:
logger.info("Usuário {} entrou com a senha {}", username, password);
// Bom:
logger.info("Usuário {} entrou no sistema", username);
Não abuse do nível ERROR
Se você registra tudo usando logger.error, quando de fato ocorrer uma catástrofe, ninguém vai perceber — todos já se acostumaram às “luzes vermelhas”. Use ERROR apenas para situações em que o aplicativo realmente não pode continuar funcionando ou quando a lógica de negócio foi violada.
Registre exceções com stack trace completo
Não registre apenas ex.getMessage(), caso contrário você nunca saberá onde exatamente ocorreu o erro. Passe a exceção como segundo parâmetro para o logger.
logger.error("Erro ao processar a solicitação", ex);
Use identificadores únicos (correlação de eventos)
Em sistemas grandes é útil atribuir a cada solicitação, usuário ou operação um identificador único. Isso ajuda a “costurar” eventos de diferentes partes do sistema.
logger.info("Processamento do pedido iniciado: orderId={}", orderId);
logger.info("Pedido processado com sucesso: orderId={}", orderId);
Não registre tudo no log
Se há logs demais, eles se tornam inúteis. Não faça log de cada linha de código; do contrário, será impossível encontrar a informação necessária.
Formate as mensagens de forma clara
Escreva mensagens para que não apenas o autor do código, mas também quem for ler os logs daqui a seis meses, consiga entender. Evite siglas, abreviações obscuras e “piadas internas”.
5. Prática: configurando formato e níveis de log
Exemplo de configuração de formato no Log4j2 (log4j2.xml)
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
O que isso significa?
- %d{...} — hora do evento.
- %-5level — nível (ERROR, INFO etc.).
- %logger{36} — nome do logger (geralmente a classe).
- %msg — a própria mensagem.
Exemplo de código com diferentes níveis de log (SLF4J)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogDemo {
private static final Logger logger = LoggerFactory.getLogger(LogDemo.class);
public static void main(String[] args) {
logger.info("Aplicativo iniciado");
logger.debug("Valor da variável x: {}", 42);
try {
throw new IllegalArgumentException("Ai-ai-ai!");
// ...
} catch (Exception ex) {
logger.error("Ocorreu um erro ao iniciar", ex);
}
}
}
Demonstração da diferença entre níveis
Se nas configurações do logger o nível for INFO, mensagens com nível DEBUG e abaixo não serão exibidas. Tente alterar o nível para debug no config — você verá mais detalhes.
6. Erros comuns
Erro nº 1: concatenação de strings nos logs. Com muita frequência iniciantes escrevem assim:
logger.debug("Usuário: " + user.getName() + ", função: " + user.getRole());
Como resultado, mesmo com o nível DEBUG desativado, essas strings ainda serão montadas, o que gera carga extra. Use parâmetros!
Erro nº 2: log sem stack trace da exceção. Registram apenas a mensagem:
logger.error("Erro: " + ex.getMessage());
No fim, o log não contém informação de onde exatamente ocorreu o erro. Passe a exceção como segundo parâmetro!
Erro nº 3: registrar tudo no nível ERROR. Se tudo está vermelho — nada está vermelho. Use os níveis conforme o propósito, caso contrário erros importantes se perderão entre as “miudezas”.
Erro nº 4: log de dados sensíveis. Nunca registre senhas, tokens, números de cartão. Mesmo que pareça que ninguém vai ver, a vida adora surpresas.
Erro nº 5: mensagens incompreensíveis. Se uma mensagem de log parecer “ERR42: fail”, daqui a um mês você mesmo não lembrará o que isso significa. Escreva de forma clara e detalhada.
Erro nº 6: ausência de identificadores únicos. Em sistemas complexos, sem orderId, userId e outros identificadores você não conseguirá “costurar” os eventos e entender o que aconteceu com um usuário ou pedido específico.
GO TO FULL VERSION