SpringBoot+LogBack實現異步日志入庫和日志信息過濾


目前公司需要對在做的系統日志進行入庫保存,使用比較多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&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;zeroDateTimeBehavior=convertToNull&amp;transformedBitIsBoolean=true&amp;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&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;zeroDateTimeBehavior=convertToNull&amp;transformedBitIsBoolean=true&amp;serverTimezone=Asia/Shanghai</url>
                <username>root</username>
                <password>123456</password>
            </dataSource>
        </connectionSource>
    </appender>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM