Log4j

Ramiro Lago (Octubre 2006)

Log4j: introducción

LOG4J es una forma de liberarse de println(). Es una herramienta para centralizar y administrar los mensajes de debugging y avisos de la aplicación

La primera y una de las mayores ventajas de cualquier API de logging sobre el tradicional System.out.println es la capacidad de habilitar y deshabilitar ciertos logs, mientras otros no sufren ninguna alteración. Esto se realiza categorizando los mensajes de logs de acuerdo al criterio del programador.



Instalación básica para usarlo en nuestro IDE (en nuestro caso es Eclipse 3.1)

Para empezar hay que decir que estamos hablando de la versión 1.2.8, es decir, el archivo log4j-1.2.8.jar.

El archivo log4j.jar se ha puesto en el jre\lib\ext. Si usas Eclipse hay que asegurarse de que sea este el JRE que usa Eclipse (no es raro tener varios JREs cargados, ver "Archivos de programa-Java"). El JRE que usamos en Eclipse se comprueba en Windows-Preferences-Java-Installed JREs. El Java Runtime Edition carga de forma automática las librerias que están en jre\lib\ext. En Eclipse se puede comprobar las librerias cargadas en Windows-Preferences-Java-Installed JREs, selecciona el JRE usado y pulsa "Edit". Al cargarse la librería, nuestro entorno de programación reconocerá las clases org.apache.log4j.Logger, org.apache.log4j.PropertyConfigurator, etc. No aparecerá el mensaje de error "The import org.apache.log4j.Logger cannot be resolves". Si se coloca en 'ext' no es necesario ponerlo en el CLASSPATH.

Otra opción es ponerlo en CLASSPATH.

Página donde puedes obtener jar, documentación, etc.



Configuración para nuestra aplicación

Ya hemos dicho que el archivo log4j.jar se ha puesto en el jre\lib\ext que utiliza nuestro IDE (Eclipse en nuestro caso).

La configuración del tipo de logging se realiza en el archivo log4j.properties. Un sencillo ejemplo, que lanza mensajes al archivo /doc/Java_eclipse/geniu/log/ser_geniu.log; más adelante veremos los detalles:


	log4j.rootCategory=ALL, Default
	log4j.appender.Default=org.apache.log4j.FileAppender
	log4j.appender.Default.Threshold=INFO
	log4j.appender.Default.ImmediateFlush=true
	log4j.appender.Default.file=/doc/Java_eclipse/geniu/log/ser_geniu.log
	log4j.appender.Default.layout=org.apache.log4j.PatternLayout
	log4j.appender.Default.layout.ConversionPattern=%d %-5p %C.%M(%L)===> %m%n
	log4j.appender.Default.append=false
	

¿Dónde colocar el archivo log4j.properties?. En el directorio que quieras. Pero ojo, no lo pongas derivando de WEB-INF/classes, ya que si haces "Clean" del proyecto, el IDE que utilices (Eclipse, JBUilder, etc.) lo eliminará. En este ejemplo lo hemos colocado en el directorio raíz del contexto de aplicación.

El archivo properties define el archivo log donde se volcarán los mensajes. Este archivo puedes ponerlo en el directorio LOG del contexto.

Un ejemplo de archivo de propiedades:

Propiedad Observaciones
log4j.rootCategory=ALL, Default Indica alias para las salidas de log, podemos tener varias
log4j.appender.Default=org.apache.log4j.FileAppender Para Default su appender (lugar donde se envian los mensajes) es un archivo
log4j.appender.Default.Threshold=INFO No mostrará mensajes por debajo del nivel INFO
log4j.appender.Default.ImmediateFlush=true Se vuelca el mensaje inmediatamente en el appender
log4j.appender.Default.file=/doc/Java_eclipse/geniu/log/ser_geniu.log Archivo (appender)
log4j.appender.Default.layout=org.apache.log4j.PatternLayout Tipo de diseño de los mensajes
log4j.appender.Default.layout.ConversionPattern=%d %-5p %C.%M(%L)===> %m%n Diseño del mensaje
log4j.appender.Default.append=false No añade, borra el contenido anterior

Los niveles de mensajes de menor a mayor prioridad:

Página donde explica el patrón de diseño de mensajes.



Ejemplo de programación

En este servlet:


package geniu.controlador;

import javax.servlet.http.HttpServlet;
import java.io.IOException;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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

import geniu.persistencia.LectorPropiedades;

/****************************************************************************************
 * @see Controlador
 ****************************************************************************************/
public class Controlador extends HttpServlet {

	private LectorPropiedades propAcciones;
	private ServletContext sc;
	
	Logger log = Logger.getLogger( this.getClass() );

	/*********************************************************************************
	 * INIT: se obtiene el contexto y se cargan las propiedades que representan
	 * las acciones
	 **********************************************************************************/
	public void init(ServletConfig config) throws ServletException {
		propAcciones = new LectorPropiedades( "acciones.properties");
		sc = config.getServletContext();
		
		PropertyConfigurator.configure( sc.getRealPath("/") + "log4j.properties");
		System.out.println("===> ARCHIVO LOGJ4.PROPERTIES: "+sc.getRealPath("/") + "log4j.properties");
		log.info( "INICIO DE CONTROLADOR" );
	}
	
	/*********************************************************************************
	 * DOPOST: Lee de las propiedades el componente al que se debe redirigir la llamada
	 **********************************************************************************/
	public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		String nomServlet = null;
	    try {
	    	//// Leo de request la entrada: página y operación (evento) que desencadena la llamada  
	    	StringBuffer propiedad = new StringBuffer();
	    	propiedad.append( request.getParameter("pagina") );
	    	propiedad.append( "." + request.getParameter("operacion" ));

	    	// Leo de las propiedades la salida (servlet/jsp al que se redirige la llamada)
	    	nomServlet = propAcciones.getParametro( propiedad.toString(), "");
		log.info( "CONTROLADOR. PROPIEDAD IN:" + propiedad + "  SERVLET OUT:" + nomServlet);

	    	//// Si no se ha podido leer la salida, página de error
	    	if ( nomServlet == null || nomServlet.length() == 0) {
		    	response.sendError( response.SC_NOT_FOUND, "Error en la redirección a " + nomServlet);
		    	return;
	    	}
	    	
	    	//// Redirijo la llamada
	    	RequestDispatcher dispatcher = sc.getRequestDispatcher( nomServlet);
	    	dispatcher.forward( request, response );

	    }
	    catch (Exception e) {
		log.error( "ERROR. MENSAJE: " + e.toString());
	    	e.printStackTrace();
	    	response.sendError( response.SC_NOT_FOUND, "Error en la redirección a " + nomServlet);
	    }
	}
	

Como resultado de este ejemplo obtendremos lo siguiente en ser_geniu.log:


13:59:49,312 INFO  geniu.controlador.Controlador.init(36)===> INICIO DE CONTROLADOR ...
2006-11-01 13:59:49,312 INFO  geniu.controlador.Controlador.doPost(53)===> CONTROLADOR. PROPIEDAD IN:index.Cargar rangos  SERVLET OUT:/LectorTablasRangos.jsp
2006-11-01 13:59:49,359 DEBUG org.apache.catalina.core.ApplicationDispatcher.(143)===> servletPath=/LectorTablasRangos.jsp, pathInfo=null, queryString=null, name=null
....

En el siguiente ejemplo tenemos algo más complejo: dos dispositivos de log, uno el típico archivo y otro es la consola (en este caso la consola de Eclipse)


	log4j.rootCategory=ALL, Default, MiConsola
	log4j.appender.Default=org.apache.log4j.FileAppender
	log4j.appender.Default.Threshold=DEBUG
	log4j.appender.Default.ImmediateFlush=true
	log4j.appender.Default.file=/doc/Java_eclipse/geniu/log/ser_geniu.log
	log4j.appender.Default.layout=org.apache.log4j.PatternLayout
	log4j.appender.Default.layout.ConversionPattern=%d %-5p %C.%M(%L)===> %m%n
	log4j.appender.Default.append=false
	log4j.appender.MiConsola=org.apache.log4j.ConsoleAppender
	log4j.appender.MiConsola.Threshold=INFO
	log4j.appender.MiConsola.ImmediateFlush=true
	log4j.appender.MiConsola.layout=org.apache.log4j.PatternLayout
	log4j.appender.MiConsola.layout.ConversionPattern=%d{ABSOLUTE} %-5p %C.%M(%L):%n==> %m%n
	


Un monitor de mensajes

Además, en las últimas versiones de log4j viene Chainsaw, un monitor gráfico de mensajes, en la forma de una aplicación Java. Antes de utilizarlo hay que ejecutarlo:

java org.apache.log4j.chainsaw.Main

Evidentemente esta ejecución es imposible si log4j.jar no está en jre/lib/ext o en el CLASSPATH (además java debe estar en el path del sistema).

Un vistazo a lo que ofrece Chainsaw:

Ahora vamos a poner un ejemplo de archivo properties que usa como appenders (salidas) tanto un archivo como Chainsaw:

log4j.rootCategory=ALL, Default, Chainsaw

log4j.appender.Default=org.apache.log4j.FileAppender

log4j.appender.Default.Threshold=INFO

log4j.appender.Default.ImmediateFlush=true

log4j.appender.Default.file=/doc/Java_eclipse/geniu/log/ser_geniu.log

log4j.appender.Default.layout=org.apache.log4j.PatternLayout

log4j.appender.Default.layout.ConversionPattern=%d %-5p %C.%M(%L)===> %m%n

log4j.appender.Default.append=false

log4j.appender.Chainsaw=org.apache.log4j.net.SocketAppender

log4j.appender.Chainsaw.Threshold=INFO

log4j.appender.Chainsaw.remoteHost=localhost

log4j.appender.Chainsaw.port=4445 --> Ojo con el puerto para que no haya conflictos por coincidencia de puertos con otras aplicaciones

log4j.appender.Chainsaw.locationInfo=true

Si hemos referenciado Chainsaw en el archivo log4j.properties y nos hemos olvidado de ejecutar Chainsaw antes de lanzar la aplicación nos aparecerá un aviso como este:


	log4j:ERROR Could not connect to remote log4j server at [localhost]. We will try again later.
	java.net.ConnectException: Connection refused: connect
	

Indica que no ha podido encontrar Chainsaw y conectarse a él. Normalmente no hay que preocuparse, no impedirá que se despliegue el contexto de aplicación.



Instalación en Tomcat

En algunas versiones de Tomcat el uso de log4j.jar puede dar lugar al siguiente mensaje cuando se arranca Tomcat:


	log4j:WARN No appenders could be found for logger
	log4j:WARN Please initialize the log4j system properly.
	

Explicación: Tomcat reconoce que log4j ha sido cargado, pero advierte que no hay archivo properties. No lo hay para Tomcat, sólo lo hemos puesto en nuestro contexto de aplicación. El mensaje es inofensivo, lo que se dice a continuación puede evitar esta advertencia.

Si queremos usar log4j en el propio Tomcat conviene poner este jar en common/lib. Junto con commons-logging.jar (este jar esta también sometido a múltiples versiones). El archivo log4j.properties se pondrá en common/classes. Por ejemplo:

log4j.rootLogger=info, R

log4j.appender.R=org.apache.log4j.ConsoleAppender

log4j.appender.R.layout=org.apache.log4j.PatternLayout

log4j.appender.R.layout.ConversionPattern=%-5p %-30.30c{1} %x - %m%n

En este ejemplo lo que hacemos es volcar de manera formateada los mensajes en la consola de Tomcat. Se podría usar un FileAppender.


Volver al índice