目前公司需要對在做的系統日志進行入庫保存,使用比較多logback,所以就打算用logback實現
首先導入logback支持的連接池
<!-- 這個依賴必須存在,否則會報java.lang.ClassNotFoundException: org.apache.commons.dbcp.BasicDataSource-->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
然后通過ch.qos.logback.classic.db.script下的選擇對應的sql文件初始化表,我這里用的是mysql,不過這里有個坑,如果不在建表的時候設置字符集,中文的日志就不會保存到
mysql中,所以這里改一下:
# Logback: the reliable, generic, fast and flexible logging framework.
# Copyright (C) 1999-2010, QOS.ch. All rights reserved.
#
# See http://logback.qos.ch/license.html for the applicable licensing
# conditions.
# This SQL script creates the required tables by ch.qos.logback.classic.db.DBAppender.
#
# It is intended for MySQL databases. It has been tested on MySQL 5.1.37
# on Linux
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
) comment '記錄事件表' charset = utf8mb4;
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)
) comment '記錄事件屬性表' charset = utf8mb4;
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)
) comment '記錄事件異常' charset = utf8mb4;
COMMIT;
然后配置logback.xml,logback.xml文件配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!--
* Copyright (c) 2018-2028, Chill Zhuang 庄騫 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* -->
<configuration scan="true" scanPeriod="60 seconds">
<!-- 彩色日志依賴的渲染類 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" />
<!-- 控制台輸出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="application_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件輸出的文件名-->
<FileNamePattern>${LOG_HOME}/info/info.log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天數-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度%msg:日志消息,%n是換行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>500MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 異常日志文件 -->
<appender name="error_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件輸出的文件名-->
<FileNamePattern>${LOG_HOME}/error/error.log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天數-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度%msg:日志消息,%n是換行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>500MB</MaxFileSize>
</triggeringPolicy>
<!-- 只打印錯誤日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--連接數據庫配置-->
<appender name="db_classic_mysql_pool" class="ch.qos.logback.classic.db.DBAppender">
<filter class="org.example.core.log.SuperfluousLogFilter "/>
<connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource">
<dataSource class="org.apache.commons.dbcp.BasicDataSource">
<driverClassName>com.mysql.cj.jdbc.Driver</driverClassName>
<url>jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=Asia/Shanghai</url>
<username>root</username>
<password>123456</password>
</dataSource>
</connectionSource>
</appender>
<!--myibatis log configure-->
<logger name="com.apache.ibatis" level="TRACE"/>
<logger name="java.sql.Connection" level="DEBUG" />
<logger name="java.sql.Statement" level="DEBUG"/>
<logger name="java.sql.PreparedStatement" level="DEBUG"/>
<!-- 日志輸出級別 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="application_file" />
<appender-ref ref="error_file" />
<appender-ref ref="db_classic_mysql_pool" />
</root>
</configuration>
重點在 filter標簽配置的 org.example.core.log.SuperfluousLogFilter
,實現如下:
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;
public class SuperfluousLogFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent event) {
System.out.println("eventLoggerName:" + event.getLoggerName());
if (getPackName(event.getLoggerName()).startsWith("org.springblade.modules")) {
return FilterReply.ACCEPT;
} else {
return FilterReply.DENY;
}
}
public String getPackName(String className) {
return className.substring(0, className.lastIndexOf("."));
}
}
這里主要做的就是通過實現decide
方法來判斷是否是你需要保存的日志,FilterReply.ACCEPT
表示通過,FilterReply.DENY
表示拒絕。
ILoggingEvent
包含相當多日志信息的獲取來滿足不同過濾條件的要求,在此不贅述,根據自己要求實現既可。
擴展DBAppender用於支持自定義表
如果不想保存在logback的表中,那么可以自己重寫一個DBAppender用於擴展,如果需要使用到ApplicationContext,可以實現ApplicationContextAware,代碼如下
@Configuration
public class LogDBAppender extends DBAppenderBase<ILoggingEvent> implements ApplicationContextAware {
protected static final Method GET_GENERATED_KEYS_METHOD;
private static ApplicationContext applicationContext;
//插入sql
protected String insertSQL;
// message 日志內容
static final int MESSAGE = 1;
// level_string
static final int LEVEL_STRING = 2;
// created_time 時間
static final int CREATE_TIME = 3;
// logger_name 全類名
static final int LOGGER_NAME = 4;
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() {
// 將寫好的sql語句賦值給insertSQL
insertSQL = buildInsertSQL();
super.start();
}
// 自己寫新增sql語句
private static String buildInsertSQL() {
return "insert into xxx";
}
@Override
protected Method getGeneratedKeysMethod() {
return GET_GENERATED_KEYS_METHOD;
}
@Override
protected String getInsertSQL() {
return insertSQL;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
LogDBAppender.applicationContext = applicationContext;
}
/**
* 主要修改的方法
*
* @param stmt
* @param event
* @throws SQLException
*/
private void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
//通過PreparedStatement 和 ILoggingEvent保存需要的內容
}
@Override
protected void subAppend(ILoggingEvent eventObject, Connection connection, PreparedStatement statement) throws Throwable {
bindLoggingEventWithInsertStatement(statement, eventObject);
int updateCount = statement.executeUpdate();
if (updateCount != 1) {
addWarn("Failed to insert loggingEvent");
}
}
@Override
protected void secondarySubAppend(ILoggingEvent eventObject, Connection connection, long eventId) throws Throwable {
}
}
如果執行了且沒報錯,但是數據庫卻沒有插入新的數據,那應該是發生了異常,但是logback不會將異常信息拋出,所以可以先通過測試用例確保自己的
寫的jdbc代碼是可以正確執行的。
然后將該自定義的LogDBAppender配置在logback.xml中即可生效
<!--連接數據庫配置-->
<appender name="db_classic_mysql_pool" class="org.example.core.log.LogDBAppender">
<connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource">
<dataSource class="org.apache.commons.dbcp.BasicDataSource">
<driverClassName>com.mysql.cj.jdbc.Driver</driverClassName>
<url>jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=Asia/Shanghai</url>
<username>root</username>
<password>123456</password>
</dataSource>
</connectionSource>
</appender>