Logback 日志持久化

Logback是log4j的增强版,比log4j更具灵活,其提供了将日志输出到数据库的功能,本文将介绍如何将指定的日志输出到mysql中。

一、自定义log标志

由于Logback原生的配置会将所有的日志信息输出到mysql数据表中,故需要自定义标志,继承AbstractMatcherFilter,过滤掉无标志的日志:

1、自定义标志过滤器

 public class LogbackMarkerFilter extends AbstractMatcherFilter<ILoggingEvent> {
 
     private Marker markerToMatch = null;
 
     @Override
     public void start() {
         if (null != this.markerToMatch) {
             super.start();
         } else {
             addError(" no MARKER yet !");
         }
     }
 
     @Override
     public FilterReply decide(ILoggingEvent event) {
         Marker marker = event.getMarker();
         if (!isStarted()) {
             return FilterReply.NEUTRAL;
         }
         if (null == marker) {
             return onMismatch;
         }
         if (markerToMatch.contains(marker)) {
             return onMatch;
         }
         return onMismatch;
     }
 
     public void setMarker(String markerStr) {
         if (null != markerStr) {
             markerToMatch = MarkerFactory.getMarker(markerStr);
         }
     }
 }

2、logback-spring.xml 相关配置文件

 <!-- 数据库日志记录 -->
 <appender name="DB_APPENDER" class="ch.qos.logback.classic.db.DBAppender">
   <filter class="com.cenobitor.logging.filter.LogbackMarkerFilter">
        <!-- 自定义标志 -->
        <marker>DB</marker>
        <onMatch>ACCEPT</onMatch>
        <onMismatch>DENY</onMismatch>
     </filter>
   <!-- 配置数据源 springboot默认情况会开启光连接池 -->
   <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
         <driverClass>${driverClass}</driverClass>
         <url>${url}</url>
         <user>${username}</user>
         <password>${password}</password>
     </connectionSource>
 </appender>
 
 <!-- 异步日志记录 -->
 <appender name="ASYNC_APPENDER" class="ch.qos.logback.classic.AsyncAppender">
   <appender-ref ref="DB_APPENDER" />
   <includeCallerData>true</includeCallerData>
 </appender>
 
         <!-- 日志输出级别 -->
 <root level="${LOG_LEVEL}">
   <appender-ref ref="ASYNC_APPENDER" />
 </root>

经配置,其需要建三张数据表,分别为日志信息、异常信息、属性信息,其建表语句如下:

 BEGIN;
 DROP TABLE IF EXISTS logging_event_property;
 DROP TABLE IF EXISTS logging_event_exception;
 DROP TABLE IF EXISTS logging_event;
 COMMIT;
 
 
 BEGIN;
 CREATE TABLE logging_event 
   (
     timestmp         BIGINT NOT NULL,
     formatted_message  TEXT NOT NULL,
     logger_name       VARCHAR(254) NOT NULL,
     level_string      VARCHAR(254) NOT NULL,
     thread_name       VARCHAR(254),
     reference_flag    SMALLINT,
     arg0              VARCHAR(254),
     arg1              VARCHAR(254),
     arg2              VARCHAR(254),
     arg3              VARCHAR(254),
     caller_filename   VARCHAR(254) NOT NULL,
     caller_class      VARCHAR(254) NOT NULL,
     caller_method     VARCHAR(254) NOT NULL,
     caller_line       CHAR(4) NOT NULL,
     event_id          BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
   );
 COMMIT;
 
 BEGIN;
 CREATE TABLE logging_event_property
   (
     event_id          BIGINT NOT NULL,
     mapped_key        VARCHAR(254) NOT NULL,
     mapped_value      TEXT,
     PRIMARY KEY(event_id, mapped_key),
     FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
   );
 COMMIT;
 
 BEGIN;
 CREATE TABLE logging_event_exception
   (
     event_id         BIGINT NOT NULL,
     i                SMALLINT NOT NULL,
     trace_line       VARCHAR(254) NOT NULL,
     PRIMARY KEY(event_id, i),
     FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
   );
 COMMIT;

二、自定义输出日志

由于Logback原生要求建三张表,如何指定指输出一种信息,及自定义日志内容,而异常、属性信息不输出?

通过查看DBAppender发现,插入数据方法,此处只需重写DBAppender,即继承DBAppenderBase<ILoggingEvent>,删除掉异常、属性信息插入的相关方法即可实现只输出指定日志到指定表,而其它信息将不会输出到数据库中,代码如下:

 public class LogDBAppender extends DBAppenderBase<ILoggingEvent> {
 
     protected String insertSQL;
     protected static final Method GET_GENERATED_KEYS_METHOD;
 
     private DBNameResolver dbNameResolver;
 
     static final int TIMESTMP_INDEX = 1;
     static final int FORMATTED_MESSAGE_INDEX = 2;
     static final int LOGGER_NAME_INDEX = 3;
     static final int LEVEL_STRING_INDEX = 4;
     static final int THREAD_NAME_INDEX = 5;
     static final int REFERENCE_FLAG_INDEX = 6;
     static final int ARG0_INDEX = 7;
     static final int ARG1_INDEX = 8;
     static final int ARG2_INDEX = 9;
     static final int ARG3_INDEX = 10;
     static final int CALLER_FILENAME_INDEX = 11;
     static final int CALLER_CLASS_INDEX = 12;
     static final int CALLER_METHOD_INDEX = 13;
     static final int CALLER_LINE_INDEX = 14;
     static final int EVENT_ID_INDEX = 15;
 
     static final StackTraceElement EMPTY_CALLER_DATA = CallerData.naInstance();
 
     static {
         // PreparedStatement.getGeneratedKeys() method was added in JDK 1.4
         Method getGeneratedKeysMethod;
         try {
             // the
             getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);
         } catch (Exception ex) {
             getGeneratedKeysMethod = null;
         }
         GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
     }
 
     public void setDbNameResolver(DBNameResolver dbNameResolver) {
         this.dbNameResolver = dbNameResolver;
     }
 
     @Override
     public void start() {
         if (dbNameResolver == null)
             dbNameResolver = new DefaultDBNameResolver();
         insertSQL = buildInsertSQL(dbNameResolver);
         super.start();
     }
 
     @Override
     protected void subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement) throws Throwable {
 
         bindLoggingEventWithInsertStatement(insertStatement, event);
         bindLoggingEventArgumentsWithPreparedStatement(insertStatement, event.getArgumentArray());
 
         // This is expensive... should we do it every time?
         bindCallerDataWithPreparedStatement(insertStatement, event.getCallerData());
 
         int updateCount = insertStatement.executeUpdate();
         if (updateCount != 1) {
             addWarn("Failed to insert loggingEvent");
         }
     }
 
     protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId) throws Throwable {
         Map<String, String> mergedMap = mergePropertyMaps(event);
         //insertProperties(mergedMap, connection, eventId);
 
 //        if (event.getThrowableProxy() != null) {
 //            insertThrowable(event.getThrowableProxy(), connection, eventId);
 //        }
     }
 
     void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
         stmt.setLong(TIMESTMP_INDEX, event.getTimeStamp());
         stmt.setString(FORMATTED_MESSAGE_INDEX, event.getFormattedMessage());
         stmt.setString(LOGGER_NAME_INDEX, event.getLoggerName());
         stmt.setString(LEVEL_STRING_INDEX, event.getLevel().toString());
         stmt.setString(THREAD_NAME_INDEX, event.getThreadName());
         stmt.setShort(REFERENCE_FLAG_INDEX, DBHelper.computeReferenceMask(event));
     }
 
     void bindLoggingEventArgumentsWithPreparedStatement(PreparedStatement stmt, Object[] argArray) throws SQLException {
 
         int arrayLen = argArray != null ? argArray.length : 0;
 
         for (int i = 0; i < arrayLen &amp;&amp; i < 4; i++) {
             stmt.setString(ARG0_INDEX + i, asStringTruncatedTo254(argArray[i]));
         }
         if (arrayLen < 4) {
             for (int i = arrayLen; i < 4; i++) {
                 stmt.setString(ARG0_INDEX + i, null);
             }
         }
     }
 
     String asStringTruncatedTo254(Object o) {
         String s = null;
         if (o != null) {
             s = o.toString();
         }
 
         if (s == null) {
             return null;
         }
         if (s.length() <= 254) {
             return s;
         } else {
             return s.substring(0, 254);
         }
     }
 
     void bindCallerDataWithPreparedStatement(PreparedStatement stmt, StackTraceElement[] callerDataArray) throws SQLException {
 
         StackTraceElement caller = extractFirstCaller(callerDataArray);
 
         stmt.setString(CALLER_FILENAME_INDEX, caller.getFileName());
         stmt.setString(CALLER_CLASS_INDEX, caller.getClassName());
         stmt.setString(CALLER_METHOD_INDEX, caller.getMethodName());
         stmt.setString(CALLER_LINE_INDEX, Integer.toString(caller.getLineNumber()));
     }
 
     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 &amp;&amp; callerDataArray.length > 0 &amp;&amp; callerDataArray[0] != null;
     }
 
     Map<String, String> mergePropertyMaps(ILoggingEvent event) {
         Map<String, String> mergedMap = new HashMap<String, String>();
         // we add the context properties first, then the event properties, since
         // we consider that event-specific properties should have priority over
         // context-wide properties.
         Map<String, String> loggerContextMap = event.getLoggerContextVO().getPropertyMap();
         Map<String, String> mdcMap = event.getMDCPropertyMap();
         if (loggerContextMap != null) {
             mergedMap.putAll(loggerContextMap);
         }
         if (mdcMap != null) {
             mergedMap.putAll(mdcMap);
         }
 
         return mergedMap;
     }
 
     @Override
     protected Method getGeneratedKeysMethod() {
         return GET_GENERATED_KEYS_METHOD;
     }
 
     @Override
     protected String getInsertSQL() {
         return insertSQL;
     }
 
 
     static String buildInsertSQL(DBNameResolver dbNameResolver) {
         StringBuilder sqlBuilder = new StringBuilder("INSERT INTO ");
         sqlBuilder.append(dbNameResolver.getTableName(TableName.LOGGING_EVENT)).append(" (");
         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.TIMESTMP)).append(", ");
         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.FORMATTED_MESSAGE)).append(", ");
         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.LOGGER_NAME)).append(", ");
         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.LEVEL_STRING)).append(", ");
         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.THREAD_NAME)).append(", ");
         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.REFERENCE_FLAG)).append(", ");
         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG0)).append(", ");
         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG1)).append(", ");
         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG2)).append(", ");
         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG3)).append(", ");
         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_FILENAME)).append(", ");
         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_CLASS)).append(", ");
         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_METHOD)).append(", ");
         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_LINE)).append(") ");
         sqlBuilder.append("VALUES (?, ?, ? ,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
         return sqlBuilder.toString();
     }
 }

现在只需将配置中引用的DBAppender:

<appender name="DB_APPENDER" class="ch.qos.logback.classic.db.DBAppender">

更改为自己重写的类:

<appender name="DB_APPENDER" class="com.cenobitor.logging.db.LogDBAppender">

表logging_event_property、表logging_event_exception将可删除,至此基本的配置已完成,可以畅快的使用了。

三、使用

log.info(MarkerFactory.getMarker("DB"), "logback!");

即可异步将该日志输出到数据库中。&nbsp;