Question

I want to create three appenders: file, console and database. Besides I have stored in database three variables: info, error and warn, whose values ​​correspond to one of the above appenders.

So, when I will run the following statement:

Logger.info ("bla bla bla")

I need get the current value of info in the database dynamically and display the message on the right appender (file, console or database).

With Logback:

1. I need to create an entire DBAppender class (like this), because I don't want to store the information in three different tables (only in one).

2. Capture the value of the database for info seems simple thanks to the filters. So I could include the filter in each appender and, based on the value of info, I could decide whether or not to use "the current appender".

With Log4j 2:

1. I can use JDBCAppender (instead of having to create a new class).

2. How do I create a customized filter that serve me to fetch the value of info in the database, and help me decide if I should or should not use one of the appenders (file, console or database)?

The question is:

Is there any way to make it easier the "first point of Logback" or to carry out the "second point of Log4j2"?

Thanks in advance.

Was it helpful?

Solution 2

Finally I found a solution to my problem. In logback.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<configuration>

   <!-- Loads the datasource properties file -->
   <property resource="datasource.properties" />

   <!-- Directory where log files will be stored -->
   <property name="LOG_DIRECTORY" value="/tmp" />

   <!-- Appenders -->
   <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
      <filter class="com.myproject.logback.ConsoleFilter" />
      <encoder>
         <pattern>%d{yyyy-MM-dd HH:mm:ss} [%-5level.%msg - %class.%method : %line]%n</pattern>
      </encoder>
   </appender>

   <appender name="database" class="com.myproject.logback.CustomDBAppender">
      <filter class="com.myproject.logback.DBFilter" />
      <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource">
         <dataSource class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <driverClass>${jdbc.driverClass}</driverClass>
            <jdbcUrl>${jdbc.jdbcUrl}</jdbcUrl>
            <databaseName>${jdbc.databaseName}</databaseName>
            <user>${jdbc.user}</user>
            <password>${jdbc.password}</password>
        </dataSource>
     </connectionSource>
  </appender>

  <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
     <filter class="com.myproject.logback.FileFilter" />
     <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <!-- Rollover daily -->
        <fileNamePattern>${LOG_DIRECTORY}/mylog-%d{yyyy-MM-dd}.txt</fileNamePattern>
        <!-- Keep 10 days of history -->
        <maxHistory>10</maxHistory>
     </rollingPolicy>
     <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss} [%-5level.%msg - %class.%method : %line]%n</pattern>
     </encoder>
  </appender>

  <!-- Logger for my project -->
  <logger name="com.myproject" additivity="false" level="all">
     <appender-ref ref="console" />
     <appender-ref ref="database" />
     <appender-ref ref="file" />
  </logger>

  <root level="info">
     <appender-ref ref="console" />
  </root>

</configuration>

Now CustomDBAppender.java:

public class CustomDBAppender extends DBAppenderBase<ILoggingEvent> {

    protected String insertSQL;

    // Indices of the fields of the table in which the logging information is stored
    private static final int EVENTTIME_INDEX     = 1;
    private static final int MESSAGE_INDEX       = 2;
    private static final int LOGGER_INDEX        = 3;
    private static final int LEVEL_INDEX         = 4;
    private static final int CALLER_CLASS_INDEX  = 5;
    private static final int CALLER_METHOD_INDEX = 6;
    private static final int CALLER_LINE_INDEX   = 7;
    private static final int TRACE_INDEX         = 8;

    static final StackTraceElement EMPTY_CALLER_DATA = CallerData.naInstance();

    @Override
    public void append (ILoggingEvent eventObject) {

       Connection connection = null;
       PreparedStatement insertStatement = null;
       try {
          connection = connectionSource.getConnection();
          connection.setAutoCommit (false);

          insertStatement = connection.prepareStatement (getInsertSQL());

          // Inserting the event in database
          synchronized (this) {
             subAppend (eventObject, connection, insertStatement);
          }
          secondarySubAppend (eventObject, connection, 1);

          connection.commit();

       } catch (Throwable sqle) {
          addError("problem appending event", sqle);
       } finally {
          DBHelper.closeStatement (insertStatement);
          DBHelper.closeConnection (connection);
       }
    }

    @Override
    protected Method getGeneratedKeysMethod() {
       return null;
    }

    @Override
    protected String getInsertSQL() {
       return insertSQL;
    }

    @Override
    protected void secondarySubAppend (ILoggingEvent eventObject,
                       Connection connection, long eventId) throws Throwable {}

    @Override
    public void start() {
       insertSQL = CustomDBAppender.buildInsertSQL();
       super.start();
    }

    @Override
    protected void subAppend (ILoggingEvent event, Connection connection,
                      PreparedStatement insertStatement) throws Throwable {

       bindLoggingEventWithInsertStatement (insertStatement, event);
       bindCallerDataWithPreparedStatement (insertStatement, event.getCallerData());

       int updateCount = insertStatement.executeUpdate();
       if (updateCount != 1) {
          addWarn("Failed to insert loggingEvent");
       }
    }

    void bindCallerDataWithPreparedStatement (PreparedStatement stmt,
            StackTraceElement[] callerDataArray) throws SQLException {

       StackTraceElement caller = extractFirstCaller (callerDataArray);

       stmt.setString (CALLER_CLASS_INDEX, caller.getClassName());
       stmt.setString (CALLER_METHOD_INDEX, caller.getMethodName());
       stmt.setString (CALLER_LINE_INDEX, Integer.toString (caller.getLineNumber()));
    }

    void bindLoggingEventWithInsertStatement (PreparedStatement stmt,
            ILoggingEvent event) throws SQLException {

       stmt.setTimestamp (EVENTTIME_INDEX, new Timestamp (event.getTimeStamp()));
       stmt.setString (MESSAGE_INDEX, event.getFormattedMessage());
       stmt.setString (LOGGER_INDEX, event.getLoggerName());
       stmt.setString (LEVEL_INDEX, event.getLevel().toString());

       if (event.getThrowableProxy() != null && 
           event.getThrowableProxy().getStackTraceElementProxyArray() != null)
          stmt.setString (TRACE_INDEX, ThrowableProxyUtil.asString (event.getThrowableProxy()));
       else
          stmt.setString (TRACE_INDEX, null);
    }

    private static String buildInsertSQL () {

       return "INSERT INTO mylogtable "
            + " (eventtime, message, logger, level, callerclass, callermethod, callerline, trace) "
            + "VALUES "
            + "(?, ?, ?, ?, ?, ?, ?, ?)";
    }

    private StackTraceElement extractFirstCaller (StackTraceElement[] callerDataArray) {

       StackTraceElement caller = EMPTY_CALLER_DATA;
       if (hasAtLeastOneNonNullElement (callerDataArray))
          caller = callerDataArray[0];

       return caller;
    }

    private boolean hasAtLeastOneNonNullElement (StackTraceElement[] callerDataArray) {
       return callerDataArray != null && callerDataArray.length > 0 && callerDataArray[0] != null;
    }
 }

Every filter has a similar structure, for this reason I only show one, FileFilter.java:

public class FileFilter extends Filter<ILoggingEvent> {

   @Override
   public FilterReply decide (ILoggingEvent event) {

      try {
         // I have in a database table something like this:
         //    Level   -   Appender
         //    ERROR         FILE
         //    DEBUG        CONSOLE
         //    ...

         // Here I check with a service if "the actual logger level"
         // [event.getLevel()] in the previous database table, is set
         // to record their messages with the "current appender"

         if (thePreviousCondition == true)
            return FilterReply.ACCEPT;

         return FilterReply.DENY;

      } catch (Exception e) {
         return FilterReply.DENY;
      }
   }
}

The previous code is a simplification of "my real code", basically because I have in CustomDBAppender one table for each logger level, instead of a only one "general table". Besides I used REDIS to cache the values of Level-Appender to avoid search always in database.

Also I only define 4 levels in my application: ERROR, WARN, INFO and DEBUG. So by a enum I carry out the equivalence between the level of Logback and the level of my application.

However, this is the skeleton of what has helped me to do what I intended.

OTHER TIPS

Log4j2 supports filters, I believe fairly similar to Logback. It sounds like you are looking for filtering based on level, you can do this with the ThresholdFilter.

Or perhaps you are looking for something like the RoutingAppender (see FAQ for an extended example)? This allows you to configure multiple appenders and dynamically route log events to different appenders based on the ThreadContext map.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top