Log4J

De Dos Ideas.
Saltar a: navegación, buscar

Log4J es una librería para resolver el log de aplicaciones Java.

Los Appender

Log4J utiliza "appenders" para guardar el log generado por las aplicaciones. Un appender es el encargado de procesar un mensaje de log enviado por la aplicación. Usualmente se encarga de almacenarlo en algún medio persistente, como ser un archivo.

Existen distintos appenders para guardar logs en archivos, base de datos o colas de mensajeria.

JMSAppnder

Log4J cuenta con un Appender especial que redirecciona los eventos de log a un Topic JMS.

El appender a utilizar es org.apache.log4j.net.JMSAppender, el cual se encarga de enviar los eventos de log a un Topic asociado. Un topic es parecido a una cola, pero con la diferencia que los mensajes se distribuyen a todos los suscriptores de la misma. Si no hay suscriptores, el mensaje se pierde.

El JMSAppnder es muy útil en un ambiente en cluster, para centralizar la información de los log en un único lugar.

Ejemplo de uso

En la práctica, utilizar log4j con este appender es igual que en cualquier otro caso. En el archivo de configuración log4j.properties es necesario agregar el appender en cuestión.

log4j.logger.com.zim=DEBUG, jms
log4j.appender.jms=org.apache.log4j.net.JMSAppender
log4j.appender.jms.InitialContextFactoryName=weblogic.jndi.WLInitialContextFactory
log4j.appender.jms.ProviderURL=t3://miApplicationServer:7001
log4j.appender.jms.TopicConnectionFactoryBindingName=JmsZimCF
log4j.appender.jms.TopicBindingName=ZimTopic
log4j.appender.jms.locationInfo=true

En el ejemplo, utilizamos el Connection Factory JmsZmiCF para conectarnos al topic ZimTopic (ambos teniendo que estar configurados en algún Servidor de Aplicaciones).

Con esta configuración todos los logs de las clases que pertenezcan al paquete "com.zim" se enviaran como mensajes al topic. Al Topic se envian objetos de tipo LoggingEvent, clase que contiene la información de un evento de log.

Luego, podrá haber distintos suscriptores que tomen las acciones necesarias sobre el mensaje. Por ejemplo, podriamos tener suscriptores que guarden el log en una base de datos, u otros que envien un mail o sms.

Sobre el locationInfo

Por default, el atributo locationInfo está en "false". Esto hace que NO se envien los datos para el objeto LocationInfo de LoggingEvent. Al setearlo en true, el objeto LocationInfo contendrá la información sobre la clase, método y número de línea en donde ocurrió el error.

Procesando el mensaje

Luego, queda tan solo decidir qué hacer con el mensaje. Usualmente, será un Message Driven Bean que procese de alguna manera el mensaje que envia Log4J.

¿Y qué envia exactamente? Log4J enviará un ObjectMessage de JMS, que contiene una instancia de LoggingEvent, la cual contiene la información relativa al log que generó la aplicación.

SMTPAppender

Es un Appender muy útil para la generación de alarmas vía mail.

NOTA 1: Por estar pensado para casos de error (o tal vez por bug) sólo se envía el mail cuando el nivel de log es ERROR o superior.

NOTA 2: Utilizar la versión de log4j 1.2.16 o superior (en las anteriores hay algún inconveniente sobre todo si se utiliza smtp.gmail.com).

Ejemplo de uso

Configuración en log4j.properties:

  1. Acordate!!! Nivel ERROR o superior

log4j.logger.com.demo.smtpAppender.MyService=ERROR, mailAppender

  1. SMTP appender para alarmas via mail

log4j.appender.mailAppender=org.apache.log4j.net.SMTPAppender log4j.appender.mailAppender.Threshold=WARN log4j.appender.mailAppender.BufferSize=10 log4j.appender.mailAppender.To=<mails_destino_separados_por_coma> log4j.appender.mailAppender.From=<mail_from> log4j.appender.mailAppender.SMTPHost=<smtp_host> log4j.appender.mailAppender.SMTPPort=<puerto> log4j.appender.mailAppender.SMTPUsername=<username> log4j.appender.mailAppender.SMTPPassword=<password> log4j.appender.mailAppender.SMTPProtocol=smtps log4j.appender.mailAppender.Subject=Alarma generada con Log4J log4j.appender.mailAppender.layout=org.apache.log4j.PatternLayout log4j.appender.mailAppender.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n

Con esta configuración los logs de nivel error o superior de la clase MyService enviarán un mail con la información que se loguee.


Log4j en J2EE, en servidores WebLogic

Si queres compartir los mismos componentes log4j en un módulo EJB y Web, y el EJB se usa localmente, en el ear tienen que estar el EJB y el Web. El log4-*.jar en /APP-INF/lib, y log4.properties en /APP-INF/classes. /APP-INF/ en la raíz del ear.

Y dentro del build-impl.xml del ear, los sgtes. pasos para la tarea que crea el META-INF:

<copy todir="${build.dir}/APP-INF/lib"> <fileset file="${log4j.jar}"/> </copy> <copy todir="${build.dir}/APP-INF/classes"> <fileset file="${log4j.properties}"/> </copy>

Log4J en Debug

Se puede usar el argumento -Dlog4j.debug como parametro para la JVM y muestra algunas cosas de log4J por consola, como por ejemplo, el archivo desde donde está levantando las propiedades.

Obtención del Logger

Existe una serie de variantes para definir y obtener el logger de una clase:

Los modificadores

private

Por lo general queremos que el logger se reporte como la clase que se está ejecutando. Utilizando private nos aseguramos que una clase que herede tenga que declarar su propio Logger y no usará accidentalmente el de la superclase. Contrariamente, si queremos por granularidad que utilice el de la superclase, habrá que declararlo como protected.

final

La referencia al logger es la misma durante todo el ciclo de vida de una clase (si se declara static) o de un objeto (si es atributo de instancia). Por lo tanto, declarándola final se expresa esto, se protege a una asignación errónea y se mejora la performance.

static

La creación del Logger está administrada por una fábrica a la que le pedimos las instancias. Es responsabilidad de la fábrica darnos la instancia correcta. En este caso, la motivación de utilizar static no es la de tener una sola copia. La motivación es que los logger no deben serializarse, deberían ser transient. Siendo static es una variable de la clase y no un atributo de la instancia, por lo que no será serializado. La razón de no marcarlo como transient (que sería conceptualmente más acertado) es que se debería implementar un mecanismo para que cuando la instancia es des-serializada, vuelva a obtener una copia del logger.

transient

Los logger no deben ser serializados, por eso deberían ser marcados como transient. Es más fácil marcarlos como static ya que de esta forma no es necesario volver a obtenerlos cuando la clase es des-serializada. Si la clase no es serializable, no es necesario.

Clave para la obtención del logger

El logger se le pide a la fábrica a través de una clave. Esta clave es de tipo String. El método está sobrecargado para recibir también un objeto de tipo class que luego la fábrica convertirá a String.

String

private static final Logger log = Logger.getLogger("com.foo.bar.Blah");

Utilizando un String se obtiene un logger independientemente de la clase donde se esté. Si lo que se busca el logger para la clase, esta es la forma más riesgosa ya que no está verificada por el compilador.

Objeto class

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

Utilizando un objeto de tipo Class permite que el compilador verifique el nombre de la clase. También se actualiza si se utiliza alguna herramienta o IDE para cambiar el nomre de la clase.

método getClass()

private transient Logger log = Logger.getLogger(getClass());

En este caso la clave se genera en tiempo de ejecución pidiendo al objeto su clase a través del método getClass(). Esto tiene la ventaja de que siempre tendrá el nombre apropiado de la clase. Tiene la restricción de que sólo se podrá utilizar si el logger es un atributo de instancia (no static).

Otra particularidad es que el método getClass() invocado en una clase padre responderá desde la clase hija.

En caso de que el objeto sea serializable habrá que agregar el modificador transient para que no se serialice el logger.

Conclusión

La forma práctica de no cometer errores es la de utilizar la combinación más restrictiva y cambiarla sólo si se tiene alguna razón claramente justificada e implementando los mecanismos necesarios.

Forma típica:

public class Blah {

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

}

Log4JMDC

MDC es una característica de Log4J que permite agregar información adicional (de forma "clave=valor"), que luego puede ser referenciada en el patrón de log. Aquí entonces podemos almacenar datos generales (como ser, el nombre de usuario) y mostrarlos en todos los logs que se generen.

En el patrón de log, se pueden hacer referencia a las variables del MDC usando:

%X{clave}

Los datos del MDC se guardan en el thread en ejecución. Un uso común es crear un Filter web, y allí guardar el nombre de usuario logueado en el entorno MDC, para que luego pueda loguearse.

Filter web con MDC

El siguiente es un filter web común que guarda la variable "username" en el MDC, para luego referenciarla en patrón de log4j.xml

/*

* To change this template, choose Tools | Templates
* and open the template in the editor.
*/

package com.dosideas.mdc.filter;

import java.io.IOException; import javax.servlet.*; import org.apache.log4j.MDC; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder;

/**

* Adds MDC information for Log4j. In particular, it adds the username (if
* available) to MDC.
*/

public class Log4jMDCFilter implements Filter {

   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
       String username = obtenerNombreDeUsuarioLogueado();
       try {
           MDC.put("username", username);
           chain.doFilter(request, response);
       } finally {
           MDC.remove("username");
       }
   }
   @Override
   public void init(FilterConfig filterConfig) throws ServletException { }
   @Override
   public void destroy() { }
   private String obtenerNombreDeUsuarioLogueado() { .... }

}

Luego, en el archivo log4j.xml podemos hacer uso de la variable username (noten el valor del atributo "ConversionPattern"): <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

   <appender name="console" class="org.apache.log4j.ConsoleAppender">
       <param name="Target" value="System.out"/>
       <layout class="org.apache.log4j.PatternLayout">
           <param name="ConversionPattern" value="%-5p %d{ISO8601} [%X{username}] %c - %m%n"/>
       </layout>
   </appender>
   <root>
       <priority value ="debug" />
       <appender-ref ref="console" />
   </root>

</log4j:configuration>

Modificar log4j en ejecución

Utilizando un listener de spring se puede configurar la ruta del archivo log4j.properties (fuera del empaquetado) y el tiempo en que se vuelve a leer el mismo, lo cual nos permite modificar sus valores en ejecucion sin necesidad de redeployar la aplicacion.

Para esto se configura dentro del web.xml

<listener>

 <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>

</listener> <context-param>

       <param-name>log4jConfigLocation</param-name>
       <param-value>file:/path/log4j.properties</param-value>

</context-param> <context-param>

       <param-name>log4jRefreshInterval</param-name>
       <param-value>5000</param-value>

</context-param>

Un error con el que nos podemos topar al momento de agregar esta configuración e intentar desplegar en Weblogic es el siguiente:

Cannot set web app root system property when WAR file is not expanded

Ante este error, la solución es agregar en el weblogic.xml dentro del tag <weblogic-web-app> el siguiente código:

<container-descriptor>

       <show-archived-real-path-enabled>true</show-archived-real-path-enabled>

</container-descriptor>

Configurar Log4j con Spring

Para configurar Log4j con Spring se debe agregar el siguiente bean a la aplicación:

<bean id="log4jInitializer"  class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
   <property name="staticMethod"  value="org.springframework.util.Log4jConfigurer.initLogging" />
   <property name="arguments">
      <list>
         <value>file:/path/log4j.properties</value>
         <value>1000</value>
      </list>
   </property>
</bean>

En el bean se esta indicando la ruta desde la cual se va a tomar el log4j.properties y el intervalo de refresco, el cual nos permite modificar sus valores en ejecucion sin necesidad de redeployar la aplicacion.

Ver también