springboot+logback日志异步数据库

logback.xml配置文件:




    
    


    
    
        
            
            %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n
        
    


    
    
        
            
            ${LOG_HOME}/%d{yyyy-MM-dd}/MIXPAY_%d{yyyy-MM-s}.log
            
            50
        
        
            
            %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n
        
        
        
            50MB
        
    


    
    
    
    
    
    


    
    
        
             
             DB
             ACCEPT
             DENY
        
        
            
                net.sf.log4jdbc.DriverSpy
                jdbc:log4jdbc:mysql://127.0.0.1:3306/dbname?characterEncoding=UTF-8
                root
                123456
            
        
    


    
    
          
          true
    

    
    
        
        
        
        
        
        
    

异步日志的核心配置如下:


    
    
        
        
             
             DB
             ACCEPT
             DENY
        
        
            
            
                
                net.sf.log4jdbc.DriverSpy
                
                jdbc:log4jdbc:mysql://127.0.0.1:3306/dbname?characterEncoding=UTF-8
                
                root
                
                123456
            
        
    

自定义 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 {

    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 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 mergePropertyMaps(ILoggingEvent event) {
        Map mergedMap = new HashMap();
        // 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 loggerContextMap = event.getLoggerContextVO().getPropertyMap();
        Map 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 {

    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!");

数据库显示如下

如果项目集成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系列)