Este artigo tem como foco principal mostrar na prática o uso do Log4j 2, dando foco a sua configuração de forma detalhada e muitos exemplos para você possa assimilar de forma fácil e rápida a utilização dessa poderosa framework.

Antes de começarmos a entrar em méritos mais técnicos que se referem a configuração propriamente dita, é importante saber que o Log4j 2 é a mais nova versão lançada pela Apache afim de melhorar o gerenciamento de Log's de uma determinada aplicação. Este veio para substituir o Log4j 1.x que possui algumas limitações que foram corrigidas nesta nova versão. Não entraremos em detalhes sobre as novidades do Log4j 2 pois não é foco deste artigo, mas vale ressaltar que há muitas diferenças entre ambas as versões e estas ficarão bem nítidas conforme as configurações forem explanadas nas seções abaixo.

Iniciando configuração

A configuração pode ser realizada de quatro formas:

  1. Através de um arquivo de configuração que pode ser escrito em XML, JSON ou YAML;
  2. Programaticamente, criando uma implementação das classes ConfigurationFactory e Configuration;
  3. Programaticamente, chamando as API's expostas na interface Configuration afim de adicionar componentes a configuração padrão;
  4. Programaticamente, chamando métodos internos a classe Logger;

Nosso artigo focará no caso 1, ou seja, explicaremos como realizar a configuração do Log4j 2 usando um arquivo de configuração, o que na maioria dos casos é uma ótima solução visto que o arquivo pode ser migrado de projeto para projeto, funcionando como um “plugin”, sem perder a produtividade.

Quando o Log4j é inicializado ele mesmo carrega as configurações de forma automática e isto é feito através da localização de todos os ConfigurationFactory. Por exemplo: Um ConfigurationFactory para o XML e outro para o arquivo JSON. Vejamos os passos para localização dessas configurações pelo framework:

  1. É tentando localizar o arquivo “log4j.configurationFile” (onde o pré-fixo configurationFile será XML ou JSON em nosso caso), e caso seja encontrado então é carregado o ConfigurationFactory correspondente a extensão do arquivo de configuração. Como dissemos anteriormente, o ConfigurationFactory para o XML é um e para o JSON é outro.
  2. Caso o passo 1 não seja atendido, é usado o ConfigurationFactory JSON para tentar encontrar os arquivos log4j2-test.json ou log4j2-test.jsn no classpath da aplicação.
  3. Se o caso 3 não for atendido, é usado o ConfigurationFactory XML para tentar encontrar o arquivo log4j2-test.xml no classpath da aplicação.
  4. Não sendo atendido os casos acima para localização dos arquivos de teste, é usado o ConfigurationFactory JSON para tentar localizar os arquivos log4j2.json ou log4j2.jsn no classpath da aplicação.
  5. Se o passo 5 não for atendido, é usado o ConfigurationFactory XML para tentar localizar o arquivo log4j2.xml no classpath da aplicação.
  6. Não sendo atendido nenhum dos passos acima e obviamente não havendo arquivo de configuração correto para a inicialização do Log4j, o DefaultConfiguration é utilizado, fazendo com que os eventos de log tenham saida no console.

Vamos iniciar vendo duas classes que fazem uso no Log4j, como mostram as Listagens 1 e 2.


  import com.foo.Bar;
   
  // Importando classes do log4j
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.LogManager;
   
  public class MyApp {
   
  // Define a static logger variable so that it references the
  // Logger instance named "MyApp".
   
  //Define uma variável logger static que referencia uma instancia de Logger
  //chamada de MyApp
  static final Logger logger = LogManager.getLogger(MyApp.class.getName());
   
  public static void main(String[] args) {
   
  //Uma aplicação simples que mostra o log no console
   
  logger.trace("Entering application.");
  Bar bar = new Bar();
  if (!bar.doIt()) {
    logger.error("Didn't do it.");
  }
  logger.trace("Exiting application.");                 }
  }
Listagem 1. Classe MyApp

  package com.foo;
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.LogManager;
   
  public class Bar {
    static final Logger logger = LogManager.getLogger(Bar.class.getName());
   
    public boolean doIt() {
      logger.entry();
      logger.error("Did it again!");
      return logger.exit(false);
    }
  }
Listagem 2. Classe Bar

Na Listagem 1 criamos uma variável logger que é uma instancia de Logger, nomeando este nosso Logger com o nome “MyApp”. Usamos aqui o nome qualificado da classe MyApp, ou seja, se ela fizesse parte de algum pacote, ficaria algo como “com.MyApp”. No classe MyApp usa a classe definida na Listagem 2 como “com.foo.Bar”, mas atente a linha “Bar.class.getName()” isso faz com que o nome do nosso Logger seja “com.foo.Bar”.

Como ainda não configuramos nenhum arquivo para determinar os detalhes da configuração de log do nosso projeto, o Log4j usará uma configuração padrão, como citado anteriormente, disponibilizada através da classe DefaultConfiguration. Vejamos qual a configuração padrão disponibilizada:


  • Um ConsoleAppender é anexado ao root Logger, ou seja, todas as saídas de log serão para o console;
  • O mesmo ConsoleAppender mencionado acima tem um PatternLayout definido com o seguinte padrão de saída: "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n";
  • Por padrão o root Logger é configurado para usar o nível ERROR, ou seja, Level.ERROR.

Dadas as informações padrões descritas acima, a saída no console da nossa Listagem 1 e Listagem 2 será algo como presente na Listagem 3.


  17:13:01.540 [main] ERROR com.foo.Bar - Did it again!
  17:13:01.540 [main] ERROR MyApp - Didn't do it.
Listagem 3. Saída no console das Listagens 1 e 2

Se fossemos criar um arquivo de configuração baseada nas configurações padrões citadas acima, ficaria como a Listagem 4.


  <?xml version="1.0" encoding="UTF-8"?>
  <Configuration status="WARN">
    <Appenders>
      <Console name="Console" target="SYSTEM_OUT">
        <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
      </Console>
    </Appenders>
    <Loggers>
      <Root level="error">
        <AppenderRef ref="Console"/>
      </Root>
    </Loggers>
  </Configuration>
Listagem 4. Arquivo de configuração baseado na configuração padrão

Você irá perceber que mesmo colocando o arquivo de configuração acima (log4j2.xml) não irá mudar em nada na saída do console apresentado na Listagem 3, por outro lado se você mudar a tag para você terá a saída presente na Listagem 5.


  17:13:01.540 [main] TRACE MyApp - Entering application.
  17:13:01.540 [main] TRACE com.foo.Bar - entry
  17:13:01.540 [main] ERROR com.foo.Bar - Did it again!
  17:13:01.540 [main] TRACE com.foo.Bar - exit with (false)
  17:13:01.540 [main] ERROR MyApp - Didn't do it.
  17:13:01.540 [main] TRACE MyApp - Exiting application.
Listagem 5. Mudando level para trace

Veja como fica nosso arquivo de configuração no nível trace, presente na Listagem 6.


  <?xml version="1.0" encoding="UTF-8"?>
  <Configuration status="WARN">
    <Appenders>
      <Console name="Console" target="SYSTEM_OUT">
        <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
      </Console>
    </Appenders>
    <Loggers>
      <Root level="trace">
        <AppenderRef ref="Console"/>
      </Root>
    </Loggers>
  </Configuration>
Listagem 6. Arquivo de configuração com trace no root logger

No caso acima estamos definindo todos os Logger's, que não possuem seu nível de configuração específico, como trace. Em nosso caso, a classe MyApp e Bar ficam com nível trace. Mas e se desejássemos que apenas a classe Bar possuísse nível trace? Observe a Listagem 7.


  <?xml version="1.0" encoding="UTF-8"?>
  <Configuration status="WARN">
    <Appenders>
      <Console name="Console" target="SYSTEM_OUT">
        <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
      </Console>
    </Appenders>
    <Loggers>
      <logger name="com.foo.Bar" level="TRACE"/>
      <Root level="error">
        <AppenderRef ref="Console"/>
      </Root>
    </Loggers>
  </Configuration>
Listagem 7. Definindo nível de log para classe Bar

Na Listagem 7 estamos explicitamente criando um nível de Logger TRACE apenas para a classe “com.foo.Bar”, isto significa que todos os eventos de log serão mostrados para a classe Bar, enquanto que para as outras classes apenas o nível ERROR será mostrado.

Usando a propriedade Additivity

Na Listagem 7 você deve ter percebido que não utilizamos a tag “” dentro do logger “com.foo.Bar”, isto porque o recurso conhecido como additivity garante que o Appender do pai será utilizado caso não definíssemos nenhum, e em nosso caso o appender do Logger “com.foo.Bar” é o mesmo definido no root Logger, ou seja, “Console”. Esta é uma ótima técnica para definir os Appenders de quem não foi configurado, porém em alguns casos pode ser um problema, vejamos a configuração presente na Listagem 8.


  <?xml version="1.0" encoding="UTF-8"?>
  <Configuration status="WARN">
    <Appenders>
      <Console name="Console" target="SYSTEM_OUT">
        <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
      </Console>
    </Appenders>
    <Loggers>
      <Logger name="com.foo.Bar" level="trace">
        <AppenderRef ref="Console"/>
      </Logger>
      <Root level="error">
        <AppenderRef ref="Console"/>
      </Root>
    </Loggers>
  </Configuration>
Listagem 8. Configurando Appender Console para Logger com.foo.bar

Se você executar o seu projeto usando as configurações acima, então verá uma duplicação de logs no seu console, algo parecido com a Listagem 9.


  17:13:01.540 [main] TRACE com.foo.Bar - entry
  17:13:01.540 [main] TRACE com.foo.Bar - entry
  17:13:01.540 [main] ERROR com.foo.Bar - Did it again!
  17:13:01.540 [main] TRACE com.foo.Bar - exit (false)
  17:13:01.540 [main] TRACE com.foo.Bar - exit (false)
  17:13:01.540 [main] ERROR MyApp - Didn't do it.
Listagem 9. Duplicação de logs devido a Listagem 8

Entenda porque a duplicação ocorre: Primeiramente é enviado o evento de log para o Appender 'Console' que está anexado ao Logger com.foo.Bar e este mostra no console a mensagem devida, depois disso (devido ao recurso additivity) é usado o Appender do root Logger que também mostra a mesma mensagem no console, provendo assim a duplicação de mensagens.

Para resolver tal problema a solução é desativar o additivity do Logger com.foo.Bar, vejamos como fazer isso com o código da Listagem 10.


  <?xml version="1.0" encoding="UTF-8"?>
  <Configuration status="WARN">
    <Appenders>
      <Console name="Console" target="SYSTEM_OUT">
        <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
      </Console>
    </Appenders>
    <Loggers>
      <Logger name="com.foo.Bar" level="trace" additivity="false">
        <AppenderRef ref="Console"/>
      </Logger>
      <Root level="error">
        <AppenderRef ref="Console"/>
      </Root>
    </Loggers>
  </Configuration>
Listagem 10. Desativando o additivity

Desativando o additivity os eventos de log não serão passados aos pais, ou seja, se o com.foo.Bar tiver outro pai que não seja o root Logger, este também será desconsiderado.

Reconfiguração Automática

Uma das novidades advindas no log4j2 é a possibilidade de reconfiguração automática do arquivo de configuração sem perder nenhum evento de log. O Log4j detecta mudanças no arquivo de configuração e realiza a reconfiguração necessária para que as alterações entrem e vigor. Há um atributo chamado monitorInterval que especifica o intervalo em segundos em que as configurações devem ser checadas a procura de alterações, onde o tempo mínimo é de 5 segundos. Veja o código da Listagem 11.


  <?xml version="1.0" encoding="UTF-8"?>
  <Configuration status="WARN" monitorInterval="30">
    <Appenders>
      <Console name="Console" target="SYSTEM_OUT">
        <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
      </Console>
    </Appenders>
    <Loggers>
      <Logger name="com.foo.Bar" level="trace" additivity="false">
        <AppenderRef ref="Console"/>
      </Logger>
      <Root level="error">
        <AppenderRef ref="Console"/>
      </Root>
    </Loggers>
  </Configuration>
Listagem 11. Configurando o monitorInterval em 30 segundos

Sintaxe de Configuração XML

Vejamos agora com mais detalhes como funciona a configuração em XML do log4j2, dando ênfase ao uso das tags e seus detalhes. Podemos realizar diversos tipos de configurações simples ou complexas como por exemplo: Desabilitar os logs de certa parte da aplicação, mostrar o log apenas quando determinado critério for atendido como por exemplo uma ação sendo executado por determinado usuário e etc.

A tag aceito diversos atributos, são eles:


  1. advertiser: A única opção disponível é 'multicastdns', que é a propriedade responsável por publicar os eventos de log em uma fonte externa.
  2. dest: Irá enviar a saída para stderr, arquivo ou URL.
  3. monitorInterval: Quantidade de tempo em segundos para que seja checado se houve mudanças no arquivo de configuração.
  4. name: Nome do configuration
  5. packages: Uma lista de nomes de pacotes separadas por virgulas, esses pacotes referem-se a plugins. Atentando que os plugins são carregados no classloader, sendo assim o mecanismo de reconfiguração não irá alterar os plugins caso eles sejam alterados no arquivo de configuração.
  6. schema: Usado para carregar o XML Schema afim de validar a configuração. Este é válido apenas quando o atributo 'strict' está setado como 'true', caso contrário este será ignorado.
  7. shutdownHook: Por padrão este está setado como 'true' e indica que o Log4j é automaticamente desligado quando a JVM também é desligada, caso seja setado como 'false' o Log4j continuará ativo mesmo depois que a JVM for desligada.
  8. status: Esta propriedade serve para configurar o nível de log de eventos internos do log4j, estes podem ser: trace, debug, info, warn, error e fatal. Log4j irá mostrar detalhes sobre a sua inicialização, ações internas e etc. Por exemplo: Se você precisa solucionar problemas relacionados ao log4j você pode usar o nível “trace” que lhe dará todos os detalhes necessários a respeito de ações internas do log4j.
  9. strict: Habilita a validação do XML usando um schema específico.
  10. verbose: Habilita informações de diagnóstico enquanto os plugins estão sendo carregados.

Existem duas formas de se trabalhar com XML: strict ou concise. De forma resumida, o strict XML provê uma validação de forma que este deve seguir um padrão mais rigoroso, por outro lado o concise lhe proporciona maior liberdade para trabalhar mas torna seu código “menos padronizado”.

Vejamos um exemplo usando a forma 'concise' na Listagem 12.


  <?xml version="1.0" encoding="UTF-8"?>;
  <Configuration>
    <Properties>
      <Property name="name1">value</property>
      <Property name="name2" value="value2"/>
    </Properties>
    <filter  ... />
    <Appenders>
      <appender ... >
        <filter  ... />
      </appender>
      ...
    </Appenders>
    <Loggers>
      <Logger name="name1">
        <filter  ... />
      </Logger>
      ...
      <Root level="level">
        <AppenderRef ref="name"/>
      </Root>
    </Loggers>
  </Configuration>
Listagem 12. Usando XML Concise

Atente para as linhas que possuem as reticências em appender e filter. Estes foram criados usando a forma concise do XML, se fossemos usar strict XML a estrutura seria como segue na Listagem 13.


  <?xml version="1.0" encoding="UTF-8"?>;
  <Configuration>
    <Properties>
      <Property name="name1">value</property>
      <Property name="name2" value="value2"/>
    </Properties>
    <Filter type="type" ... />
    <Appenders>
      <Appender type="type" name="name">
        <Filter type="type" ... />
      </Appender>
      ...
    </Appenders>
    <Loggers>
      <Logger name="name1">
        <Filter type="type" ... />
      </Logger>
      ...
      <Root level="level">
        <AppenderRef ref="name"/>
      </Root>
    </Loggers>
  </Configuration>
Listagem 13. Usando Strict XML

A diferença é que precisamos seguir o padrão “” definido, sendo assim independente do nome do usado para o seu Appender ele sempre terá o mesmo padrão dos outros.

Vamos ver um exemplo mais simples na Listagem 14.


  <patternLayout pattern="%m%n"/>
Listagem 14. Usando concise XML com patternLayout

Perceba que o patternLayout é um tipo de Layout, mas com o concise XML nós conseguimos definir diretamente qual Layout vamos usar, agora veja como ficaria com o strict XML na Listagem 15.


   <Layout type="PatternLayout">
          <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
        </Layout>
Listagem 15. Usando strict XML com patternLayout

Na Listagem 15, independente do tipo de layout que vamos usar, o padrão sempre será o mesmo: “.

Usando JSON

Para finalizar este artigo, nossa última seção tratará da construção do mesmo arquivo de configuração apresentado nas seções anteriores em XML, porém agora usando JSON. Veja um exemplo na Listagem 16.


  { "configuration": { "status": "error", "name": "RoutingTest",
                       "packages": "org.apache.logging.log4j.test",
        "properties": {
          "property": { "name": "filename",
                        "value" : "target/rolling1/rollingtest-${sd:type}.log" }
        },
      "ThresholdFilter": { "level": "debug" },
      "appenders": {
        "Console": { "name": "STDOUT",
          "PatternLayout": { "pattern": "%m%n" }
        },
        "List": { "name": "List",
          "ThresholdFilter": { "level": "debug" }
        },
        "Routing": { "name": "Routing",
          "Routes": { "pattern": "${sd:type}",
            "Route": [
              {
                "RollingFile": {
                  "name": "Rolling-${sd:type}", "fileName": "${filename}",
                  "filePattern": "target/rolling1/test1-${sd:type}.%i.log.gz",
                  "PatternLayout": {"pattern": "%d %p %c{1.} [%t] %m%n"},
                  "SizeBasedTriggeringPolicy": { "size": "500" }
                }
              },
              { "AppenderRef": "STDOUT", "key": "Audit"},
              { "AppenderRef": "List", "key": "Service"}
            ]
          }
        }
      },
      "loggers": {
        "logger": { "name": "EventLogger", "level": "info", "additivity": "false",
                    "AppenderRef": { "ref": "Routing" }},
        "root": { "level": "error", "AppenderRef": { "ref": "STDOUT" }}
      }
    }
  }
Listagem 16. Exemplo log4j2 em JSON

O formato em JSON é muito similar ao formato do concise XML, isto porque cada chave representa o nome exato do plugin que será utilizado. Por exemplo: O ThresholdFilter, Console e PatternLayout são todos plugins no arquivo JSON, no exemplo acima temos que o ThresholdFilter terá o nível DEBUG e o Console tem o nome STDOUT.

Este artigo teve como principal objetivo demonstrar na prática a configuração do Log4j2 em detalhes, usando um arquivo de configuração. Nosso enfoque foi maior no XML por ser o mais usado na maioria dos projetos, mas também mostramos de forma resumida como poderia ser feito usando JSON.