You are viewing a plain text version of this content. The canonical link for it is here.
Posted to log4j-dev@logging.apache.org by Kevin Steppe <ks...@pacbell.net> on 2002/03/25 20:31:45 UTC

JDBCAppender

Ceki,
    I finally tested and debugged the new JDBCAppender.
This should now be ready for inclusion.   I've attached both
the source file and an example config file.  Code follows:
-------------------------------------------------------------------------------------



package org.apache.log4j;

import org.apache.log4j.*;
import org.apache.log4j.spi.*;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.helpers.OptionConverter;

import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.SQLException;


/**
 * The JDBCAppender provides for sendinhg log messages to a
database.
 *
 * Each append call adds to an ArrayList buffer.  When the
buffer is filled
 * each log event is placed in a sql statement
(configurable) and executed.
 *
 * BufferSize, db URL, User, & Password are configurable
options in
 * the standard Log4J ways.
 *
 * The setSql(String sql) sets the SQL statement to be used
for logging --
 * this statement is sent to a PatternLayout (either created
automaticly
 * by the appender or added by the user).  Therefore by
default all the
 * conversion patterns in PatternLayout can be used inside
of the statement.
 * (see the test cases for examples)
 *
 * Overriding the getLogStatement method allows more
explicit control of the
 * statement used for logging.
 *
 * For use as a base class:
 *
 *    Override getConnection() to pass any connection you
want.
 *       Typically this is used to enable application wide
connection pooling.
 *    Override closeConnection(Connection con) -- if you
override getConnection
 *       make sure to implement closeConnection to handle
the connection you
 *       generated.  Typically this would return the
connection to the pool it
 *       came from.
 *
 *    Override getLogStatement(LoggingEvent event) to
produce specialized or
 *       dynamic statements The default uses the sql option
value
 *
 * @author: Kevin Steppe (<A
HREF="mailto:ksteppe@pacbell.net">ksteppe@pacbell.net</A>)
*/


public class JDBCAppender extends
org.apache.log4j.AppenderSkeleton
    implements org.apache.log4j.Appender {

  /**
   * URL of the DB for default connection handling
   */
  protected String databaseURL = "jdbc:odbc:myDB";

  /**
   * User to connect as for default connection handling
   */
  protected String databaseUser = "me";

  /**
   * User to use for default connection handling
   */
  protected String databasePassword = "mypassword";

  /**
   * Connection used by default.  The connection is opened
the first time it
   * is needed and then held open until the appender is
closed (usually at
   * garbage collection).  This behavior is best modified by
creating a
   * sub-class and overriding the <code>getConnection</code>
and
   * <code>closeConnection</code> methods.
   */
  protected Connection connection = null;

  /**
   * Stores the string given to the pattern layout for
conversion into a SQL
   * statement, eg: insert into LogTable (Thread, Class,
Message) values
   * ("%t", "%c", "%m")
   *
   * Be careful of quotes in your messages!
   *
   * Also see PatternLayout.
   */
  protected String sqlStatement = "";

  /**
   * size of LoggingEvent buffer before writting to the
database.
   * Default is 1.
   */
  protected int bufferSize = 1;

  /**
   * ArrayList holding the buffer of Logging Events.
   */
  protected ArrayList buffer;

  /**
   * Helper object for clearing out the buffer
   */
  protected ArrayList removes;

  public JDBCAppender() {
    super();
    buffer = new ArrayList(bufferSize);
    removes = new ArrayList(bufferSize);
  }

  /**
   * Adds the event to the buffer.  When full the buffer is
flushed.
   */
  public void append(LoggingEvent event) {
    buffer.add(event);

    if (buffer.size() >= bufferSize)
      flushBuffer();
  }

  /**
   * By default getLogStatement sends the event to the
required Layout object.
   * The layout will format the given pattern into a
workable SQL string.
   *
   * Overriding this provides direct access to the
LoggingEvent
   * when constructing the logging statement.
   *
   */
  protected String getLogStatement(LoggingEvent event) {
    return getLayout().format(event);
  }

  /**
   *
   * Override this to provide an alertnate method of getting

   * connections (such as caching).  One method to fix this
is to open
   * connections at the start of flushBuffer() and close
them at the
   * end.  I use a connection pool outside of JDBCAppender
which is
   * accessed in an override of this method.
   * */
  protected void execute(String sql) throws SQLException {

    Connection con = null;
    Statement stmt = null;

    try {
        con = getConnection();

        stmt = con.createStatement();
        stmt.executeUpdate(sql);
    } catch (SQLException e) {
       if (stmt != null)
      stmt.close();
       throw e;
    }
    stmt.close();
    closeConnection(con);

    System.out.println("Execute: " + sql);
  }


  /**
   * Override this to return the connection to a pool, or to
clean up the
   * resource.
   *
   * The default behavior holds a single connection open
until the appender
   * is closed (typically when garbage collected).
   */
  protected void closeConnection(Connection con) {
  }

  /**
   * Override this to link with your connection pooling
system.
   *
   * By default this creates a single connection which is
held open
   * until the object is garbage collected.
   */
  protected Connection getConnection() throws SQLException {

      if (!DriverManager.getDrivers().hasMoreElements())
      setDriver("sun.jdbc.odbc.JdbcOdbcDriver");

      if (connection == null) {
        connection =
DriverManager.getConnection(databaseURL, databaseUser,
     databasePassword);
      }

      return connection;
  }

  /**
   * Closes the appender, flushing the buffer first then
closing the default
   * connection if it is open.
   */
  public void close()
  {
    flushBuffer();

    try {
      if (connection != null && !connection.isClosed())
          connection.close();
    } catch (SQLException e) {
        errorHandler.error("Error closing connection", e,
ErrorCode.GENERIC_FAILURE);
    }
    this.closed = true;
  }

  /**
   * loops through the buffer of LoggingEvents, gets a
   * sql string from getLogStatement() and sends it to
execute().
   * Errors are sent to the errorHandler.
   *
   * If a statement fails the LoggingEvent stays in the
buffer!
   */
  public void flushBuffer() {
    //Do the actual logging
    removes.ensureCapacity(buffer.size());
    for (Iterator i = buffer.iterator(); i.hasNext();) {
      try {
        LoggingEvent logEvent = (LoggingEvent)i.next();
     String sql = getLogStatement(logEvent);
     execute(sql);
        removes.add(logEvent);
      }
      catch (SQLException e) {
     errorHandler.error("Failed to excute sql", e,
      ErrorCode.FLUSH_FAILURE);
      }
    }
    buffer.removeAll(removes);
    //buffer.clear();
  }


  /** closes the appender before disposal */
  public void finalize() {
    close();
  }


  /**
   * JDBCAppender builds a layout internally if one is not
provided.
   */
  public boolean requiresLayout() {
    return false;
  }


  /**
   *
   */
  public void setSql(String s) {
    sqlStatement = s;
    if (getLayout() == null) {
        this.setLayout(new PatternLayout(s));
    }
    else {

((PatternLayout)getLayout()).setConversionPattern(s);
    }
  }


  /**
   * Returns pre-formated statement eg: insert into LogTable
(msg) values ("%m")
   */
  public String getSql() {
    return sqlStatement;
  }


  public void setUser(String user) {
    databaseUser = user;
  }


  public void setURL(String url) {
    databaseURL = url;
  }


  public void setPassword(String password) {
    databasePassword = password;
  }


  public void setBufferSize(int newBufferSize) {
    bufferSize = newBufferSize;
    buffer.ensureCapacity(bufferSize);
    removes.ensureCapacity(bufferSize);
  }


  public String getUser() {
    return databaseUser;
  }


  public String getURL() {
    return databaseURL;
  }


  public String getPassword() {
    return databasePassword;
  }


  public int getBufferSize() {
    return bufferSize;
  }


  /**
   * Ensures that the given driver class has been loaded for
sql connection
   * creation.
   */
  public void setDriver(String driverClass) {
    try {
      Class.forName(driverClass);
    } catch (Exception e) {
      errorHandler.error("Failed to load driver", e,
    ErrorCode.GENERIC_FAILURE);
    }
  }

}



Re: JDBCAppender

Posted by Ceki Gülcü <ce...@qos.ch>.
Thanks Kevin. I just committed under the jdbc package.

At 11:31 25.03.2002 -0800, you wrote:
>Ceki,
>     I finally tested and debugged the new JDBCAppender.
>This should now be ready for inclusion.   I've attached both
>the source file and an example config file.  Code follows:
>-------------------------------------------------------------------------------------
>
>
>
>package org.apache.log4j;
>
>import org.apache.log4j.*;
>import org.apache.log4j.spi.*;
>import org.apache.log4j.PatternLayout;
>import org.apache.log4j.helpers.OptionConverter;
>
>import java.util.List;
>import java.util.ArrayList;
>import java.util.Iterator;
>
>import java.sql.DriverManager;
>import java.sql.Connection;
>import java.sql.Statement;
>import java.sql.SQLException;
>
>
>/**
>  * The JDBCAppender provides for sendinhg log messages to a
>database.
>  *
>  * Each append call adds to an ArrayList buffer.  When the
>buffer is filled
>  * each log event is placed in a sql statement
>(configurable) and executed.
>  *
>  * BufferSize, db URL, User, & Password are configurable
>options in
>  * the standard Log4J ways.
>  *
>  * The setSql(String sql) sets the SQL statement to be used
>for logging --
>  * this statement is sent to a PatternLayout (either created
>automaticly
>  * by the appender or added by the user).  Therefore by
>default all the
>  * conversion patterns in PatternLayout can be used inside
>of the statement.
>  * (see the test cases for examples)
>  *
>  * Overriding the getLogStatement method allows more
>explicit control of the
>  * statement used for logging.
>  *
>  * For use as a base class:
>  *
>  *    Override getConnection() to pass any connection you
>want.
>  *       Typically this is used to enable application wide
>connection pooling.
>  *    Override closeConnection(Connection con) -- if you
>override getConnection
>  *       make sure to implement closeConnection to handle
>the connection you
>  *       generated.  Typically this would return the
>connection to the pool it
>  *       came from.
>  *
>  *    Override getLogStatement(LoggingEvent event) to
>produce specialized or
>  *       dynamic statements The default uses the sql option
>value
>  *
>  * @author: Kevin Steppe (<A
>HREF="mailto:ksteppe@pacbell.net">ksteppe@pacbell.net</A>)
>*/
>
>
>public class JDBCAppender extends
>org.apache.log4j.AppenderSkeleton
>     implements org.apache.log4j.Appender {
>
>   /**
>    * URL of the DB for default connection handling
>    */
>   protected String databaseURL = "jdbc:odbc:myDB";
>
>   /**
>    * User to connect as for default connection handling
>    */
>   protected String databaseUser = "me";
>
>   /**
>    * User to use for default connection handling
>    */
>   protected String databasePassword = "mypassword";
>
>   /**
>    * Connection used by default.  The connection is opened
>the first time it
>    * is needed and then held open until the appender is
>closed (usually at
>    * garbage collection).  This behavior is best modified by
>creating a
>    * sub-class and overriding the <code>getConnection</code>
>and
>    * <code>closeConnection</code> methods.
>    */
>   protected Connection connection = null;
>
>   /**
>    * Stores the string given to the pattern layout for
>conversion into a SQL
>    * statement, eg: insert into LogTable (Thread, Class,
>Message) values
>    * ("%t", "%c", "%m")
>    *
>    * Be careful of quotes in your messages!
>    *
>    * Also see PatternLayout.
>    */
>   protected String sqlStatement = "";
>
>   /**
>    * size of LoggingEvent buffer before writting to the
>database.
>    * Default is 1.
>    */
>   protected int bufferSize = 1;
>
>   /**
>    * ArrayList holding the buffer of Logging Events.
>    */
>   protected ArrayList buffer;
>
>   /**
>    * Helper object for clearing out the buffer
>    */
>   protected ArrayList removes;
>
>   public JDBCAppender() {
>     super();
>     buffer = new ArrayList(bufferSize);
>     removes = new ArrayList(bufferSize);
>   }
>
>   /**
>    * Adds the event to the buffer.  When full the buffer is
>flushed.
>    */
>   public void append(LoggingEvent event) {
>     buffer.add(event);
>
>     if (buffer.size() >= bufferSize)
>       flushBuffer();
>   }
>
>   /**
>    * By default getLogStatement sends the event to the
>required Layout object.
>    * The layout will format the given pattern into a
>workable SQL string.
>    *
>    * Overriding this provides direct access to the
>LoggingEvent
>    * when constructing the logging statement.
>    *
>    */
>   protected String getLogStatement(LoggingEvent event) {
>     return getLayout().format(event);
>   }
>
>   /**
>    *
>    * Override this to provide an alertnate method of getting
>
>    * connections (such as caching).  One method to fix this
>is to open
>    * connections at the start of flushBuffer() and close
>them at the
>    * end.  I use a connection pool outside of JDBCAppender
>which is
>    * accessed in an override of this method.
>    * */
>   protected void execute(String sql) throws SQLException {
>
>     Connection con = null;
>     Statement stmt = null;
>
>     try {
>         con = getConnection();
>
>         stmt = con.createStatement();
>         stmt.executeUpdate(sql);
>     } catch (SQLException e) {
>        if (stmt != null)
>       stmt.close();
>        throw e;
>     }
>     stmt.close();
>     closeConnection(con);
>
>     System.out.println("Execute: " + sql);
>   }
>
>
>   /**
>    * Override this to return the connection to a pool, or to
>clean up the
>    * resource.
>    *
>    * The default behavior holds a single connection open
>until the appender
>    * is closed (typically when garbage collected).
>    */
>   protected void closeConnection(Connection con) {
>   }
>
>   /**
>    * Override this to link with your connection pooling
>system.
>    *
>    * By default this creates a single connection which is
>held open
>    * until the object is garbage collected.
>    */
>   protected Connection getConnection() throws SQLException {
>
>       if (!DriverManager.getDrivers().hasMoreElements())
>       setDriver("sun.jdbc.odbc.JdbcOdbcDriver");
>
>       if (connection == null) {
>         connection =
>DriverManager.getConnection(databaseURL, databaseUser,
>      databasePassword);
>       }
>
>       return connection;
>   }
>
>   /**
>    * Closes the appender, flushing the buffer first then
>closing the default
>    * connection if it is open.
>    */
>   public void close()
>   {
>     flushBuffer();
>
>     try {
>       if (connection != null && !connection.isClosed())
>           connection.close();
>     } catch (SQLException e) {
>         errorHandler.error("Error closing connection", e,
>ErrorCode.GENERIC_FAILURE);
>     }
>     this.closed = true;
>   }
>
>   /**
>    * loops through the buffer of LoggingEvents, gets a
>    * sql string from getLogStatement() and sends it to
>execute().
>    * Errors are sent to the errorHandler.
>    *
>    * If a statement fails the LoggingEvent stays in the
>buffer!
>    */
>   public void flushBuffer() {
>     //Do the actual logging
>     removes.ensureCapacity(buffer.size());
>     for (Iterator i = buffer.iterator(); i.hasNext();) {
>       try {
>         LoggingEvent logEvent = (LoggingEvent)i.next();
>      String sql = getLogStatement(logEvent);
>      execute(sql);
>         removes.add(logEvent);
>       }
>       catch (SQLException e) {
>      errorHandler.error("Failed to excute sql", e,
>       ErrorCode.FLUSH_FAILURE);
>       }
>     }
>     buffer.removeAll(removes);
>     //buffer.clear();
>   }
>
>
>   /** closes the appender before disposal */
>   public void finalize() {
>     close();
>   }
>
>
>   /**
>    * JDBCAppender builds a layout internally if one is not
>provided.
>    */
>   public boolean requiresLayout() {
>     return false;
>   }
>
>
>   /**
>    *
>    */
>   public void setSql(String s) {
>     sqlStatement = s;
>     if (getLayout() == null) {
>         this.setLayout(new PatternLayout(s));
>     }
>     else {
>
>((PatternLayout)getLayout()).setConversionPattern(s);
>     }
>   }
>
>
>   /**
>    * Returns pre-formated statement eg: insert into LogTable
>(msg) values ("%m")
>    */
>   public String getSql() {
>     return sqlStatement;
>   }
>
>
>   public void setUser(String user) {
>     databaseUser = user;
>   }
>
>
>   public void setURL(String url) {
>     databaseURL = url;
>   }
>
>
>   public void setPassword(String password) {
>     databasePassword = password;
>   }
>
>
>   public void setBufferSize(int newBufferSize) {
>     bufferSize = newBufferSize;
>     buffer.ensureCapacity(bufferSize);
>     removes.ensureCapacity(bufferSize);
>   }
>
>
>   public String getUser() {
>     return databaseUser;
>   }
>
>
>   public String getURL() {
>     return databaseURL;
>   }
>
>
>   public String getPassword() {
>     return databasePassword;
>   }
>
>
>   public int getBufferSize() {
>     return bufferSize;
>   }
>
>
>   /**
>    * Ensures that the given driver class has been loaded for
>sql connection
>    * creation.
>    */
>   public void setDriver(String driverClass) {
>     try {
>       Class.forName(driverClass);
>     } catch (Exception e) {
>       errorHandler.error("Failed to load driver", e,
>     ErrorCode.GENERIC_FAILURE);
>     }
>   }
>
>}
>
>
># An example config file for JDBCAppender:
>
>log4j.rootCategory=DEBUG, jdbc
>
># JDBCAppender writes messages into the database
>log4j.appender.jdbc=org.apache.log4j.JDBCAppender
>
># DB Options
>log4j.appender.jdbc.URL=jdbc:odbc:myDB
>log4j.appender.jdbc.user=me
>log4j.appender.jdbc.password=password
>log4j.appender.jdbc.driver=sun.jdbc.odbc.JdbcOdbcDriver
>
>#SQL statement to be used (with multiple columns formated)
>log4j.appender.jdbc.sql=insert into logTable (message, class, priority, 
>log_date) values ('%m', '%c', '%p', '%d')
>
>#set the buffer size
>log4j.appender.JDBC.buffersize=2
>
>
>--
>To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
>For additional commands, e-mail: <ma...@jakarta.apache.org>

--
Ceki

My link of the month: http://java.sun.com/aboutJava/standardization/


--
To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>