Skip to main content

Logs em Java: o que registrar e o que não registrar?

Escrito por
java loggging feature

11 de novembro de 2020

0 minutos de leitura

Os logs são uma ferramenta útil para identificar erros e depurar código. Para engenheiros e, mais especificamente, em ambientes de DevOps, eles são muito valiosos.

Além de sua função operacional, os logs também são fundamentais para a segurança em Java. Quando ocorre uma violação de segurança, os arquivos de log são o primeiro lugar onde procurar pistas sobre o que aconteceu. Mas, com tantos frameworks disponíveis para registrar logs em aplicações Java, como escolher o mais adequado? E, mais importante, como registrar logs em Java da maneira correta?

A qualidade dos logs é essencial. Registrar informações demais ou de menos pode ser catastrófico do ponto de vista da segurança em Java. Na verdade, o registro e monitoramento insuficientes (A10:2017) fazem parte da lista OWASP Top 10.

Neste artigo, vou apresentar uma abordagem prática para registrar logs em Java: o que devemos registrar, o que não devemos e como implementar esse processo corretamente.

O que devemos registrar?

Quando eu servia no exército, sempre que precisava relatar algo, usávamos o método 5W-H — Quem, O quê, Onde, Quando, Por quê e Como. Esse método também é uma excelente regra para criar mensagens de log.

Quem fez isso?

Identifique o usuário ou sistema que fez a solicitação. Se possível, registre o ID do usuário e o endereço IP.

O que está acontecendo?

Que tipo de mensagem de log é essa? Muitos desenvolvedores já conhecem os diferentes níveis de log usados para indicar a importância de uma mensagem (ERROR, WARN, INFO, DEBUG, TRACE). É recomendável distinguir entre logs funcionais, de segurança e, quando aplicável, de auditoria.

Onde aconteceu?

Identifique o local. Geralmente, você define um logger por classe, o que já é um bom começo. No entanto, também precisamos saber qual método específico está envolvido. Além disso, se você estiver trabalhando com uma arquitetura de microsserviços, é importante identificar o nó em questão.

Quando aconteceu?

O horário é essencial, então inclua um timestamp nos logs. Leve em conta as diferenças de fuso horário e, principalmente, as mudanças de horário que ocorrem duas vezes por ano. Recomendo sempre usar UTC para que todos entendam o horário.

Por que aconteceu?

Em geral, é isso que você precisa descobrir. Nem sempre dá para registrar o motivo em uma única linha de log. Às vezes, ele é evidente. Nesse caso, inclua-o na mensagem. Muitas vezes, só conseguimos explicar o “porquê” ao relacionar diferentes logs. Por isso, deixe as mensagens de log sempre muito claras.

Como aconteceu?

Assim como o “porquê”, o “como” geralmente não pode ser registrado em uma única mensagem. Por isso, correlacionar mensagens é extremamente importante, sobretudo em uma arquitetura baseada em serviços. Para isso, crie um ID de correlação exclusivo para cada solicitação recebida por cada serviço com uma interface de saída. Depois, registre esse ID em todas as mensagens. Se precisar enviá-lo a outro serviço, você pode usar o cabeçalho HTTP específico X-Correlation-ID para correlacionar as solicitações sem manter estado.

Registro de eventos de segurança

Java é usado principalmente em processos de back-end e interage diretamente com os dados. Por isso, precisamos acompanhar os eventos relacionados à segurança. É preciso priorizá-los de acordo com a importância, dando atenção especial aos que exigem privilégios elevados, como eventos de administrador.

Em geral, considere registrar:

  • logins bem-sucedidos e malsucedidos

  • logouts

  • alterações e redefinições de senha

  • criação e remoção de usuários

  • falhas de autorização

  • alterações nos níveis de acesso

Além disso, registre indicadores que possam apontar para ataques específicos.

  • erros de validação de entrada: identifique tags de script que possam levar a ataques XSS ou parâmetros como UNION, que indicam uma injeção de SQL

  • respostas HTTP 4xx e 5xx

  • quantidades absurdas de solicitações vindas de um mesmo endereço IP

  • verifique os intervalos de IP, por exemplo, se um serviço interno receber solicitações de um endereço IP externo

Uma ótima estratégia é definir limites para determinados eventos de segurança registrados. Você pode enviar alertas automaticamente quando esses limites forem ultrapassados. Assim, é possível agir de acordo e evitar danos ainda maiores.

O que não devemos registrar?

Registrar informações demais é tão prejudicial quanto registrar de menos. Você não quer encher os logs com informações inúteis em produção. Por isso, defina sempre um nível de log adequado para os sistemas de produção, como WARN ou ERROR.

Durante o desenvolvimento, os engenheiros podem registrar informações confidenciais que contenham dados pessoais para depurar aplicações. Embora devamos evitar isso, mensagens internas de depuração precisam usar o nível de log correto — por exemplo, DEBUG ou TRACE — e não devem ficar visíveis no ambiente de produção.

Como regra geral, uma aplicação não deve revelar dados específicos da aplicação ou dos usuários, pois um invasor pode usá-los. Além disso, ao expor informações pessoais, é muito provável que você deixe de cumprir as normas de privacidade. Mesmo que apenas pessoas internas tenham acesso aos logs, eles representam um ponto único de falha. Portanto, não queremos que essas informações caiam em mãos erradas.

Durante o desenvolvimento, pense no que você realmente precisa registrar. Precisamos incluir um ID interno, a senha sem criptografia ou os dados do cartão de crédito de alguém? Tenha cuidado para não exibir um objeto de dados inteiro em Java, pois a saída depende do método toString() desse objeto.

Ao registrar informações pessoais para depuração, contornamos muitas das nossas próprias políticas de segurança. O mais sensato é não registrar esse tipo de informação. Se for necessário registrar dados específicos, use o nível de log adequado, como DEBUG. Também defina um nível mais alto para os sistemas de produção, como WARN.

Em resumo: informações pessoais e confidenciais não devem aparecer nos logs de produção.

Logs de segurança em Java

Antes de tudo, use um framework de logging em Java. Parece óbvio, mas muitas aplicações ainda usam o antigo System.out.println() para escrever diretamente no console. Isso deve ser evitado a todo custo. Um bom linter, como Spotbugs, PMD ou SonarLint, pode ajudar.

Há muitos frameworks de logging diferentes para Java. Este artigo aborda Logback e Log4j2, que atualmente são os mais usados. Mas, independentemente do framework, é recomendável separar os logs de segurança dos logs gerais da aplicação. No Logback e no Log4j2, você pode fazer isso facilmente adicionando marcadores ao logger. Com um marcador específico, como SECURITY ou até AUDIT, você pode filtrar esses logs com facilidade. Outra opção é adicionar um appender extra ao logger e filtrar pelo marcador específico para armazenar ou exibir esses logs separadamente.

Logback

Para implementar o Logback, basta incluir a dependência logback-classic, como mostrado abaixo. Ela também inclui os pacotes logback-core e slf4j-api.

<dependency>
   <groupId>ch.qos.logback</groupId>
   <artifactId>logback-classic</artifactId>
   <version>1.2.3</version>
</dependency>

Marcadores

Você pode definir marcadores usando qualquer String e adicioná-los ao logger, independentemente do nível de log, como mostrado abaixo.

private static final Logger logger = LoggerFactory.getLogger(LogbackExample.class);
private static final Marker security = MarkerFactory.getMarker("SECURITY");

public boolean  login(String userName, String encryptedPassword) {
   logger.warn(security, "login attempt by user: {}", userName);
  ...
}

Configuração

No exemplo a seguir de configuração do Logback, criei dois appenders no arquivo logback.xml.

O primeiro appender exibe todas as linhas de log no console, inclusive os marcadores que eu tiver adicionado. O segundo filtra apenas os logs de segurança com base no marcador SECURITY e os grava em um arquivo separado a cada dia.

<configuration>
   <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
       <encoder>
           <pattern>%d{HH:mm:ss.SSS} [%thread] %-8marker %-5level %logger{36} - %msg%n</pattern>
       </encoder>
   </appender>

   <timestamp key="byDay" datePattern="yyyyMMdd"/>
   <appender name="SECURITY-FILE" class="ch.qos.logback.core.FileAppender">
       <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
           <evaluator class="ch.qos.logback.classic.boolex.OnMarkerEvaluator">
               <marker>SECURITY</marker>
           </evaluator>
           <OnMatch>ACCEPT</OnMatch>
           <OnMismatch>DENY</OnMismatch>
       </filter>
       <file> security-${byDay}.log</file>
       <append>true</append>
       <encoder>
           <pattern>%d{HH:mm:ss.SSS} [%thread] %-8marker %-5level %logger{36} - %msg%n</pattern>
       </encoder>
   </appender>

   <root level="warn">
       <appender-ref ref="STDOUT" />
       <appender-ref ref="SECURITY-FILE" />
   </root>
</configuration>

Log4j2

Para usar o Log4j2, inclua os pacotes log4j-api e log4j-core, como mostrado abaixo.

<dependency>
   <groupId>org.apache.logging.log4j</groupId>
   <artifactId>log4j-api</artifactId>
   <version>2.13.3</version>
</dependency>

<dependency>
   <groupId>org.apache.logging.log4j</groupId>
   <artifactId>log4j-core</artifactId>
   <version>2.13.3</version>
</dependency>

Marcadores

A criação e a inclusão de marcadores são semelhantes ao exemplo do Logback. A diferença é que, desta vez, usamos o gerenciador específico do Log4j para criar o logger e o marcador.

private static final Logger logger = LogManager.getLogger(Log4j2Example.class);
private static final Marker security = MarkerManager.getMarker("SECURITY");

public boolean login(String userName, String encryptedPassword) {
   logger.warn(security, "login attempt by user: {}", userName);
  ...
}

Configuração

Assim como na configuração do Logback, criei uma configuração para o Log4j2 com dois appenders, que armazenam os logs de segurança em um arquivo separado com base nos marcadores. Essa configuração deve estar no arquivo log4j2.xml no classpath.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
   <Appenders>
       <Console name="Console" target="SYSTEM_OUT">
           <PatternLayout pattern="%d{HH:mm:ss.SSS} [%thread] %-8marker %-5level %logger{36} - %msg%n"/>
       </Console>
       <File name="SECURITY-FILE" fileName="securityFile.log">
           <Filters>
               <MarkerFilter marker="SECURITY" onMatch="ACCEPT" onMismatch="DENY"/>
           </Filters>
           <PatternLayout pattern="%d{HH:mm:ss.SSS} [%thread] %-8marker %-5level %logger{36} - %msg%n"/>
       </File>
   </Appenders>

   <Loggers>
       <Root level="WARN">
           <AppenderRef ref="Console"/>
           <AppenderRef ref="SECURITY-FILE"/>
       </Root>
   </Loggers>
</Configuration>

Logs em sistemas distribuídos

Em sistemas distribuídos, é melhor não gravar os logs em um único arquivo por serviço. Prefira armazená-los em um local centralizado. Isso facilita relacionar as linhas de log e encontrar possíveis problemas.

Dica profissional: ao implementar o logging centralizado, é útil configurar as linhas de log no formato JSON. Assim, indexar e pesquisar fica mais fácil e rápido.

Além do logging centralizado, considere também monitorar ativamente as linhas de log e configurar alertas. Vários produtos podem gerar gráficos em tempo real com base nos seus logs e enviar alertas automaticamente quando os valores ultrapassarem um limite predefinido.

Conclusão

Registrar logs não é algo que se faz sem pensar. Analisar com atenção o que você registra, quando registra e como utiliza os logs é essencial para investigar problemas e agir rapidamente. A prevenção é melhor do que a solução, mas ainda é possível que alguém invada seu sistema — e você vai querer evitar danos ainda maiores.

Se esse momento difícil chegar, garanta que seus logs possam ajudar você!

Comece a jogar Capture the Flag

Aprenda a resolver desafios de Capture the Flag assistindo à gravação sob demanda do nosso workshop virtual introdutório.

Leia mais

blog feature ai violet
Blog

VulnBump: agentes de IA conseguem corrigir dependências vulneráveis sem quebrar o build?

O VulnBump testa agentes de IA em 307 atualizações reais de dependências. Uma correção só é considerada válida se eliminar a vulnerabilidade e mantiver o build e os testes funcionando.

blog feature snyk container custom base recommendations
Blog

Conheça o Evo Agentic AppSec: segurança que acompanha o ritmo do desenvolvimento com IA

O desenvolvimento impulsionado por IA está avançando mais rápido do que os fluxos de trabalho de segurança tradicionais. O AppSec agentivo da Snyk usa agentes de segurança para encontrar, corrigir e verificar vulnerabilidades continuamente, enquanto as pessoas definem os critérios.

Blog

Modelos de ponta encontraram as vulnerabilidades. Só o atacante encontrou as cadeias.

A análise estática encontrou as falhas, mas só os testes de ataque em aplicações ativas provaram como elas poderiam ser encadeadas para causar invasões. Uma comparação entre Evo COS, Claude Security e Claude Code Security.