Ejemplo con log4j

De ChuWiki

¿Qué es log4j?[editar]

Log4j es una librería adicional de java que permite a nuestra aplicación mostrar mensajes de información de lo que está sucediendo en ella, lo que habitualmente se conoce como un log. Tiene la ventaja sobre un vulgar System.out.println() que es mucho más configurable, permitiendo desde un fichero de configuración, eliminar determinados mensajes o sacarlos a un fichero, a una base de datos, por pantalla, a una ventana separada o símplemente por la salida estándar.

Vamos a ver aquí un ejemplo sencillo de uso con log4j, para empezar a ver sus posibilidades.

Necesitamos primero descargar la librería de log4j. Actualmente, la versión más moderna es la 1.2.15 y te la puedes descargar de http://logging.apache.org/log4j/1.2/download.html . Desempaquetamos el zip y tiene un montón de cosas, pero para nuestro uso nos vale con el fichero log4j-1.2.15.jar que aparece.

Ahora creamos un proyecto java con nuestro IDE favorito y añadimos este jar al proyecto o bien al CLASSPATH si no usamos un IDE.


Configuración básica de log4j[editar]

Lo primero que debemos hacer con log4j en nuestro código es darle una configuración. Esta configuración sirve para que log4j sepa dónde y cómo sacar nuestros mensajes de log. Es decir, para indicarle a log4j si debe sacar dichos mensajes por pantalla, a un fichero o a otro lado y para indicarle el formato de dichos mensajes: si debe poner la fecha y hora del mensaje, la línea de código donde se ha escrito el mensaje, el nombre de la clase, etc.

Una de las ventajas de log4j es que todo esto se puede configurar a través de un fichero, pero lo dejamos para más adelante. Vamos a ver ahora la configuración por defecto. Para ello, basta poner lo siguiente en nuestro código

import org.apache.log4j.BasicConfigurator;
...
BasicConfigurator.configure();

esto configurará log4j para que saque los mensajes de log por la estándar out (la pantalla habitualmente) con un formato concreto. Por ejemplo, si escribimos a través de log4j el mensaje "un warning", saldrá por pantalla así

0 [main] WARN Logger de Ejemplo  - un warning

donde todo lo que sale delante del texto "un warning" lo añade log4j y forma parte de esa configuración por defecto.

Una vez configurado log4j, debemos obtener un "Logger". Un "Logger" no es más que una instancia de una clase Logger de log4j a la que le decimos los mensajes que queremos mostrar y ella se encargá de darles formato (según la configuración inicial) y sacarlos por el sitio adecuado (pantalla en este caso). Una de las ventajas de log4j es que podemos obtener en una misma aplicación varios Logger distintos, configurados de distinta forma. De esta manera, por ejemplo, podemos poner todos aquellos mensajes relativos a conexiones y consultas a base de datos en un fichero, todos los mensajes relativos a accesos de usuarios en consola o todos los errores en un segundo fichero. Y cada Logger puede tener un formato distinto.

Para obtener un Logger debemos pedírselo a la clase Logger de log4j por un nombre. De esta forma, log4j creará un Logger con ese nombre si no existe ya, o nos devolverá el existente si ya lo tiene creado. El código puede ser así

import org.apache.log4j.Logger;
...
Logger log = Logger.getLogger("Logger de Ejemplo");

donde el texto "Logger de Ejemplo" es cualquier texto que queramos.

Una vez obtenido el Logger y guardado en la variable log, podemos sacar varios tipos de mensajes según su importancia, usando los métodos al efecto

log.trace("un mensaje");
log.debug("un mensaje");
log.info("un mensaje");
log.warn("un mensaje");
log.error("un mensaje");
log.fatal("un mensaje");

Ahí están ordenados por importancia, siendo el primero el de mensajes poco importantes. Aunque cada uno puede usarlos como quiera, yo suelo seguir el siguiente criterio:

  • trace() es el menos importante y se suele usar en la fase de desarrollo, para que el programador sepa por donde va pasando el programa. Es el típico System.out.println("paso por aquí").
  • debug() es para información útil para depurar, como algún resultado parcial, el valor de alguna variable, etc.
  • info() es para cosas normales en la aplicación que pueden tener cierto interés para mostrar en el log. Por ejemplo, se establece una conexión con base de datos, se conecta un cliente a nuestro servidor, un usuario entra en sesión, se salva la sesión en base de datos, etc.
  • warn() para pequeños fallos que se pueden recuperar fácilmente, por ejemplo, un usuario introduce una password errónea, un fichero de configuración no existe y la aplicación coge la configuración por defecto que se establezca en el código, etc.
  • error() para errores importantes, pero que no obligan a terminar la aplicación. Por ejemplo, no se puede uno conectar a la base de datos, aunque hay otras funcionalidades que sí pueden seguir ofreciéndose, aun sin base de datos.
  • fatal() para errores que obligan a terminar la aplicación, por ejemplo, se acaba la memoria disponible.

Un ejemplo completo de código sería el siguiente

package com.chuidiang.ejemplos;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

public class EjemploLog4j {

	public static void main(String[] args) {
		BasicConfigurator.configure();
		Logger log = Logger.getLogger("Logger de Ejemplo");
		log.warn("un warning");
		log.error("un error");
	}

}

y la salida de este programa sería

0 [main] WARN Logger de Ejemplo  - un warning
0 [main] ERROR Logger de Ejemplo  - un error

Obtener Loggers[editar]

Aunque el ejemplo básico anterior está bien como un inicio, la forma de obtener el Logger no es la más correcta ni la más habitual.

Aunque podemos obtener un Logger con un texto cualquiera, lo habitual es obtener un Logger para cada clase. Cada clase tiene su propio Logger. Además, no tiene sentido obtener el Logger cada vez que lo necesitemos, por eso suele guardarse el Logger en un atributo privado, final y estático de la clase, de esta forma

...
public class EjemploLog4j {

	private final static Logger log = Logger.getLogger(EjemploLog4j.class);

	public static void main(String[] args) {
           ...
        }
}

¿Por qué se hace así?. Al tener un Logger para cada clase, luego log4j nos permite configurar cada Logger de forma distinta, por lo que la salida de cada clase podría tener un formato distinto, ir a distinto sitio (ficheros distintos, pantalla) o incluso no salir el log de unas clases y sí el de otras.

Además, log4j construye una jerarquía de Loggers con el nombre que se le pasa, entendiendo el punto dentro de la cadena como separadador de la jerarquía, de forma similar a cómo las clases java se organizan en paquetes. De esta forma, si el nombre del Logger de una clase es "com.chuidiang.ejemplo.EjemploLog4j" y el de otra es "com.chuidiang.base_datos.BaseDatos", coincidiendo con el nombre de las clases, podemos configurar el Logger de nombre "com.chuidiang" de una determinada forma, y automáticamente la heredarán todas las clases por debajo del paquete "com.chuidiang", incluidas las dos mencionadas.

Si en nuestro programa hemos pensado bien nuestra jerarquía de paquetes, podemos configurar los Logger por paquetes, de forma que tendremos Loggers separados para funcionalidades distintas (base de datos, interface gráfica de usuario, algorítmica, etc).

Habitualmente el Logger se obtiene con Logger.getLogger(EjemploLog4j.class) en vez de usar un nombre en forma de String. log4j convierte esto al nombre de la clase completo, de forma que en el ejemplo, el nombre del Logger sería "com.chuidiang.ejemplos.EjemploLog4j".

Este logger suele guardarse en un atributo estático, final y privado de la clase, de forma que sólo puede usarlo ella, al ser estático es compartido por todas las instancias de la clase además de ser accesible en los métodos estáticos de la clase.

Configuración de los Logger[editar]

Hemos dicho muchas veces que los Logger se pueden configurar, aunque hasta ahora sólo hemos usado la configuración por defecto. Vamos a ver unos ejemplos básicos de cómo configurar estos Logger.

Se puede hacer, por supuesto, a base de llamar a métodos de la clase Logger, pero no es lo habitual. Lo habitual es meter esta configuración en un fichero de propiedades o un fichero XML y cargar dicha configuración desde nuestro programa. Vamos con una configuración básica con el fichero de propiedades. Creamos un fichero log4j.properties (o el nombre que queramos) con el siguiente contenido

En primer lugar, debemos decir para cada Logger que vayamos a usar dos cosas: a partir de que nivel de errores queremos que se muestren y cómo queremos que se muestren. Para ello, ponemos una línea así

log4j.logger.com.chuidiang=ALL,CONSOLA

Lo de log4j es fijo. Luego podemos poner logger si queremos hacer referencia a un Logger concreto de los que usemos en el programa, o bien rootCategory si queremos hacer referencia a todos los Logger del programa. En el ejemplo, hemos puesto log4j.logger. Luego va el nombre del Logger. En nuestro caso, el nombre completo era "com.chuidiang.ejemplo.EjemploLog4j", pero como indicamos anteriormente, podemos "cortar" el nombre en cualquiera de los puntos de separación y así afectará a todos los Logger de las clases cuyo paquete empiece por "com.chuidiang".

Luego ponemos el igual y dos palabrejas. La primera es el nivel a partir del cual queremos que se visualicen los mensajes. Si ponemos ALL, se visualizarán todos. Si ponemos, por ejemplo, WARN, sólo se verán los mensajes de warn() y los más importantes a WARN, que son error() y fatal(), pero no se visualizarán los de menor importancia trace(), debug() e info().

La segunda palabreja, CONSOLA, puede ser cualquier nombre que nosotros queramos y hace referencia a un dónde y cómo escribir dichos mensajes. Debemos, por tanto, definir CONSOLA más adelante en el fichero. Para ello ponemos las siguientes líneas

log4j.appender.CONSOLA=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLA.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLA.layout.ConversionPattern=%-4r [%t] %-5p %c - %m%n

Un appender de log4j es una clase encargada de dar formato al mensaje y escribirlo en algún sitio (fichero, base de datos, e-mail, pantalla, etc). En la primera línea indicamos que la clase encargada de formatear e imprimir los mensajes de CONSOLA será ConsoleAppender. Esta clase el propia de log4j y lo que hace es sacar los mensajes por pantalla.

La segunda línea añade layout al nombre de log4j.appender.CONSOLA y hace referencia a la clase que se encargará de dar formato a los mensajes. En este caso, usaremos PatternLayout, también una clase propia de Log4j. Esta clase coge un patrón que le digamos y escribirá los mensajes según ese patrón. Por ejemplo, podemos decirle que queremos los mensajes así: "fecha:hora NivelDelMensaje - ElMensaje", de forma que los mensajes saldrán todos con su fecha:hora, su nivel de importancia (warn, debug, info, error, etc) un guión y el mensaje.

¿Cómo inidicamos este formato?. Pues con la siguiente línea a la que después de layout añadimos .ConversionPattern y unos caracteres extraños que indican el formato. Tienes un listado de esos caracteres en http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html , pero vamos a ver los que hemos puesto en el ejemplo (que son los que salen por defecto)

  • %-4r El %r es el tiempo en milisegundos desde que se construyo el logger. El -4 indica que este número debe ocupar cuatro caracteres y estar alineado a la izquierda, añadiendo los espacios necesarios detrás hasta ocupar los cuatro caracteres.
  • [%t] El %t es el nombre del hilo en el que se hace el log. Se indica que este nombre se ponga entre corchetes.
  • %-5p El %p es el nivel del mensaje (DEBUG, INFO, WARN, etc) y debe ocupar 5 caracteres alineados a la izquierda.
  • %c es el nombre del Logger, que en nuestro ejemplo coincide con el nombre de la clase con paquete y todo.
  • - un guión
  • %m el mensaje
  • %n un fin de línea

Total, que nuestro fichero log4j.properties puede quedar así

log4j.logger.com.chuidiang=ALL,CONSOLA
#log4j.rootCategory=ALL,CONSOLA
log4j.appender.CONSOLA=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLA.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLA.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n

La línea que empieza por # es un comentario y correspondería a la configuración para todos los Loggers.

Ahora sólo nos falta, desde código, cargar este fichero de propiedades que, si recuerdas, hemos llamado log4j.properties. Para ello tenemos dos opciones

  • Por defecto, log4j busca un fichero log4j.properties en el CLASSPATH. Nos bastaría entonces con colocar este fichero con este nombre en algún sitio que esté en el CLASSPATH. Si nuestro programa en un jar, basta con meter el fichero log4j.properties dentro del jar en el directorio raíz del jar.
  • Poner el fichero log4j.properties en algún sitio y hacer un poco de código para que lo lea. El código quedaría asi
package com.chuidiang.ejemplos;

import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.Logger;

public class EjemploLog4j {

	private final static Logger log = Logger.getLogger(EjemploLog4j.class);

	public static void main(String[] args) {
		PropertyConfigurator.configure("log4j.properties");
		log.warn("un warning");
		log.error("un error");
	}

}

La carga se hace con la clase PropertyConfigurator, llamando al método configure() y pasando como parámetro el nombre del fichero con su path. Por supuesto, el fichero debe estar en el directorio actual de ejecución, puesto que no le hemos puesto ningún path.

Enlaces[editar]