springboot+logback日誌非同步資料庫
阿新 • • 發佈:2019-01-04
logback.xml配置檔案:
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="false"> <!--定義日誌檔案的儲存地址 勿在 LogBack 的配置中使用相對路徑--> <springProperty scope="context" name="LOG_HOME" source="logging.path"/> <!-- 控制檯輸出 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化輸出:%d表示日期,%thread表示執行緒名,%-5level:級別從左顯示5個字元寬度%msg:日誌訊息,%n是換行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <!-- 按照每天生成日誌檔案 --> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!--日誌檔案輸出的檔名--> <FileNamePattern>${LOG_HOME}/%d{yyyy-MM-dd}/MIXPAY_%d{yyyy-MM-s}.log</FileNamePattern> <!--日誌檔案保留天數--> <MaxHistory>50</MaxHistory> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化輸出:%d表示日期,%thread表示執行緒名,%-5level:級別從左顯示5個字元寬度%msg:日誌訊息,%n是換行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> <!--日誌檔案最大的大小--> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <MaxFileSize>50MB</MaxFileSize> </triggeringPolicy> </appender> <!-- show parameters for hibernate sql 專為 Hibernate 定製 --> <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/> <logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="DEBUG"/> <logger name="org.hibernate.SQL" level="DEBUG"/> <logger name="org.hibernate.engine.QueryParameters" level="DEBUG"/> <logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG"/> <!--日誌非同步到資料庫 --> <appender name="DB_APPENDER" class="com.config.LogDBAppender"> <filter class="com.config.LogbackMarkerFilter"> <!-- 自定義標誌 --> <marker>DB</marker> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource"> <dataSource class="com.alibaba.druid.pool.DruidDataSource"> <driverClassName>net.sf.log4jdbc.DriverSpy</driverClassName> <url>jdbc:log4jdbc:mysql://127.0.0.1:3306/dbname?characterEncoding=UTF-8</url> <username>root</username> <password>123456</password> </dataSource> </connectionSource> </appender> <!-- 非同步日誌記錄 --> <appender name="ASYNC_APPENDER" class="ch.qos.logback.classic.AsyncAppender"> <appender-ref ref="DB_APPENDER" /> <includeCallerData>true</includeCallerData> </appender> <!-- 日誌輸出級別 --> <root level="INFO"> <!-- 控制檯輸出 --> <appender-ref ref="STDOUT"/> <!-- 按照每天生成日誌檔案 --> <appender-ref ref="FILE"/> <!-- 非同步資料庫--> <appender-ref ref="ASYNC_APPENDER"/> </root> </configuration>
非同步日誌的核心配置如下:
<!--日誌非同步到資料庫 --> <!-- 自定義LogDBAppender 拓展DBAppenderBase --> <appender name="DB_APPENDER" class="com.config.LogDBAppender"> <!-- 自定義LogbackMarkerFilter 拓展AbstractMatcherFilter--> <filter class="com.config.LogbackMarkerFilter"> <!-- 自定義標誌 --> <marker>DB</marker> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource"> <!-- 本例採用DruidDataSource連線--> <dataSource class="com.alibaba.druid.pool.DruidDataSource"> <!-- 資料庫連線驅動類 --> <driverClassName>net.sf.log4jdbc.DriverSpy</driverClassName> <!-- 資料庫連線地址 --> <url>jdbc:log4jdbc:mysql://127.0.0.1:3306/dbname?characterEncoding=UTF-8</url> <!-- 資料庫使用者 --> <username>root</username> <!-- 資料庫密碼 --> <password>123456</password> </dataSource> </connectionSource> </appender>
自定義 LogDBAppender (Appender是logback框架中最重要的元件之一)
import ch.qos.logback.classic.db.DBHelper; import ch.qos.logback.classic.db.names.ColumnName; import ch.qos.logback.classic.db.names.DBNameResolver; import ch.qos.logback.classic.db.names.DefaultDBNameResolver; import ch.qos.logback.classic.db.names.TableName; import ch.qos.logback.classic.spi.CallerData; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.db.DBAppenderBase; import java.lang.reflect.Method; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; /** * @Title: LogDBAppender.java * @Description: TODO(日誌持久化配置) * @Author: 愛飄de小子 上午9:43 */ 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"); } } @Override 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 && 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 && callerDataArray.length > 0 && 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(); } }
日誌攔截 新建LogbackMarkerFilter類
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.AbstractMatcherFilter;
import ch.qos.logback.core.spi.FilterReply;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
/**
* @Title: LogbackMarkerFilter.java
* @Description: TODO(日誌攔截)
* @Author: 愛飄de小子 上午10:15
*/
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);
}
}
}
資料庫指令碼:
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;
主要用到logging_event表,logging_event_property和logging_event_exception可以刪除。
使用:
//指定配置的Marker
log.info(MarkerFactory.getMarker("DB"),"hello,logback!");
資料庫顯示如下
如果專案整合spring-data-jpa,可以不執行資料庫指令碼,配置開啟自動更新表,並新建LoggingEvent類:
import lombok.Data;
import javax.persistence.*;
/**
* @Title: LoggingEvent.java
* @Description: TODO(日誌持久實體)
* @Author: 愛飄de小子 上午10:57
*/
@Data
@Entity
@Table(name="logging_event")
public class LoggingEvent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "event_id", nullable = false, columnDefinition = "BIGINT UNSIGNED")
private Long eventId;
@Column(name = "timestmp", nullable = false, columnDefinition = "BIGINT")
private Long timestmp;
@Column(name = "formatted_message", nullable = false, columnDefinition = "text")
private String formattedMessage;
@Column(name = "logger_name", nullable = false, columnDefinition = "varchar(254)")
private String loggerName;
@Column(name = "level_string", nullable = false, columnDefinition = "varchar(254)")
private String levelString;
@Column(name = "thread_name", columnDefinition = "varchar(254) DEFAULT NULL")
private String threadName;
@Column(name = "reference_flag", columnDefinition = "smallint(6)")
private Integer referenceFlag;
@Column(name = "arg0", columnDefinition = "varchar(254) DEFAULT NULL")
private String arg0;
@Column(name = "arg1", columnDefinition = "varchar(254) DEFAULT NULL")
private String arg1;
@Column(name = "arg2", columnDefinition = "varchar(254) DEFAULT NULL")
private String arg2;
@Column(name = "arg3", columnDefinition = "varchar(254) DEFAULT NULL")
private String arg3;
@Column(name = "caller_filename", nullable = false, columnDefinition = "varchar(254)")
private String callerFilename;
@Column(name = "caller_class", nullable = false, columnDefinition = "varchar(254)")
private String callerClass;
@Column(name = "caller_method", nullable = false, columnDefinition = "varchar(254)")
private String callerMethod;
@Column(name = "caller_line", nullable = false, columnDefinition = "varchar(5)")
private String callerLine;
}