08 abril 2011

Logging asíncrono a base de datos con log4j

En esta entrada voy a describir la solución arquitectónica elegida para implementar un sistema de log asíncrono a base de datos relacional basado en log4j.

Problema

El requerimiento de negocio principal que pretende resolver esta solución es guardar log en un almacén persistente de las operaciones realizadas por un sistema transaccional de alto rendimiento. Detrás de este requerimiento general se esconden los siguientes requerimientos y restricciones ya mucho más concretos:
  • El sistema de log no puede ralentizar las operaciones que se quieran tracear, parece indicado pensar en una solución asíncrona.
  • El sistema de log debe almacenar las operaciones en una base de datos relacional para su posterior análisis y consulta.
  • El sistema de log debe proporcionar a los clientes una interfaz simple de invocación.
  • El sistema de log debe ser extensible para incorporar, en un futuro, log de diferentes tipos.
  • El sistema de log será accesible en un servidor de aplicaciones Weblogic.

Solución

A grandes rasgos, la solución se basa en utilizar log4j en el cliente para enviar el mensaje de log de forma asíncrona a una cola JMS y consumir estos mensajes desde el servidor para, otra vez mediante log4j, enviar el mensaje de log a la base de datos relacional. La arquitectura del sistema de log se resume en el siguiente diagrama:


Cliente: El cliente puede ser una aplicación java “standalone” o bien algún componente de servidor, la única restricción que debe cumplir es que tiene que tener log4j configurado para poder mandar mensajes a la cola JMS. Los clientes pueden enviar mensajes de log a la cola JMS simplemente instanciando un logger de la categoría donde tienen configurado el appender a JMS y haciendo uso de las instrucciones “normales” de log4j, por ejemplo:
Logger logger = Logger.getLogger("jmsLogger");
logger.info("My first log messages!");
Configuración de log4j para el cliente (JMSAppender): El fichero de configuración de log4j para el cliente debe incluir un appender configurado para enviar log a una cola de mensajes JMS (en nuestro caso un cola de mensajes JMS dentro de un servidor de aplicaciones weblogic) y una categoría con este appender. El nombre de la categoría es lo que usará el cliente para obtener el Logger. Por ejemplo, el siguiente fragmento de fichero de log4j configura un appender JMS llamado JMSQueue que envía el log a un topic de weblogic. El appender se usa en una categoría llamada jmsLogger. Se debe especificar en el appender la factoría de contextos iniciales JNDI, la conexión a weblogic, el nombre de la factoria de conexiones a los topics (creada en el servidor de aplicaciones) y el nombre del topic que recibirá los mensajes (creada en el servidor de aplicaciones).
<appender name="JMSQueue" class="org.apache.log4j.net.JMSAppender">
<param name="InitialContextFactoryName" value="weblogic.jndi.WLInitialContextFactory"/>
<param name="ProviderURL" value="t3://localhost:7001" />
<param name="TopicConnectionFactoryBindingName" value="JmsLogConnectionFactory" />
<param name="TopicBindingName" value="LogTopic" />        
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p-%d{dd/MM HH:mm:ss,SS}-%c{1}-%m%n"/>
</layout>
</appender>
<!-- Categories -->        
<category name="jmsLogger" additivity="false">
<priority value="INFO" />
<appender-ref ref="JMSQueue" />                 
</category>
Consumidor de la cola de mensajes: El consumidor de la cola de mensajes se encarga de recibir los mensajes de log que van llegando al topic i redireccionarlos al appender de JDBC para guardar el mensaje en la base de datos relacional. El consumidor de la cola de mensajes será un Message Driven Bean desplegado en el  servidor de aplicaciones Weblogic. En nuestra solución se ha usado un consumidor de mensajes basado en Spring pero se puede usar cualquier
Cada vez que llega un mensaje a la cola de mensajes se ejecuta el método onMessage() del consumidor de la cola de mensajes:
/**
* Este método se invoca cada vez que llega un mensaje a la cola
* de mensajes JMS. Solo trata los mensajes provinientes de log4j.
*/
public void onMessage(Message message) {    
try {
if (message instanceof ObjectMessage && ((ObjectMessage) message).getObject() instanceof LoggingEvent) {
LoggingEvent logObject;
logObject = (LoggingEvent) ((ObjectMessage) message).getObject();        
Logger logger = Logger.getLogger(this.categoryName);    
logger.log(logObject.getLevel(), logObject.getMessage());                    
}
} catch (Exception e) {
Logger logger = Logger.getRootLogger();
logger.error("Couldn't log message to database", e);
}
}
Como vemos lo único que hace es redireccionar le mensaje de log recibido a log4j para que este lo guarde en la base de datos mediante el appender JDBC que tiene configurado.
Configuración de log4j para el servidor (JDBCAppender): El fichero de configuración de log4j para el servidor debe incluir un appender configurado para enviar log a una tabla de una base de datos relacional y una categoría con este appender. Por defecto el appender JDBC de log4j NO puede usar pools de conexiones a base de datos. Por temas de rendimiento y por el hecho de estar ejecutándose en un servidor de aplicaciones se ha usado un appender de JDBC que si permite la obtención de las conexiones mediante un pool.
El fichero log4j.xml refleja como se configura este appender:
<appender name="DB" class="org.apache.log4j.jdbcplus.JDBCAppender">
<param name="connector" value="com.foo.WeblogicPoolConnectionHandler"/>
<param name="dbclass" value="com.mysql.jdbc.Driver"/>     
<param name="sql" value="INSERT INTO LOGGING (id, prio, cat, thread, msg, layout_msg, throwable,
the_timestamp) VALUES (@INC@, '@PRIO@', '@CAT@', '@THREAD@', '@MSG@', '@LAYOUT:1@', '@THROWABLE@', '@TIMESTAMP@')"/>    
</appender>
<!-- Categories -->         
<category name="databaseLogger" additivity="false">
<priority value="INFO" />
<appender-ref ref="DB" />                       
</category>
en la configuración se especifica el uso de nuestro handler y la sentencia que usará el JDBCAppender para añadir el log a la tabla de la base de datos. Este appender no tenía ningún handler para trabajar con los pools de conexiones de Weblogic así que se ha creado uno: WeblogicPoolConnectionHandler:
/**
* ConnectionHandler que permite obtener conexiones JDBC de un 
* pool de conexiones activado en el JNDI de Weblogic
*
*/
public class WeblogicPoolConnectionHandler implements JDBCPoolConnectionHandler {
private String dsName = "logDataSource";
public void freeConnection(Connection conn) throws Exception {
conn.close();
}
public Connection getConnection() throws Exception {
Connection conn = null;
Context ctx = null;  
ctx = new InitialContext();
javax.sql.DataSource ds = (javax.sql.DataSource) ctx
.lookup(this.dsName);
conn = ds.getConnection();
return conn;
}
public Connection getConnection(String arg0, String arg1, String arg2)
throws Exception {
return this.getConnection();
}
}
Como veis nuestro Handler se encarga, simplemente, de obtener conexiones a la base de datos mediante un pool.
Con todo lo anterior, el funcionamiento básico del sistema es el siguiente:
  1. El cliente manda el mensaje de log a la categoría JMS de log4j.
  2. La configuración de log4j del cliente envía este mensaje a la cola JMS del servidor de aplicaciones que tenga configurada.
  3. El consumidor asociado a la cola de mensajes recibe el mensaje y lo reenvía a la categoría de base de datos de log4j.
  4. La configuración de log4j en el servidor envía ese mensaje a la base de datos relacional que tenga configurada. La conexión a la base de datos relacional se obtiene mediante un pool de conexiones del servidor de aplicaciones.
Salut! Xampi

03 abril 2011

Microsoft Dream Spark: un examen de certificación gratis para estudiantes hasta junio

dreamspark_300_250

Hola a todos, preparando el examen de certificación 70-516 TS: Accessing Data with Microsoft .NET Framework 4 vi que mediante la iniciativa DreamSpark, a parte de software gratuito para estudiantes y profesores universitarios, se están ofreciendo dos cosas realmente interesantes:

1.- Un examen de certificación gratuito para estudiantes: En la misma web de DreamSpark tenéis la posibilidad de obtener un Voucher Code que os permitirá acceder a un examen de certificación para la comunidad docente (72-XXX) de forma gratuita hasta el 30 de junio. Para poder realizar el examen “for free” basta con proporcionar vuestra dirección de correo electrónico de la universidad a la que pertenecéis, verificar-lo en la web de DreamSpark, obtener el Voucher Code. Al registraros en Prometric debéis seleccionar el examen como examen del programa 072 y no 070 y introducir el Voucher Code al final del proceso de schedule. El día del examen deberéis presentar vuestra acreditación como profesor o estudiante en el centro donde vayáis a realizarlo y listos!

2.- Suscripción de 90 días gratuita a Pluralsight: Igual que para la obtención del Voucher Code para el examen gratuito, debéis proporcionar vuestra dirección de correo y obtendréis un código de acceso a Pluralsight. Pluralsight es una librería de formación “on-line” que tiene cursos realmente interesantes para desarrolladores .NET, echad un vistazo y veréis que están muy bien!

Espero que sea de ayuda! Xampi