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.
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:
Cada vez que llega un mensaje a la cola de mensajes se ejecuta el método onMessage() del consumidor de la cola de mensajes:
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:
Con todo lo anterior, el funcionamiento básico del sistema es el siguiente:
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:
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).Logger logger = Logger.getLogger("jmsLogger");
logger.info("My first log messages!");
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<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>
Cada vez que llega un mensaje a la cola de mensajes se ejecuta el método onMessage() del consumidor de la cola de mensajes:
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./**
* 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);
}}
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:
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:<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>
Como veis nuestro Handler se encarga, simplemente, de obtener conexiones a la base de datos mediante un pool./**
* 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();}}
Con todo lo anterior, el funcionamiento básico del sistema es el siguiente:
- El cliente manda el mensaje de log a la categoría JMS de log4j.
- La configuración de log4j del cliente envía este mensaje a la cola JMS del servidor de aplicaciones que tenga configurada.
- 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.
- 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.
No hay comentarios:
Publicar un comentario