Logger simplificado

Sempre quis ter um logger que fosse simples e que bastasse escrever algo do tipo:

1
error("Deu pau!");

Sem a necessidade de instanciar ou utilizar uma factory de loggers. Aí resolvi iniciar um projeto de logger para resolver esse meu problema. A primeira abordagem foi criar um logger utilizando annotation. Implementei o seguinte:

1
2
3
4
5
6
7
import static org.gvlabs.logger.LoggerInjector.initLog;
...
    @ConsoleLogger
    private static Logger consoleLogManager;
 
    @FileLogger(filePath = FILE_PATH)
    private static Logger fileLogManager;

E para utilizar no código, faria o seguinte:

1
2
consoleLogManager.error(TEST_MSG);
fileLogManager.error(TEST_MSG);

O problema maior era como eu iria injetar o Logger utilizando anotação em um projeto sem framework e não web. Criei uma outra classe para a injeção, mas como precisaria chamar ela antes da chamada do log, ficaria com a mesma complexidade de instanciar. Aí veio uma ideia: se eu criasse uma classe com métodos estáticos, não precisaria instanciar. Combinei essa ideia com as anotações e utilizando um bloco estático eu conseguiu injetar as instâncias nas declarações com anotação, mais ou menos da seguinte maneira:

1
2
3
static { initLog(TesteLogger.class); }
@ConsoleLogger
private static Logger consoleLogManager;

Com essa abordagem, eu passava a classe que continha as anotações, por reflexão eu inseria uma nova instância nos atributos anotados. Havia outro problema para ser resolvido: será que não haveria uma forma do Java saber que está dentro dessa classe e não obrigar o usuário digitar o nome da classe, conforme o código abaixo?

1
2
3
static { initLog(); }
@ConsoleLogger
private static Logger consoleLogManager;

Para conseguir isso, tive que pensar como saber dentro do método initLog a classe que chamou esse método. A resposta veio do stacktrace. Quem já viu um stacktrace de erro por exemplo, sabe que ele mostra uma sequencia de métodos, até chegar no último executado. É assim que o Java (e a maioria dos compiladores) executa as chamadas de métodos: utilizando a memória stack (empilhamento de métodos). Com essa informação, basta eu pegar na stack a classe do método executado anteriormente do initLog:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static Class<?> getClassToInject() {
    final StackTraceElement[] stes = Thread.currentThread().getStackTrace();
    boolean foundLogClass = false;
    String className = null;
    for (StackTraceElement ste : stes) {
        className = ste.getClassName();
        if (className.equals(LoggerInjector.class.getCanonicalName())) {
            foundLogClass = true;
        } else {
            if (foundLogClass
                    && !className.startsWith("java.lang.reflect.")
                    && !className.startsWith("sun.reflect.")) {
                try {
                    return Class.forName(className);
                } catch (ClassNotFoundException e) {
                    JLOGGER.log(Level.SEVERE, "Unexpected error", e);
                }
            }
 
        }
 
    }
 
    return null;
}

A classe do initLog chama-se LoggerInjector. Esse método busca na stacktrace (linha 2 e 5) e se não for dos pacotes de reflection do Java (java.lang.reflect ou sun.reflect) retorna a classe do próximo método que está na pilha de métodos (stack). Com isso eu consigo recuperar a classe alvo e injetar. Utilizando essa mesma lógica, eu consigo automaticamente recuperar o método, a classe, a linha que chamou qualquer método do log e exibir melhor as informações do log. Ainda com o estático em mente, criei uma classe com métodos estáticos que possui um Logger com annotation e com o bloco estático para inicializar. Dessa maneira agora é só utilizar o import static e chamar o método conforme o meu desejo inicial:

1
2
3
4
5
6
import static org.gvlabs.logger.StaticConsoleLogger.*;
...
    error("Deu pau!");
...
    info("Informações úteis");
...

Estou disponibilizando uma lib para quem quiser utilizar em seus projetos. O código fonte encontra-se disponível no Google Code e no Github.

Sobre: Thiago Galbiatti Vespa

Thiago Galbiatti Vespa é mestre em Ciências da Computação e Matemática Computacional pela USP e bacharel em Ciências da Computação pela UNESP. Coordenador de projetos do JavaNoroeste, membro do JCP (Java Community Process), consultor Oracle, arquiteto de software de empresas de médio e grande porte, palestrante de vários eventos e colaborador de projetos open source. Possui as certificações: Oracle Certified Master, Java EE 5 Enterprise Architect – Step 1, 2 and 3; Oracle WebCenter Portal 11g Certified Implementation Specialist; Oracle Service Oriented Architecture Infrastructure Implementation Certified Expert; Oracle Certified Professional, Java EE 5 Web Services Developer; Oracle Certified Expert, NetBeans Integrated Development Environment 6.1 Programmer; Oracle Certified Professional, Java Programmer; Oracle Certified Associate, Java SE 5/SE 6