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.