springboot+logback日志异步数据库
2018-12-01 本文已影响0人
flyingkid
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!");
数据库显示如下
深度截图_选择区域_20181201175331.png如果项目集成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;
}