Sempre quis ter um logger que fosse simples e que bastasse escrever algo do tipo:
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:
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:
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:
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?
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:
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:
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.