HIBERNATE JBoss.org
 |  Register  | 
     
News 
About 
   Feature List 
   Road Map 
Documentation 
   Related Projects 
   External Documentation 
Download 
Forum & Mailinglists 
Support & Training 
JIRA Issue Tracking
Wiki Community Area


Hibernate Public Training Courses


Get Hibernate in Action eBook!


JavaWorld 2003 Finalist


Jolt Award 2004 Winner
      
Documentation > Community Area > Hibernate Log4j Appender

Hibernate Log4j Appender

(12/06/2003 - Updated for Hibernate 2)

This section provides code for a Hibernate appender for use with the log4j logging framework.

The log4j framework already contains a JDBC appender (org.apache.log4j.jdbc.JDBCAppender), that can be used to insert log output into a database table. If you are already using Hibernate, this has several drawbacks.

The first is that you need to configure a separate database connection, or extend the JDBCAppender class to obtain its connection from Hibernate.

The second is that the JDBC appender provides no natural primary key that you can easily use with Hibernate to retrieve the records for use within your application (for example, in a log viewer page). If you are like me, you will have written some additional framework around Hibernate that probably assumes every record has a primary key.

The Hibernate log4j appender allows you to persist log4j logging events by exposing the details of these log events through the HibernateAppenderLoggingEvent interface. To use the log appender, you must provide your own implementation of this interface. The interface basically specifies getters and setters for the information available in a log4j logging event. This has been specified as an interface rather than an abstract class so that your implementation is free to extend your own ancestor.

You must also provide a class that implements the HibernateSessionService which defines the following method:

public Session openSession() throws HibernateException;

This will be how the appender obtains an open Hibernate session. This allows you to perform any other actions that you require when opening the session, such as registering audit interceptors.

The basic configuration required in the log4j.properties file will be similar to the following:

### direct messages to Hibernate
log4j.appender.hibernate=HibernateAppender
log4j.appender.hibernate.sessionServiceClass=HibernateHelper
log4j.appender.hibernate.loggingEventClass=LogEvent

You need to replace the HibernateHelper with your own class that implements the HibernateSessionService and the LogEvent with your own class that implements the HibernateAppenderLoggingEvent interface.

You can then map the loggingEventClass class in a Hibernate mapping file, as you would with your applications own persistent objects. My mapping file is shown below, but you are free to persist as much or as little of the log event that you require:

<hibernate-mapping>
  <class name="LogEvent" 
         table="LOGGER" 
         proxy="LogEvent">
    <id name="id" column="log_id">
      <generator class="hilo"/>
    </id>
    <list name="loggingEventThrowableWrapper" table="LOGGER_THROWABLE" lazy="true">
      <key column="log_id"/>
      <index column="i"/>
      <composite-element class="LogEventThrowable">
          <property name="message" column="message" length="255" not-null="true"/>
      </composite-element>
    </list>
    <property name="level"
              column="level"
              length="7"
              not-null="true"/>
    <property name="message"
              column="message"
              length="255"
              not-null="true"/>
    <property name="className"
              column="class_name"
              length="255"
              not-null="true"/>
    <property name="fileName"
              column="file_name"
              length="255"
              not-null="true"/>
    <property name="lineNumber"
              column="line_number"
              length="5"
              not-null="true"/>
    <property name="methodName"
              column="method_name"
              length="255"
              not-null="true"/>
    <property name="loggerName"
              column="logger_name"
              length="255"
              not-null="true"/>
    <property name="startDate"
              column="start_date"
              not-null="true"/>
    <property name="logDate"
              column="log_date"
              not-null="true"/>
    <property name="threadName"
              column="thread_name"
              length="255"
              not-null="true"/>
  </class>
</hibernate-mapping>

You will notice from the above mapping file that if the log event includes a throwable exception, the call stack is available for you to persist. The HibernateAppenderLoggingEvent interface provides the following method:

public void addThrowableMessage(int position, String throwableMessage);

You can implement this in your implementing class whichever way you choose. In the above mapping file, I have implemented the add using an ArrayList that contains LogEventThrowable objects:

private List loggingEventThrowableWrapper = new ArrayList();
public void addThrowableMessage(int position, String throwableMessage) {
    LogEventThrowable letw = new LogEventThrowable();
    letw.setPosition(new Integer(position));
    letw.setMessage(throwableMessage);
    loggingEventThrowableWrapper.add(letw);        
}

The code for the three classes required for the appender is provided below. The main complexity in this code is dealing with the problem that Hibernate itself can generate log messages. The code needs to prevent the recursive situation where logging an event sent to the Hibernate appender generates a log message which is set to be logged by the Hibernate appender (which generates a log message etc.) I didn't want to just ignore any log events from Hibernate as there may be warnings and errors as a result of starting Hibernate, or any of the additional services that Hibernate uses such as JCS. Naturally, if Hibernate fails to start in a usable state, the logger cannot use Hibernate to persist log events to the database.


HibernateAppenderSessionService class

import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Session;

/**
 * Interface for providing an open Hibernate session to the 
 * {@link HibernateAppender}.
 * 
 * @author David Howe
 * 
 * @version 1.1
 *
 */
public interface HibernateAppenderSessionService {
    /**
     * <P>Returns a reference to a Hibernate session instance.</P>
     * 
     * <P>This interface gives applications the ability to open a session
     * using their existing infrastructure, which may include registering
     * audit interceptors if required.</P>
     * 
     * @return An open Hibernate session
     * @throws HibernateException
     */
    public Session openSession() throws HibernateException;
}

HibernateAppenderLoggingEvent class

import java.util.Date;

/**
 * Interface for logging events that are to be persisted using the 
 * {@link HibernateAppender}.
 * 
 * @author David Howe
 * 
 * @version 1.0
 *
 */
public interface HibernateAppenderLoggingEvent {
    /**
     * Returns the log message.
     * 
     * @return The log message
     */
    public String getMessage();
    /**
     * Returns the name of the class.
     * 
     * @return The class name
     */
    public String getClassName();
    /**
     * Returns the source file name of the class.
     * 
     * @return The source file name
     */
    public String getFileName();
    /**
     * Returns the line number in the class that initiated the log event.
     * 
     * @return The line number
     */
    public String getLineNumber();
    /**
     * Returns the date and time of the log event.
     * 
     * @return The date and time of the log event
     */
    public Date getLogDate();
    /**
     * Returns the name of the logger.
     * 
     * @return The name of the logger
     */
    public String getLoggerName();
    /**
     * Returns the name of the method in the class that initiated the log event.
     * 
     * @return The name of the method
     */
    public String getMethodName();
    /**
     * Returns the date and time that the application started.
     * 
     * @return The date and time that the application started
     */
    public Date getStartDate();
    /**
     * Returns the name of the thread.
     * 
     * @return The name of the thread
     */
    public String getThreadName();
    /**
     * Returns the logging level of this log event.
     * 
     * @return The log level of this logging event
     */
    public String getLevel();
    /**
     * Sets the log message.
     * 
     * @param The log message
     */
    public void setMessage(String message);
    /**
     * Sets the name of the class.
     * 
     * @param string
     */
    public void setClassName(String className);
    /**
     * Sets source file name for the class.
     * 
     * @param The file name
     */
    public void setFileName(String fileName);
    /**
     * Sets the line number in the class that initiated the log event.
     * @param The line number
     */
    public void setLineNumber(String lineNumber);
    /**
     * Sets the date and time of the log event.
     * 
     * @param The date and time of the log event
     */
    public void setLogDate(Date logDate);
    /**
     * Sets the logger name.
     * 
     * @param The logger name
     */
    public void setLoggerName(String loggerName);
    /**
     * Sets the name of the method that initiated the log event.
     * 
     * @param The method name
     */
    public void setMethodName(String methodName);
    /**
     * Sets the date and time that the application started.
     * 
     * @param The date and time that the application started
     */
    public void setStartDate(Date startDate);
    /**
     * Sets the name of the thread.
     * 
     * @param The thread name
     */
    public void setThreadName(String threadName);
    /**
     * Sets the log level.
     * 
     * @param The log level of this logging event
     */
    public void setLevel(String level);
    /**
     * Adds a throwable exception message to the log event.
     * 
     * @param The position in the call stack
     * @param The throwable message
     */
    public void addThrowableMessage(int position, String throwableMessage);
}

HibernateAppender class

import java.util.Date;
import java.util.Iterator;
import java.util.Vector;

import org.apache.log4j.Appender;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
import org.apache.log4j.spi.ErrorCode;
import org.apache.log4j.spi.LoggingEvent;

import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Session;

/**
 * Log4J appender that uses Hibernate to store log events in a database.
 * To use this appender, you must provide two properties in the Log4J 
 * properties file:
 * 
 * <UL>sessionServiceClass</UL>
 * <UL>loggingEventClass</UL>
 * 
 * <P>The sessionServiceClass must implement the 
 * {@link HibernateAppenderSessionService} interface which provides the appender
 * with an open Hibernate session.  Your implementation of this interface
 * can perform any additional activities such as registering audit 
 * interceptors if required.</P>
 * 
 * <P>The loggingEventClass must implement the
 * {@link HibernateAppenderLoggingEvent} interface.  Using an interface for the
 * logging event leaves your implementation free to extend any
 * existing persistence ancestor that you may already be using.</P>
 * 
 * <P>An example Log4J properties file to configure the HibernateAppender
 * would be:</P>
 * 
 * <code>
 * ### direct messages to Hibernate<BR/>
 * log4j.appender.hibernate=HibernateAppender<BR/>
 * log4j.appender.hibernate.sessionServiceClass=HibernateHelper<BR/>
 * log4j.appender.hibernate.loggingEventClass=LogEvent<BR/>
 * </code>
 *
 * <P>You can now write a Hibernate mapping file for the class specified as
 * the <code>loggingEventClass</code> to persist whichever parts of the
 * logging event that you require.
 * </P>
 * 
 * @author David Howe
 * 
 * @version 1.1
 *
 */
public class HibernateAppender extends AppenderSkeleton implements Appender {

    private String sessionServiceClass;
    private String loggingEventClass;
    private HibernateAppenderSessionService hibernateSessionServiceImplementation;
    private Class loggingEventWrapperImplementationClass;
    private static Vector buffer = new Vector();
    private static Boolean appending = Boolean.FALSE;

    /* (non-Javadoc)
     * @see org.apache.log4j.AppenderSkeleton#append(org.apache.log4j.spi.LoggingEvent)
     */
    protected void append(LoggingEvent loggingEvent) {

        /* Ensure exclusive access to the buffer in case another thread is
         * currently writing the buffer.
         */
        synchronized (buffer) {
            // Add the current event into the buffer
            buffer.add(loggingEvent);
            /* Ensure exclusive access to the appending flag to guarantee that
             * it doesn't change in between checking it's value and setting it
             */
            synchronized (appending) {
                if (!appending.booleanValue()) {
                    /* No other thread is appending to the log, so this 
                     * thread can perform the append
                     */
                    appending = Boolean.TRUE;
                } else {
                    /* Another thread is already appending to the log and it
                     * will take care of emptying the buffer
                     */
                    return;
                }
            }
        }

        try {
            Session session =
                hibernateSessionServiceImplementation.openSession();

            /* Ensure exclusive access to the buffer in case another thread is
             * currently adding to the buffer.
             */
            synchronized (buffer) {
                Iterator iter = buffer.iterator();
                LoggingEvent bufferLoggingEvent;
                HibernateAppenderLoggingEvent loggingEventWrapper;

                /* Get the current buffer length.  We only want to process
                 * events that are currently in the buffer.  If events get
                 * added to the buffer after this point, they must have
                 * been caused by this loop, as we have synchronized on the
                 * buffer, so no other thread could be adding an event.  Any
                 * events that get added to the buffer as a result of this
                 * loop will be discarded, as to attempt to process them will
                 * result in an infinite loop.
                 */

                int bufferLength = buffer.size();

                for (int i = 0; i < bufferLength; i++) {
                    bufferLoggingEvent = (LoggingEvent) buffer.get(i);

                    try {
                        loggingEventWrapper =
                            (HibernateAppenderLoggingEvent) loggingEventWrapperImplementationClass
                                .newInstance();
                    } catch (IllegalAccessException iae) {
                        this.errorHandler.error(
                            "Unable to instantiate class "
                                + loggingEventWrapperImplementationClass
                                    .getName(),
                            iae,
                            ErrorCode.GENERIC_FAILURE);
                        return;
                    } catch (InstantiationException ie) {
                        this.errorHandler.error(
                            "Unable to instantiate class "
                                + loggingEventWrapperImplementationClass
                                    .getName(),
                            ie,
                            ErrorCode.GENERIC_FAILURE);
                        return;
                    }

                    loggingEventWrapper.setMessage(
                        bufferLoggingEvent.getRenderedMessage());
                    loggingEventWrapper.setClassName(
                        bufferLoggingEvent
                            .getLocationInformation()
                            .getClassName());
                    loggingEventWrapper.setFileName(
                        bufferLoggingEvent
                            .getLocationInformation()
                            .getFileName());
                    loggingEventWrapper.setLineNumber(
                        bufferLoggingEvent
                            .getLocationInformation()
                            .getLineNumber());
                    Date logDate = new Date();
                    logDate.setTime(bufferLoggingEvent.timeStamp);

                    loggingEventWrapper.setLogDate(logDate);

                    loggingEventWrapper.setLoggerName(
                        bufferLoggingEvent.getLoggerName());
                    loggingEventWrapper.setMethodName(
                        bufferLoggingEvent
                            .getLocationInformation()
                            .getMethodName());

                    Date startDate = new Date();
                    startDate.setTime(LoggingEvent.getStartTime());

                    loggingEventWrapper.setStartDate(startDate);
                    loggingEventWrapper.setThreadName(
                        bufferLoggingEvent.getThreadName());

                    if (bufferLoggingEvent.getThrowableStrRep() != null) {
                        for (int j = 0;
                            j < bufferLoggingEvent.getThrowableStrRep().length;
                            j++) {
                            loggingEventWrapper.addThrowableMessage(
                                j,
                                bufferLoggingEvent.getThrowableStrRep()[j]);
                        }
                    }

                    if (bufferLoggingEvent.equals(Level.ALL)) {
                        loggingEventWrapper.setLevel("ALL");
                    } else if (
                        bufferLoggingEvent.getLevel().equals(Level.DEBUG)) {
                        loggingEventWrapper.setLevel("DEBUG");
                    } else if (
                        bufferLoggingEvent.getLevel().equals(Level.ERROR)) {
                        loggingEventWrapper.setLevel("ERROR");
                    } else if (
                        bufferLoggingEvent.getLevel().equals(Level.FATAL)) {
                        loggingEventWrapper.setLevel("FATAL");
                    } else if (
                        bufferLoggingEvent.getLevel().equals(Level.INFO)) {
                        loggingEventWrapper.setLevel("INFO");
                    } else if (
                        bufferLoggingEvent.getLevel().equals(Level.OFF)) {
                        loggingEventWrapper.setLevel("OFF");
                    } else if (
                        bufferLoggingEvent.getLevel().equals(Level.WARN)) {
                        loggingEventWrapper.setLevel("WARN");
                    } else {
                        loggingEventWrapper.setLevel("UNKNOWN");
                    }
                    session.save(loggingEventWrapper);
                }
                session.flush();
                session.close();
                buffer.clear();

                /* Ensure exclusive access to the appending flag - this really
                 * shouldn't be needed as the only other check on this flag is
                 * also synchronized on the buffer.  We don't want to do this
                 * in the finally block as between here and the finally block
                 * we will not be synchronized on the buffer and another 
                 * process could add an event to the buffer, but the appending
                 * flag will still be true, so that event would not get
                 * written until another log event triggers the buffer to
                 * be emptied.
                 */
                synchronized (appending) {
                    appending = Boolean.FALSE;
                }
            }
        } catch (HibernateException he) {
            this.errorHandler.error(
                "HibernateException",
                he,
                ErrorCode.GENERIC_FAILURE);
            // Reset the appending flag
            appending = Boolean.FALSE;
            return;
        }
    }

    /* (non-Javadoc)
     * @see org.apache.log4j.Appender#close()
     */
    public void close() {
    }

    /* (non-Javadoc)
     * @see org.apache.log4j.Appender#requiresLayout()
     */
    public boolean requiresLayout() {
        return false;
    }

    /**
     * Returns the name of class implementing the
     * {@link HibernateAppenderSessionService} interface.
     * 
     * @return Fully qualified class name 
     */
    public String getSessionServiceClass() {
        return sessionServiceClass;
    }

    /**
     * Sets the name of the class implementing the
     * {@link HibernateAppenderSessionService} interface.
     * 
     * @param Fully qualified class name
     */
    public void setSessionServiceClass(String string) {
        this.sessionServiceClass = string;
        try {
            Class c = Class.forName(string);
            try {
                hibernateSessionServiceImplementation =
                    (HibernateAppenderSessionService) c.newInstance();
            } catch (InstantiationException ie) {
                this.errorHandler.error(
                    "Unable to instantiate class " + c.getName(),
                    ie,
                    ErrorCode.GENERIC_FAILURE);
            } catch (IllegalAccessException iae) {
                this.errorHandler.error(
                    "Unable to instantiate class " + c.getName(),
                    iae,
                    ErrorCode.GENERIC_FAILURE);
            }
        } catch (ClassNotFoundException cnfe) {
            this.errorHandler.error(
                "Invalid HibernateAppenderSessionService class " + string,
                cnfe,
                ErrorCode.GENERIC_FAILURE);
        }
    }

    /**
     * Returns the name of the class implementing the 
     * {@link HibernateAppenderLoggingEvent} interface.
     * 
     * @return Fully qualified class name 
     */
    public String getLoggingEventClass() {
        return loggingEventClass;
    }

    /**
     * Sets the name of class implementing the 
     * {@link HibernateAppenderLoggingEvent} interface.
     * 
     * @param Fully qualified class name 
     */
    public void setLoggingEventClass(String string) {
        loggingEventClass = string;
        try {
            loggingEventWrapperImplementationClass =
                Class.forName(loggingEventClass);
        } catch (ClassNotFoundException cnfe) {
            this.errorHandler.error(
                "Invalid LoggingEvent class " + string,
                cnfe,
                ErrorCode.GENERIC_FAILURE);
        }
    }
}
      

coWiki