Logger simplificado

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.

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