zoukankan      html  css  js  c++  java
  • Logback 日志持久化

    Logback是log4j的增强版,比log4j更具灵活,其提供了将日志输出到数据库的功能,本文将介绍如何将指定的日志输出到mysql中。

    一、自定义log标志

    由于Logback原生的配置会将所有的日志信息输出到mysql数据表中,故需要自定义标志,继承AbstractMatcherFilter,过滤掉无标志的日志:

    1、自定义标志过滤器

     1 public class LogbackMarkerFilter extends AbstractMatcherFilter<ILoggingEvent> {
     2 
     3     private Marker markerToMatch = null;
     4 
     5     @Override
     6     public void start() {
     7         if (null != this.markerToMatch) {
     8             super.start();
     9         } else {
    10             addError(" no MARKER yet !");
    11         }
    12     }
    13 
    14     @Override
    15     public FilterReply decide(ILoggingEvent event) {
    16         Marker marker = event.getMarker();
    17         if (!isStarted()) {
    18             return FilterReply.NEUTRAL;
    19         }
    20         if (null == marker) {
    21             return onMismatch;
    22         }
    23         if (markerToMatch.contains(marker)) {
    24             return onMatch;
    25         }
    26         return onMismatch;
    27     }
    28 
    29     public void setMarker(String markerStr) {
    30         if (null != markerStr) {
    31             markerToMatch = MarkerFactory.getMarker(markerStr);
    32         }
    33     }
    34 }

    2、logback-spring.xml 相关配置文件

     1 <!-- 读取配置文件中参数 -->
     2 <springProperty scope="context" name="driverClass" source="spring.datasource.driver-class-name"/>
     3 <springProperty scope="context" name="url" source="spring.datasource.url"/>
     4 <springProperty scope="context" name="username" source="spring.datasource.username"/>
     5 <springProperty scope="context" name="password" source="spring.datasource.password"/>
     6 <springProperty scope="context" name="logFilePath" source="logging.path"/>
     7 <springProperty scope="context" name="maxHistory" source="logging.maxHistory"/>
     8 
     9 <!-- 数据库日志记录 -->
    10 <appender name="DB_APPENDER" class="ch.qos.logback.classic.db.DBAppender">
    11   <filter class="com.cenobitor.logging.filter.LogbackMarkerFilter">
    12        <!-- 自定义标志 -->
    13        <marker>DB</marker>
    14        <onMatch>ACCEPT</onMatch>
    15        <onMismatch>DENY</onMismatch>
    16     </filter>
    17   <!-- 配置数据源 springboot默认情况会开启光连接池 -->
    18   <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
    19         <driverClass>${driverClass}</driverClass>
    20         <url>${url}</url>
    21         <user>${username}</user>
    22         <password>${password}</password>
    23     </connectionSource>
    24 </appender>
    25 
    26 <!-- 异步日志记录 -->
    27 <appender name="ASYNC_APPENDER" class="ch.qos.logback.classic.AsyncAppender">
    28   <appender-ref ref="DB_APPENDER" />
    29   <includeCallerData>true</includeCallerData>
    30 </appender>
    31 
    32         <!-- 日志输出级别 -->
    33 <root level="${LOG_LEVEL}">
    34   <appender-ref ref="ASYNC_APPENDER" />
    35 </root>

    经配置,其需要建三张数据表,分别为日志信息、异常信息、属性信息,其建表语句如下:

     1 BEGIN;
     2 DROP TABLE IF EXISTS logging_event_property;
     3 DROP TABLE IF EXISTS logging_event_exception;
     4 DROP TABLE IF EXISTS logging_event;
     5 COMMIT;
     6 
     7 
     8 BEGIN;
     9 CREATE TABLE logging_event 
    10   (
    11     timestmp         BIGINT NOT NULL,
    12     formatted_message  TEXT NOT NULL,
    13     logger_name       VARCHAR(254) NOT NULL,
    14     level_string      VARCHAR(254) NOT NULL,
    15     thread_name       VARCHAR(254),
    16     reference_flag    SMALLINT,
    17     arg0              VARCHAR(254),
    18     arg1              VARCHAR(254),
    19     arg2              VARCHAR(254),
    20     arg3              VARCHAR(254),
    21     caller_filename   VARCHAR(254) NOT NULL,
    22     caller_class      VARCHAR(254) NOT NULL,
    23     caller_method     VARCHAR(254) NOT NULL,
    24     caller_line       CHAR(4) NOT NULL,
    25     event_id          BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
    26   );
    27 COMMIT;
    28 
    29 BEGIN;
    30 CREATE TABLE logging_event_property
    31   (
    32     event_id          BIGINT NOT NULL,
    33     mapped_key        VARCHAR(254) NOT NULL,
    34     mapped_value      TEXT,
    35     PRIMARY KEY(event_id, mapped_key),
    36     FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
    37   );
    38 COMMIT;
    39 
    40 BEGIN;
    41 CREATE TABLE logging_event_exception
    42   (
    43     event_id         BIGINT NOT NULL,
    44     i                SMALLINT NOT NULL,
    45     trace_line       VARCHAR(254) NOT NULL,
    46     PRIMARY KEY(event_id, i),
    47     FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
    48   );
    49 COMMIT;

    二、自定义输出日志

    由于Logback原生要求建三张表,如何指定指输出一种信息,及自定义日志内容,而异常、属性信息不输出?

    通过查看DBAppender发现,插入数据方法,此处只需重写DBAppender,即继承DBAppenderBase<ILoggingEvent>,删除掉异常、属性信息插入的相关方法即可实现只输出指定日志到指定表,而其它信息将不会输出到数据库中,代码如下:

      1 public class LogDBAppender extends DBAppenderBase<ILoggingEvent> {
      2 
      3     protected String insertSQL;
      4     protected static final Method GET_GENERATED_KEYS_METHOD;
      5 
      6     private DBNameResolver dbNameResolver;
      7 
      8     static final int TIMESTMP_INDEX = 1;
      9     static final int FORMATTED_MESSAGE_INDEX = 2;
     10     static final int LOGGER_NAME_INDEX = 3;
     11     static final int LEVEL_STRING_INDEX = 4;
     12     static final int THREAD_NAME_INDEX = 5;
     13     static final int REFERENCE_FLAG_INDEX = 6;
     14     static final int ARG0_INDEX = 7;
     15     static final int ARG1_INDEX = 8;
     16     static final int ARG2_INDEX = 9;
     17     static final int ARG3_INDEX = 10;
     18     static final int CALLER_FILENAME_INDEX = 11;
     19     static final int CALLER_CLASS_INDEX = 12;
     20     static final int CALLER_METHOD_INDEX = 13;
     21     static final int CALLER_LINE_INDEX = 14;
     22     static final int EVENT_ID_INDEX = 15;
     23 
     24     static final StackTraceElement EMPTY_CALLER_DATA = CallerData.naInstance();
     25 
     26     static {
     27         // PreparedStatement.getGeneratedKeys() method was added in JDK 1.4
     28         Method getGeneratedKeysMethod;
     29         try {
     30             // the
     31             getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);
     32         } catch (Exception ex) {
     33             getGeneratedKeysMethod = null;
     34         }
     35         GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
     36     }
     37 
     38     public void setDbNameResolver(DBNameResolver dbNameResolver) {
     39         this.dbNameResolver = dbNameResolver;
     40     }
     41 
     42     @Override
     43     public void start() {
     44         if (dbNameResolver == null)
     45             dbNameResolver = new DefaultDBNameResolver();
     46         insertSQL = buildInsertSQL(dbNameResolver);
     47         super.start();
     48     }
     49 
     50     @Override
     51     protected void subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement) throws Throwable {
     52 
     53         bindLoggingEventWithInsertStatement(insertStatement, event);
     54         bindLoggingEventArgumentsWithPreparedStatement(insertStatement, event.getArgumentArray());
     55 
     56         // This is expensive... should we do it every time?
     57         bindCallerDataWithPreparedStatement(insertStatement, event.getCallerData());
     58 
     59         int updateCount = insertStatement.executeUpdate();
     60         if (updateCount != 1) {
     61             addWarn("Failed to insert loggingEvent");
     62         }
     63     }
     64 
     65     protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId) throws Throwable {
     66         Map<String, String> mergedMap = mergePropertyMaps(event);
     67         //insertProperties(mergedMap, connection, eventId);
     68 
     69 //        if (event.getThrowableProxy() != null) {
     70 //            insertThrowable(event.getThrowableProxy(), connection, eventId);
     71 //        }
     72     }
     73 
     74     void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
     75         stmt.setLong(TIMESTMP_INDEX, event.getTimeStamp());
     76         stmt.setString(FORMATTED_MESSAGE_INDEX, event.getFormattedMessage());
     77         stmt.setString(LOGGER_NAME_INDEX, event.getLoggerName());
     78         stmt.setString(LEVEL_STRING_INDEX, event.getLevel().toString());
     79         stmt.setString(THREAD_NAME_INDEX, event.getThreadName());
     80         stmt.setShort(REFERENCE_FLAG_INDEX, DBHelper.computeReferenceMask(event));
     81     }
     82 
     83     void bindLoggingEventArgumentsWithPreparedStatement(PreparedStatement stmt, Object[] argArray) throws SQLException {
     84 
     85         int arrayLen = argArray != null ? argArray.length : 0;
     86 
     87         for (int i = 0; i < arrayLen && i < 4; i++) {
     88             stmt.setString(ARG0_INDEX + i, asStringTruncatedTo254(argArray[i]));
     89         }
     90         if (arrayLen < 4) {
     91             for (int i = arrayLen; i < 4; i++) {
     92                 stmt.setString(ARG0_INDEX + i, null);
     93             }
     94         }
     95     }
     96 
     97     String asStringTruncatedTo254(Object o) {
     98         String s = null;
     99         if (o != null) {
    100             s = o.toString();
    101         }
    102 
    103         if (s == null) {
    104             return null;
    105         }
    106         if (s.length() <= 254) {
    107             return s;
    108         } else {
    109             return s.substring(0, 254);
    110         }
    111     }
    112 
    113     void bindCallerDataWithPreparedStatement(PreparedStatement stmt, StackTraceElement[] callerDataArray) throws SQLException {
    114 
    115         StackTraceElement caller = extractFirstCaller(callerDataArray);
    116 
    117         stmt.setString(CALLER_FILENAME_INDEX, caller.getFileName());
    118         stmt.setString(CALLER_CLASS_INDEX, caller.getClassName());
    119         stmt.setString(CALLER_METHOD_INDEX, caller.getMethodName());
    120         stmt.setString(CALLER_LINE_INDEX, Integer.toString(caller.getLineNumber()));
    121     }
    122 
    123     private StackTraceElement extractFirstCaller(StackTraceElement[] callerDataArray) {
    124         StackTraceElement caller = EMPTY_CALLER_DATA;
    125         if (hasAtLeastOneNonNullElement(callerDataArray))
    126             caller = callerDataArray[0];
    127         return caller;
    128     }
    129 
    130     private boolean hasAtLeastOneNonNullElement(StackTraceElement[] callerDataArray) {
    131         return callerDataArray != null && callerDataArray.length > 0 && callerDataArray[0] != null;
    132     }
    133 
    134     Map<String, String> mergePropertyMaps(ILoggingEvent event) {
    135         Map<String, String> mergedMap = new HashMap<String, String>();
    136         // we add the context properties first, then the event properties, since
    137         // we consider that event-specific properties should have priority over
    138         // context-wide properties.
    139         Map<String, String> loggerContextMap = event.getLoggerContextVO().getPropertyMap();
    140         Map<String, String> mdcMap = event.getMDCPropertyMap();
    141         if (loggerContextMap != null) {
    142             mergedMap.putAll(loggerContextMap);
    143         }
    144         if (mdcMap != null) {
    145             mergedMap.putAll(mdcMap);
    146         }
    147 
    148         return mergedMap;
    149     }
    150 
    151     @Override
    152     protected Method getGeneratedKeysMethod() {
    153         return GET_GENERATED_KEYS_METHOD;
    154     }
    155 
    156     @Override
    157     protected String getInsertSQL() {
    158         return insertSQL;
    159     }
    160 
    161 
    162     static String buildInsertSQL(DBNameResolver dbNameResolver) {
    163         StringBuilder sqlBuilder = new StringBuilder("INSERT INTO ");
    164         sqlBuilder.append(dbNameResolver.getTableName(TableName.LOGGING_EVENT)).append(" (");
    165         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.TIMESTMP)).append(", ");
    166         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.FORMATTED_MESSAGE)).append(", ");
    167         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.LOGGER_NAME)).append(", ");
    168         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.LEVEL_STRING)).append(", ");
    169         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.THREAD_NAME)).append(", ");
    170         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.REFERENCE_FLAG)).append(", ");
    171         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG0)).append(", ");
    172         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG1)).append(", ");
    173         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG2)).append(", ");
    174         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG3)).append(", ");
    175         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_FILENAME)).append(", ");
    176         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_CLASS)).append(", ");
    177         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_METHOD)).append(", ");
    178         sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_LINE)).append(") ");
    179         sqlBuilder.append("VALUES (?, ?, ? ,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
    180         return sqlBuilder.toString();
    181     }
    182 }

    现在只需将配置中引用的DBAppender:

    <appender name="DB_APPENDER" class="ch.qos.logback.classic.db.DBAppender">

    更改为自己重写的类:

    <appender name="DB_APPENDER" class="com.cenobitor.logging.db.LogDBAppender">

    表logging_event_property、表logging_event_exception将可删除,至此基本的配置已完成,可以畅快的使用了。

    三、使用

    log.info(MarkerFactory.getMarker("DB"), "logback!");
    即可异步将该日志输出到数据库中。 
  • 相关阅读:
    bootstrap如何设置每一个选项卡对应一个页面
    SpringBoot集成百度UEditor图片上传后直接访问404解决办法
    从0开始完成SpringBoot+Mybatis实现增删改查
    Luarocks 安装艰难过程
    [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain (_ssl.c:1076)')))
    python + selenium 搭建环境步骤
    mongodb的安装
    git-新建git用户流程-1
    git-关联远程git仓库详细步骤-2
    Django启动服务的流程
  • 原文地址:https://www.cnblogs.com/gdwkong/p/9251471.html
Copyright © 2011-2022 走看看