Logback 日志持久化

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

一、自定义log标志

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

1、自定义标志过滤器

 1 public class LogbackMarkerFilter extends AbstractMatcherFilter<ILoggingEvent> {
 2 
 3     private Marker markerToMatch = null;
 4 
 5     @Override
 6     public void start() {
 7         if (null != this.markerToMatch) {
 8             super.start();
 9         } else {
10             addError(" no MARKER yet !");
11         }
12     }
13 
14     @Override
15     public FilterReply decide(ILoggingEvent event) {
16         Marker marker = event.getMarker();
17         if (!isStarted()) {
18             return FilterReply.NEUTRAL;
19         }
20         if (null == marker) {
21             return onMismatch;
22         }
23         if (markerToMatch.contains(marker)) {
24             return onMatch;
25         }
26         return onMismatch;
27     }
28 
29     public void setMarker(String markerStr) {
30         if (null != markerStr) {
31             markerToMatch = MarkerFactory.getMarker(markerStr);
32         }
33     }
34 }

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

 1 <!-- 数据库日志记录 -->
 2 <appender name="DB_APPENDER" class="ch.qos.logback.classic.db.DBAppender">
 3   <filter class="com.cenobitor.logging.filter.LogbackMarkerFilter">
 4        <!-- 自定义标志 -->
 5        <marker>DB</marker>
 6        <onMatch>ACCEPT</onMatch>
 7        <onMismatch>DENY</onMismatch>
 8     </filter>
 9   <!-- 配置数据源 springboot默认情况会开启光连接池 -->
10   <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
11         <driverClass>${driverClass}</driverClass>
12         <url>${url}</url>
13         <user>${username}</user>
14         <password>${password}</password>
15     </connectionSource>
16 </appender>
17 
18 <!-- 异步日志记录 -->
19 <appender name="ASYNC_APPENDER" class="ch.qos.logback.classic.AsyncAppender">
20   <appender-ref ref="DB_APPENDER" />
21   <includeCallerData>true</includeCallerData>
22 </appender>
23 
24         <!-- 日志输出级别 -->
25 <root level="${LOG_LEVEL}">
26   <appender-ref ref="ASYNC_APPENDER" />
27 </root>

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

 1 BEGIN;
 2 DROP TABLE IF EXISTS logging_event_property;
 3 DROP TABLE IF EXISTS logging_event_exception;
 4 DROP TABLE IF EXISTS logging_event;
 5 COMMIT;
 6 
 7 
 8 BEGIN;
 9 CREATE TABLE logging_event 
10   (
11     timestmp         BIGINT NOT NULL,
12     formatted_message  TEXT NOT NULL,
13     logger_name       VARCHAR(254) NOT NULL,
14     level_string      VARCHAR(254) NOT NULL,
15     thread_name       VARCHAR(254),
16     reference_flag    SMALLINT,
17     arg0              VARCHAR(254),
18     arg1              VARCHAR(254),
19     arg2              VARCHAR(254),
20     arg3              VARCHAR(254),
21     caller_filename   VARCHAR(254) NOT NULL,
22     caller_class      VARCHAR(254) NOT NULL,
23     caller_method     VARCHAR(254) NOT NULL,
24     caller_line       CHAR(4) NOT NULL,
25     event_id          BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
26   );
27 COMMIT;
28 
29 BEGIN;
30 CREATE TABLE logging_event_property
31   (
32     event_id          BIGINT NOT NULL,
33     mapped_key        VARCHAR(254) NOT NULL,
34     mapped_value      TEXT,
35     PRIMARY KEY(event_id, mapped_key),
36     FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
37   );
38 COMMIT;
39 
40 BEGIN;
41 CREATE TABLE logging_event_exception
42   (
43     event_id         BIGINT NOT NULL,
44     i                SMALLINT NOT NULL,
45     trace_line       VARCHAR(254) NOT NULL,
46     PRIMARY KEY(event_id, i),
47     FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
48   );
49 COMMIT;

二、自定义输出日志

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

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

  1 public class LogDBAppender extends DBAppenderBase<ILoggingEvent> {
  2 
  3     protected String insertSQL;
  4     protected static final Method GET_GENERATED_KEYS_METHOD;
  5 
  6     private DBNameResolver dbNameResolver;
  7 
  8     static final int TIMESTMP_INDEX = 1;
  9     static final int FORMATTED_MESSAGE_INDEX = 2;
 10     static final int LOGGER_NAME_INDEX = 3;
 11     static final int LEVEL_STRING_INDEX = 4;
 12     static final int THREAD_NAME_INDEX = 5;
 13     static final int REFERENCE_FLAG_INDEX = 6;
 14     static final int ARG0_INDEX = 7;
 15     static final int ARG1_INDEX = 8;
 16     static final int ARG2_INDEX = 9;
 17     static final int ARG3_INDEX = 10;
 18     static final int CALLER_FILENAME_INDEX = 11;
 19     static final int CALLER_CLASS_INDEX = 12;
 20     static final int CALLER_METHOD_INDEX = 13;
 21     static final int CALLER_LINE_INDEX = 14;
 22     static final int EVENT_ID_INDEX = 15;
 23 
 24     static final StackTraceElement EMPTY_CALLER_DATA = CallerData.naInstance();
 25 
 26     static {
 27         // PreparedStatement.getGeneratedKeys() method was added in JDK 1.4
 28         Method getGeneratedKeysMethod;
 29         try {
 30             // the
 31             getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);
 32         } catch (Exception ex) {
 33             getGeneratedKeysMethod = null;
 34         }
 35         GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
 36     }
 37 
 38     public void setDbNameResolver(DBNameResolver dbNameResolver) {
 39         this.dbNameResolver = dbNameResolver;
 40     }
 41 
 42     @Override
 43     public void start() {
 44         if (dbNameResolver == null)
 45             dbNameResolver = new DefaultDBNameResolver();
 46         insertSQL = buildInsertSQL(dbNameResolver);
 47         super.start();
 48     }
 49 
 50     @Override
 51     protected void subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement) throws Throwable {
 52 
 53         bindLoggingEventWithInsertStatement(insertStatement, event);
 54         bindLoggingEventArgumentsWithPreparedStatement(insertStatement, event.getArgumentArray());
 55 
 56         // This is expensive... should we do it every time?
 57         bindCallerDataWithPreparedStatement(insertStatement, event.getCallerData());
 58 
 59         int updateCount = insertStatement.executeUpdate();
 60         if (updateCount != 1) {
 61             addWarn("Failed to insert loggingEvent");
 62         }
 63     }
 64 
 65     protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId) throws Throwable {
 66         Map<String, String> mergedMap = mergePropertyMaps(event);
 67         //insertProperties(mergedMap, connection, eventId);
 68 
 69 //        if (event.getThrowableProxy() != null) {
 70 //            insertThrowable(event.getThrowableProxy(), connection, eventId);
 71 //        }
 72     }
 73 
 74     void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
 75         stmt.setLong(TIMESTMP_INDEX, event.getTimeStamp());
 76         stmt.setString(FORMATTED_MESSAGE_INDEX, event.getFormattedMessage());
 77         stmt.setString(LOGGER_NAME_INDEX, event.getLoggerName());
 78         stmt.setString(LEVEL_STRING_INDEX, event.getLevel().toString());
 79         stmt.setString(THREAD_NAME_INDEX, event.getThreadName());
 80         stmt.setShort(REFERENCE_FLAG_INDEX, DBHelper.computeReferenceMask(event));
 81     }
 82 
 83     void bindLoggingEventArgumentsWithPreparedStatement(PreparedStatement stmt, Object[] argArray) throws SQLException {
 84 
 85         int arrayLen = argArray != null ? argArray.length : 0;
 86 
 87         for (int i = 0; i < arrayLen && i < 4; i++) {
 88             stmt.setString(ARG0_INDEX + i, asStringTruncatedTo254(argArray[i]));
 89         }
 90         if (arrayLen < 4) {
 91             for (int i = arrayLen; i < 4; i++) {
 92                 stmt.setString(ARG0_INDEX + i, null);
 93             }
 94         }
 95     }
 96 
 97     String asStringTruncatedTo254(Object o) {
 98         String s = null;
 99         if (o != null) {
100             s = o.toString();
101         }
102 
103         if (s == null) {
104             return null;
105         }
106         if (s.length() <= 254) {
107             return s;
108         } else {
109             return s.substring(0, 254);
110         }
111     }
112 
113     void bindCallerDataWithPreparedStatement(PreparedStatement stmt, StackTraceElement[] callerDataArray) throws SQLException {
114 
115         StackTraceElement caller = extractFirstCaller(callerDataArray);
116 
117         stmt.setString(CALLER_FILENAME_INDEX, caller.getFileName());
118         stmt.setString(CALLER_CLASS_INDEX, caller.getClassName());
119         stmt.setString(CALLER_METHOD_INDEX, caller.getMethodName());
120         stmt.setString(CALLER_LINE_INDEX, Integer.toString(caller.getLineNumber()));
121     }
122 
123     private StackTraceElement extractFirstCaller(StackTraceElement[] callerDataArray) {
124         StackTraceElement caller = EMPTY_CALLER_DATA;
125         if (hasAtLeastOneNonNullElement(callerDataArray))
126             caller = callerDataArray[0];
127         return caller;
128     }
129 
130     private boolean hasAtLeastOneNonNullElement(StackTraceElement[] callerDataArray) {
131         return callerDataArray != null && callerDataArray.length > 0 && callerDataArray[0] != null;
132     }
133 
134     Map<String, String> mergePropertyMaps(ILoggingEvent event) {
135         Map<String, String> mergedMap = new HashMap<String, String>();
136         // we add the context properties first, then the event properties, since
137         // we consider that event-specific properties should have priority over
138         // context-wide properties.
139         Map<String, String> loggerContextMap = event.getLoggerContextVO().getPropertyMap();
140         Map<String, String> mdcMap = event.getMDCPropertyMap();
141         if (loggerContextMap != null) {
142             mergedMap.putAll(loggerContextMap);
143         }
144         if (mdcMap != null) {
145             mergedMap.putAll(mdcMap);
146         }
147 
148         return mergedMap;
149     }
150 
151     @Override
152     protected Method getGeneratedKeysMethod() {
153         return GET_GENERATED_KEYS_METHOD;
154     }
155 
156     @Override
157     protected String getInsertSQL() {
158         return insertSQL;
159     }
160 
161 
162     static String buildInsertSQL(DBNameResolver dbNameResolver) {
163         StringBuilder sqlBuilder = new StringBuilder("INSERT INTO ");
164         sqlBuilder.append(dbNameResolver.getTableName(TableName.LOGGING_EVENT)).append(" (");
165         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.TIMESTMP)).append(", ");
166         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.FORMATTED_MESSAGE)).append(", ");
167         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.LOGGER_NAME)).append(", ");
168         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.LEVEL_STRING)).append(", ");
169         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.THREAD_NAME)).append(", ");
170         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.REFERENCE_FLAG)).append(", ");
171         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG0)).append(", ");
172         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG1)).append(", ");
173         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG2)).append(", ");
174         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG3)).append(", ");
175         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_FILENAME)).append(", ");
176         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_CLASS)).append(", ");
177         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_METHOD)).append(", ");
178         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_LINE)).append(") ");
179         sqlBuilder.append("VALUES (?, ?, ? ,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
180         return sqlBuilder.toString();
181     }
182 }

现在只需将配置中引用的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!");
即可异步将该日志输出到数据库中。 

猜你喜欢

转载自www.cnblogs.com/gdwkong/p/9251471.html