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 <springProperty scope="context" name="driverClass" source="spring.datasource.driver-class-name"/> 3 <springProperty scope="context" name="url" source="spring.datasource.url"/> 4 <springProperty scope="context" name="username" source="spring.datasource.username"/> 5 <springProperty scope="context" name="password" source="spring.datasource.password"/> 6 <springProperty scope="context" name="logFilePath" source="logging.path"/> 7 <springProperty scope="context" name="maxHistory" source="logging.maxHistory"/> 8 9 <!-- 数据库日志记录 --> 10 <appender name="DB_APPENDER" class="ch.qos.logback.classic.db.DBAppender"> 11 <filter class="com.cenobitor.logging.filter.LogbackMarkerFilter"> 12 <!-- 自定义标志 --> 13 <marker>DB</marker> 14 <onMatch>ACCEPT</onMatch> 15 <onMismatch>DENY</onMismatch> 16 </filter> 17 <!-- 配置数据源 springboot默认情况会开启光连接池 --> 18 <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource"> 19 <driverClass>${driverClass}</driverClass> 20 <url>${url}</url> 21 <user>${username}</user> 22 <password>${password}</password> 23 </connectionSource> 24 </appender> 25 26 <!-- 异步日志记录 --> 27 <appender name="ASYNC_APPENDER" class="ch.qos.logback.classic.AsyncAppender"> 28 <appender-ref ref="DB_APPENDER" /> 29 <includeCallerData>true</includeCallerData> 30 </appender> 31 32 <!-- 日志输出级别 --> 33 <root level="${LOG_LEVEL}"> 34 <appender-ref ref="ASYNC_APPENDER" /> 35 </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!");
即可异步将该日志输出到数据库中。