lunes, 24 de octubre de 2011

Log con campos CLOB en Log4j

Recientemente he estado probando diferentes configuraciones del log4j, una de las cosas interesantes era guardar el log en base de datos. usando log4j no representa ningún problema, ya que podemos hacer uso del appender  JDBCAppender para inserciones sencillas.

El problema surge cuando queremos almacenar la traza de un error, y su tamaño por lo general tiene un tamaño considerable (muy superior a los 4000 caracteres que te permite un varchar2 de Oracle, por ejemplo). La solución obvia (en Oracle) es usar un CLOB (Character Large Object), pero actualmente Log4j no permite el uso de CLOB.

Buscando un poco por la red, encontremos algunas soluciones alternativas como la de Danko Mannhaupt, muy recomendada, la cuál permite el uso de CLOB si usamos "prepared statements". En mi caso, no planteamos usar esta solución, ya que implicaba ciertos cambios en el proyecto así como añadir librerías de terceros, etc. Además la estructura de la tabla en BBDD planteada no se ajustaba a lo que requeríamos.

En tal caso, optamos por hacerlo nosotros mismos. Tan solo es necesario heredar de la clase  JDBCAppender (la de Log4j),  y sobreescribir (override) el método execute, el cuál tiene como parámetro de entrada la SQL ya formada, por lo que sólo deberemos tratar la ristra y crear el PreparedStatement.

Veamos un ejemplo:

package com.fraguaDigital.log;

import java.io.StringReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import org.apache.log4j.jdbc.JDBCAppender;

public class CLobJDBCAppender extends JDBCAppender {

 @Override
 protected void execute(String sql) throws SQLException {
  Connection con = null;
  Statement stmt = null;
  try {
   con = getConnection();

   if (sql.contains("#")){
    int initPos = sql.indexOf("#");
    int lastPos = sql.lastIndexOf("#");
    String clobData = sql.substring(initPos+1, lastPos);
    StringReader stringReader = new StringReader(clobData);

    sql = sql.substring(0,initPos) +"?"+ sql.substring(lastPos+1);
    stmt = con.prepareStatement(sql);
    ((PreparedStatement)stmt).setCharacterStream(1, stringReader, clobData.length());          
    ((PreparedStatement)stmt).execute();
   }else{
    stmt = con.createStatement();
    stmt.executeUpdate(sql);         
   }
  } catch (SQLException e) {
   if (stmt != null)
    stmt.close();
   throw e;
  }

  stmt.close();
  closeConnection(con);
 }
}

En este caso usamos el carácter "#" para marcar el comienzo y el fin del campo que es CLOB. Esto es sólo un ejemplo, pero puede generalizarse fácilmente.

Por último, tendríamos que añadir  en la configuración del log4j que use nuestro appender y definir en la cadena sql el campo CLOB usando las "#".

<appender name="LOG_BBDD" class="com.fraguaDigital.log.CLobJDBCAppender">
 <param name="Threshold" value="WARN"/>
 <param name="driver" value="${jdbc.driverClassName}" />
 <param name="URL" value="${jdbc.url}" />
 <param name="user" value="${jdbc.username}" />
 <param name="password" value="${jdbc.password}" />
 <layout class="org.apache.log4j.PatternLayout">
  <param name="ConversionPattern"
   value="INSERT INTO tabla (FECHA, PRIORIDAD, MENSAJE, CLASE, STACKTRACE, USUARIO) VALUES ('%d', '%p', '%m%n', '%C.%M(%L)', #%throwable#)" />
 </layout> 
</appender>