美文网首页
springboot+logback日志异步数据库

springboot+logback日志异步数据库

作者: flyingkid | 来源:发表于2018-12-01 17:55 被阅读0次

    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;
     
    }
    

    相关文章

      网友评论

          本文标题:springboot+logback日志异步数据库

          本文链接:https://www.haomeiwen.com/subject/szercqtx.html