CodeGym /Cursos /JAVA 25 SELF /Formatação e níveis de logs: boas práticas

Formatação e níveis de logs: boas práticas

JAVA 25 SELF
Nível 63 , Lição 1
Disponível

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
ERROR
Falhas críticas que fazem o sistema funcionar de forma errada ou parar totalmente “Erro de conexão com o banco de dados”
WARN
Avisos importantes que não são críticos, mas requerem atenção “Não foi possível encontrar o usuário, usando guest”
INFO
Eventos normais que refletem o funcionamento esperado do aplicativo “Usuário registrado: vasya”
DEBUG
Informações detalhadas para depuração; não é necessário em produção “Método checkPassword chamado com parâmetros ...”
TRACE
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.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION