最近研究了一下logback,发现百度的结果太杂乱,并且还有明显胡编乱造的作者,所以自己记录一下探索的过程。
注意事项:
1:如果配置文件中要使用spring的配置,应把配置文件名称改为logback-spring.xml,并配合springProperty 标签一块使用即可。
2:appender 标签用于配置输出源(控制台,文件,数据库...),class属性中可以制定一个实现类用于处理。
3:logger 标签中name用于配置指定的包名,additivity为true标签表示该包处理完日志还会向上(root)输出。
4: 以下配置文件实现的功能: 读取spring的配置(不同的包配置不同的日志表和级别;数据库连接等信息);
把日志中的两个包下产生的日志,存到数据库的不同的表中,所有的日志都打印在控制台一份。
例如 org.flowable 存到 WF_LOG表中
5: 附 logback-spring.xml 和 FlowableLogForMysqlAppender.java 和创建WF_LOG表的sql文件
6:配置自定义日志输出主要就是这三个文件,其中的标签和方法名及其作用,多仔细看看代码和配置,就能搞明白很简单。
7:源码路径:( logback-classic jar包下 )
创建表的sql: ch.qos.logback.classic.db.script 文件下有不同库的建表语句(新版本的jar好像放在resources 下)
相关处理类: ch.qos.logback.classic.db.DBAppender 和 ch.qos.logback.classic.db.SQLBuilder
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="6 seconds" debug="false">
<contextName>myApplicationName</contextName>
<!-- 获取application.yml的值 -->
<springProperty scope="context" name="url" source="spring.datasource.url"/>
<springProperty scope="context" name="username" source="spring.datasource.username"/>
<springProperty scope="context" name="password" source="spring.datasource.password"/>
<springProperty scope="context" name="driver-class-name" source="spring.datasource.driver-class-name"/>
<springProperty scope="context" name="lzmispt-level" source="workflow-log.lzmispt"/>
<springProperty scope="context" name="flowable-level" source="workflow-log.flowable"/>
<!-- 输出到数据库的配置 flowable -->
<appender name="LOG-FLOWABLE-MYSQL" class="com.lzmispt.config.FlowableLogForMysqlAppender">
<connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource">
<dataSource class="com.alibaba.druid.pool.DruidDataSource">
<driverClassName>${driver-class-name}</driverClassName>
<url>${url}</url>
<username>${username}</username>
<password>${password}</password>
</dataSource>
</connectionSource>
</appender>
<!-- 输出到数据库的配置 workflow-core -->
<appender name="LOG-CORE-MYSQL" class="com.lzmispt.config.CoreLogForMysqlAppender">
<connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource">
<dataSource class="com.alibaba.druid.pool.DruidDataSource">
<driverClassName>${driver-class-name}</driverClassName>
<url>${url}</url>
<username>${username}</username>
<password>${password}</password>
</dataSource>
</connectionSource>
</appender>
<!-- 输出到控制台的配置-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %msg - %file:%line%n</pattern>
<charset>UTF-8</charset>
</encoder>
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level - %msg%n
</Pattern>
</layout>
</appender>
<logger name="com.lzmispt" level="${lzmispt-level}" additivity="true">
<appender-ref ref="LOG-CORE-MYSQL"/>
</logger>
<logger name="org.flowable" level="${flowable-level}" additivity="true">
<appender-ref ref="LOG-FLOWABLE-MYSQL"/>
</logger>
<root level="INFO">
<appender-ref ref="console"/>
</root>
</configuration>
package com.lzmispt.config;
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.sql.Timestamp;
/**
* @Description: 配置flowable日志写入mysql的 WF_Log
* @Author: ldy
* @Date: 2020-07-08 16:22
*/
public class FlowableLogForMysqlAppender extends DBAppenderBase<ILoggingEvent> {
private String insertSQL;
private static final Method GET_GENERATED_KEYS_METHOD;
private static final int TIME_INDEX = 1;
private static final int MESSAGE_INDEX = 2;
private static final int LEVEL_STRING_INDEX = 3;
private static final int LOGGER_NAME_INDEX = 4;
private static final int THREAD_NAME_INDEX = 5;
private static final int CALLER_FILENAME_INDEX = 6;
private static final int CALLER_CLASS_INDEX = 7;
private static final int CALLER_METHOD_INDEX = 8;
private static final int CALLER_LINE_INDEX = 9;
private 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;
}
@Override
public void start() {
insertSQL = buildInsertSQL();
super.start();
}
private static String buildInsertSQL() {
return "INSERT INTO WF_Log " +
"(time, message, logger_name, level_string, thread_name," +
"caller_filename, caller_class, caller_method, caller_line)"+
"VALUES (?, ?, ? ,?, ?, ?, ?, ?, ?)";
}
private void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
stmt.setTimestamp(TIME_INDEX, new Timestamp(event.getTimeStamp()));
stmt.setString(MESSAGE_INDEX, event.getFormattedMessage());
stmt.setString(LEVEL_STRING_INDEX, event.getLevel().toString());
stmt.setString(LOGGER_NAME_INDEX, event.getLoggerName());
stmt.setString(THREAD_NAME_INDEX, event.getThreadName());
}
private 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()));
}
@Override
protected void subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement) throws Throwable {
bindLoggingEventWithInsertStatement(insertStatement, event);
// This is expensive... should we do it every time?
bindCallerDataWithPreparedStatement(insertStatement, event.getCallerData());
int updateCount = insertStatement.executeUpdate();
if (updateCount != 1) {
addWarn("Failed to insert WF_Log");
}
}
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;
}
@Override
protected Method getGeneratedKeysMethod() {
return GET_GENERATED_KEYS_METHOD;
}
@Override
protected String getInsertSQL() {
return insertSQL;
}
protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId) throws Throwable {
}
}
BEGIN;
DROP TABLE IF EXISTS WF_Log;
COMMIT;
BEGIN;
CREATE TABLE WF_Log
(
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
time DATETIME(6) NOT NULL,
message TEXT NOT NULL,
level_string VARCHAR(254) NOT NULL,
logger_name VARCHAR(254) NOT NULL,
thread_name VARCHAR(254),
reference_flag SMALLINT,
caller_filename VARCHAR(254) NOT NULL,
caller_class VARCHAR(254) NOT NULL,
caller_method VARCHAR(254) NOT NULL,
caller_line CHAR(4) NOT NULL
)comment '记录flowable流程引擎的日志';
COMMIT;