去掉Log4j,如何轉換成logback

修復漏洞,去掉Log4j,引入logback

1 背景

由於log4j版本低於1.2.17的有安全漏洞問題,如果直接升級log4j到2.X版本存在困難點。因此本方案直接將log4j替換成logback,理由見附錄。

如果只是使用組件進行業務系統的集成,可以只看4 如何使用一節

2 困難點

  1. log4j-1.2.17升級到log4j-2.x版本的問題

    問題:(1)基本的Logger類的路徑變了,影響全局的業務類。
    方案:需要新建Logger類橋接

    問題:(2)配置文件不能複用,需要按照新規則重新配置
    方案:按照新的規則重新配置,且資料較少,需要學習成本

    問題:(3)組件jar和三方jar引用的低版本log4j,直接升級三方jar啓動會報錯
    方案:需要保留低版本的log4j,可能會和高版本的log4j衝突,因此該問題比較麻煩

    結論:基於上述3個問題,將log4j直接轉換成主流日誌框架logback。

  2. 升級成logback存在的問題

    問題:(1)考慮到儘可能的不影響業務,新建一個Logger類橋接,但存在的問題是,日誌輸出打印的類、方法、行號均是Logger類的相關信息,不能體現是具體哪個業務類打印出來的。
    方案:既然logback支持pattern配置日誌的格式,那內部是有地方取類、方法名、行號的,重寫對應的邏輯即可。

    問題:(2)與jboss有衝突問題,具體請轉至第5節

3 組件介紹

考慮到對業務系統侵入小,儘量讓業務上少改動,並且其他項目組有類似的應用場景,考慮複用,因此以組件方式進行開發。

3.1 代碼結構

bdc-component-log4j2logback
│  pom.xml
└─src
    ├─main
    │  ├─java
    │  │  ├─ch
    │  │  │  └─qos
    │  │  │      └─logback
    │  │  │          └─classic
    │  │  │              └─pattern
    │  │  │                      ClassOfCallerConverter.java
    │  │  │                      LineOfCallerConverter.java
    │  │  │                      MethodOfCallerConverter.java
    │  │  ├─com
    │  │  │  └─xxxx
    │  │  │      └─bdc
    │  │  │          └─component
    │  │  │              └─log4j2logback
    │  │  │                  │  LogbackConfigLoader.java
    │  │  │                  ├─bridge
    │  │  │                  │      JdbcLogger.java
    │  │  │                  └─listener
    │  │  │                          AbstractLogbackInitListener.java
    │  │  │                          DefaultLogbackInitListener.java
    │  │  └─org
    │  │      ├─apache
    │  │      │  └─log4j
    │  │      │          Logger.java
    │  │      └─slf4j
    │  │          └─override
    │  │                  LoggerFactory.java
    │  └─resources
    │      ├─lib
    │      │      log4j-1.2.17.jar
    │      │      logback-classic-1.2.3.jar
    │      │      logback-core-1.2.3.jar
    │      │      slf4j-api-1.6.0.jar
    │      └─sample
    │              logback.xml
    └─test
        └─java

3.2 關鍵類說明

3.2.1 ClassOfCallerConverter.java

功能說明:logback.xml中pattern將業務類的全路徑名打印出來,重新實現該類的邏輯可以打印出真實的業務類,而不是Logger這個類。如果不重新實現該類,打印出來的業務類都是Logger,體現不出來日誌輸出的真實的業務類。
核心代碼:

    @Override
    protected String getFullyQualifiedName(ILoggingEvent event) {

        StackTraceElement[] cda = event.getCallerData();
        if (cda != null) {
            for (int i = 0; i < cda.length; i++) {
                StackTraceElement element = cda[i];
                if (element != null) {
                    String className = element.getClassName();
                    if ("org.apache.log4j.Logger".equals(className)) {
                        // 如果是新建的橋接類,直接取下一個className,這樣才能真實的獲取到打印日誌的業務類
                        continue;
                    }
                    return className;
                }

            }
        }
        return CallerData.NA;
    }

3.2.2 MethodOfCallerConverter.java

功能說明:logback.xml中pattern將業務類的方法名打印出來,重新實現該類的邏輯可以打印出真實的業務類的方法名,而不是Logger這個類的方法。如果不重新實現該類,打印出來的方法名都是Logger類中方法,體現不出來日誌輸出的真實的業務類的方法名。
核心代碼:

    @Override
    public String convert(ILoggingEvent le) {
        StackTraceElement[] cda = le.getCallerData();
        if (cda != null) {
            for (int i = 0; i < cda.length; i++) {
                StackTraceElement element = cda[i];
                if (element != null) {
                    String className = element.getClassName();
                    if ("org.apache.log4j.Logger".equals(className)) {
                        // 如果是新建的橋接類,直接取下一個類的方法名,這樣才能真實的獲取到打印日誌的業務類的方法名
                        continue;
                    }
                    return element.getMethodName();
                }

            }
        }
        return CallerData.NA;
    }

3.2.3 LineOfCallerConverter.java

功能說明:logback.xml中pattern將業務類的具體行號打印出來,重新實現該類的邏輯可以打印出真實的業務類的具體行號,而不是Logger這個類的行號。如果不重新實現該類,打印出來的行號都是Logger類中打印日誌的行號,體現不出來日誌輸出的真實的業務類的具體行號。
核心代碼:

    @Override
    public String convert(ILoggingEvent le) {
        StackTraceElement[] cda = le.getCallerData();
        if (cda != null) {
            for (int i = 0; i < cda.length; i++) {
                StackTraceElement element = cda[i];
                if (element != null) {
                    String className = element.getClassName();
                    if ("org.apache.log4j.Logger".equals(className)) {
                        // 如果是新建的橋接類,直接取下一個類的方法名,這樣才能真實的獲取到打印日誌的業務類的方法名
                        continue;
                    }
                    return Integer.toString(element.getLineNumber());
                }

            }
        }
        return CallerData.NA;
    }

3.2.4 LogbackConfigLoader.java

功能說明:手動的加載logback.xml配置文件,爲日誌打印做好前提準備。
核心代碼:

public static void load(ILoggerFactory factory,String externalConfigFileLocation) throws IOException, JoranException {
        LoggerContext lc = (LoggerContext) factory;

        File externalConfigFile = new File(externalConfigFileLocation);
        if (!externalConfigFile.exists()) {
            throw new IOException("Logback External Config File Parameter does not reference a file that exists");
        } else {
            if (!externalConfigFile.isFile()) {
                throw new IOException("Logback External Config File Parameter exists, but does not reference a file");
            } else {
                if (!externalConfigFile.canRead()) {
                    throw new IOException("Logback External Config File exists and is a file, but cannot be read.");
                } else {
                    JoranConfigurator configurator = new JoranConfigurator();
                    configurator.setContext(lc);
                    lc.reset();
                    configurator.doConfigure(externalConfigFileLocation);
                    StatusPrinter.printInCaseOfErrorsOrWarnings(lc);
                }
            }
        }
    }

3.2.5 AbstractLogbackInitListener.java

功能說明:該類爲抽象類,不可直接實例化,該類調用LogbackConfigLoader類來加載logback.xml配置,logback.xml配置文件所在的路徑通過抽象方法getLogbackAbsolutePath()獲取,該抽象方法在具體的業務系統要集成的時候寫一個類來實現這個抽象類,將自己系統中的配置文件所在的路徑返回即可。
核心代碼:

public abstract class AbstractLogbackInitListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        // 獲得配置文件
        String configFile = getLogbackAbsolutePath();
        System.out.println("獲得日誌配置文件爲:" + configFile);
        try {
            LogbackConfigLoader.load(getLoggerContext(), configFile);
        } catch (IOException e) {
            System.err.println("加載logback配置文件失敗");
            e.printStackTrace();
        } catch (JoranException e) {
            System.err.println("加載logback配置文件失敗");
            e.printStackTrace();
        }
        // 加載XML配置文件(每半小時掃描一次配置文件,如果有更新,自動加載)
        System.out.println("初始化日誌組件成功");
    }

    /**
     * 獲取logback.xml的絕對路徑
     *
     * @return logback.xml所在的絕對路徑,包括文件名稱
     */
    public abstract String getLogbackAbsolutePath();

    /**
     * 返回ILoggerFactory的實現類LoggerContext
     * <p>
     * 有兩種方式:
     * 方式一:org.slf4j.LoggerFactory.getILoggerFactory()
     * 方式二:org.slf4j.override.LoggerFactory.getILoggerFactory()
     * <p>
     * 區別:如果不修改webapp/WEB-INF/jboss-deployment-structure.xml,前者和jboss有衝突
     * 建議:使用方式二
     *
     * @return ILoggerFactory的實現類,LoggerContext
     */
    public ILoggerFactory getLoggerContext() {
        return LoggerFactory.getILoggerFactory();
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
}

3.2.6 DefaultLogbackInitListener.java

功能說明:該類是提供的一個AbstractLogbackInitListener抽象類的默認繼承類,不具有實際意義(不同系統路徑不同),僅提供參考。
核心代碼:

public class DefaultLogbackInitListener extends AbstractLogbackInitListener {
    @Override
    public String getLogbackAbsolutePath() {
        // 日誌配置路徑
        String logPath = "config/uspc/log";
        // 日誌配置文件
        String logFile = "logback.xml";
        // 獲得配置文件
        StringBuilder logbackFile = new StringBuilder(System.getProperty("user.dir"));
        logbackFile = logbackFile.append(File.separator).append(logPath).append(File.separator).append(logFile);
        return logbackFile.toString();
    }

}

3.2.7 Logger.java

功能說明:業務系統中原來使用的log4j,使用的Logger的路徑正好是org.apache.log4j.Logger,爲了不改動業務代碼,因此新建一個同路徑同名類來做橋接,這裏面的日誌打印方法均是調用的logback日誌框架的方法,因此日誌輸出也會按照logback.xml的配置進行輸出。

注意:可能這裏會有疑問,log4j包有個org.apache.log4j.Logger類,該組件包也有一個org.apache.log4j.Logger類,會不會衝突呢,這是需要了解JVM類的加載機制。一般情況下,不同jar包,相同類(路徑和類名都相同)的加載順序是按照jar包的字母排序加載的,比如該組件命名爲bdc-component-log4j2logback-1.0.0.RELEASE.jar,默認的log4j的包爲log4j-1.2.17.jar,按照字母排序,組件jar包中的Logger先加載,而log4j中的Logger類將不會被JVM加載,因此不會衝突。可通過增加-XX:+TraceClassLoading JVM參數來觀察類的加載順序。

核心代碼:

package org.apache.log4j;

import com.xxxx.bdc.component.log4j2logback.bridge.JdbcLogger;
import org.slf4j.Marker;
import org.slf4j.override.LoggerFactory;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author [email protected]
 * @project BDC
 * @package org.apache.log4j
 * @date 2020/6/16 11:11
 **/
public class Logger implements org.slf4j.Logger, JdbcLogger {
    private String name;
    private static org.slf4j.Logger logger;
    private static final Logger self = new Logger();
    public static Logger getLogger(Class cls) {
        // 特別注意:
        // 這裏必須用org.slf4j.override.LoggerFactory類來獲得Logger,
        // 否則通過org.slf4j.LoggerFactory來獲取,會取到jboss對org.slf4j.Logger接口的實現類
        // slf4j-jboss-logmanager-1.0.2.GA-redhat-1.jar\org\slf4j\impl\Slf4jLogger.class,導致日誌不能按照logback.xml輸出
        // 如果服務容器不是jboss,使用兩者都可以
        // 如果從jboss層面或者jboss-deployment-structure.xml中排除了對slf4j的依賴,使用兩者都可以
        // 業務系統中使用哪個LoggerFactory也需要按照如上要求來
        logger = LoggerFactory.getILoggerFactory().getLogger(getClassName());
        self.name = cls.getName();
        return self;
    }

    public static Logger getLogger(String name) {
        self.name = name;
        return self;
    }

    private static String getClassName() {
        return new SecurityManager() {
            public String getClassName() {
                return getClassContext()[3].getName();
            }
        }.getClassName();
    }

3.2.8 logback.xml

功能說明:日誌輸出的核心配置,組件提供默認的配置
核心代碼:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--此處的logRootDir的值是tomcat/bin或者jboss/bin目錄下-->
    <property name="logRootDir" value="log"/>
    <property name="businessModule" value="uspc"/>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoder 默認配置爲PatternLayoutEncoder -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%level] [%class.%method:%line]- %m %n</pattern>
        </encoder>
    </appender>
    <appender name="SQL_DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logRootDir}/${businessModule}/sql/debug.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- Rollover everyday. If file exceeds 1GB within a day, then file is archived with index starting from 0 -->
            <fileNamePattern>${logRootDir}/${businessModule}/sql/debug.log-%d{yyyyMMdd}-%i</fileNamePattern>
            <!-- Each file should be at most 50MB -->
            <maxFileSize>50MB</maxFileSize>
            <!-- Keep maximum 30 days worth of archive files, deleting older ones -->
            <maxHistory>30</maxHistory>
            <!-- Total size of all archived files is at most 5GB -->
            <totalSizeCap>5GB</totalSizeCap>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%level] [%class.%method:%line]- %m %n</pattern>
            <!--配置僞true,實時輸出日誌到文件中,如果需要提升性能,將其改爲false,啓用緩衝區-->
            <immediateFlush>true</immediateFlush>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    ....省略
  </configuration>

3.2.9 log4j-1.2.17.jar

功能說明:按照目前的瞭解,日誌輸出雖然是採用的logback日誌框架輸出,但log4j的包還不能移除,有些三方jar比如freemarker、velocity等依賴了低版本的log4j,移除後會報錯,如果一定要移除,需要自己單獨新增缺少的類來橋接,很麻煩,因此建議不移除。

3.2.10 logback-core-1.2.3.jar、logback-classic-1.2.3.jar

功能說明:日誌框架,日誌輸出的核心代碼實現均在這兩個類

4 如何使用

4.1 業務系統集成

將日誌組件集成到業務系統侵入比較小,主要有3處改動的地方

4.1.1 組件jar依賴

需要將組件jar放到項目的lib依賴庫中

4.1.2 增加一個類繼承AbstractLogbackInitListener

業務系統的logback.xml在哪裏,由具體的業務系統決定,因此要通過組件加載到logback.xml,就需要集成AbstractLogbackInitListener抽象類,實現抽象方法

/**
     * 獲取logback.xml的絕對路徑
     *
     * @return logback.xml所在的絕對路徑,包括文件名稱
     */
    public abstract String getLogbackAbsolutePath();

方法返回值爲logback.xml的絕對路徑

4.1.3 在web.xml中增加監聽器配置

AbstractLogbackInitListener抽象類實現了ServletContextListener接口,因此在web.xml中增加<listener></listener>的配置,可以在項目啓動的時候就通過組件來講logback.xml加載並初始化,便於打印出啓動及以後的日誌。listener的加載順序是按照listener的配置順序加載,因此將該類配置在第一個位置首先加載。這裏假設新建的類爲LogbackInitListener,樣例配置如下

    <listener>
        <listener-class>com.xxxx.sims.rtplt.component.listener.LogbackInitListener</listener-class>
    </listener>

另外,如果沒有web.xml,是springboot工程,更加的靈活,只要在項目啓動的時候主動調用下contextInitialized方法即可。

4.1.4 修改jboss-deployment-structure.xml(可選)

修改配置文件webapp/WEB-INF/jboss-deployment-structure.xml
增加:
<module name="org.slf4j.impl"/>
<module name="org.slf4j"/>
<module name="org.jboss.logmanager"/>
到配置文件中
目的:如果項目是通過jboss部署,會和jboss自身依賴的slf4j衝突(與tomcat無衝突),增加改配置可防止衝突,此步驟可選,因爲已經從組件上來解決了與jboss的衝突。但如果系統中有主動使用org.slf4j.LoggerFactory類來獲取日誌打印類,該配置必須,是否會獲取到jboss對org.slf4j.Logger的實現(slf4j-jboss-logmanager-1.0.2.GA-redhat-1.jar\org\slf4j\impl\Slf4jLogger.class),導致打印不出日誌。

<?xml version='1.0' encoding='UTF-8'?>
<jboss-deployment-structure>
    <deployment>
        <!-- Exclusions allow you to prevent the server from automatically adding some dependencies -->
        <exclusions>
            <module name="org.slf4j.impl"/>
            <module name="org.slf4j"/>
            <module name="org.jboss.logmanager"/>
            <module name="org.apache.log4j"/>
            <module name="org.apache.commons.logging"/>
        </exclusions>
    </deployment>
</jboss-deployment-structure>

4.2 測試

項目啓動便可測試,看日誌是否按照logback.xml的配置進行輸出。

5 特別注意

5.1 集成問題

5.1.1 類型強制轉換失敗問題(已修復)

在前期未發現與jboss有衝突的時候,集成了該jar包之後,war包在jboss中啓動會報錯:java.lang.ClassCastException: org.slf4j.impl.Slf4jLoggerFactory cannot be cast to ch.qos.logback.classic.LoggerContext
原因:org.slf4j.LoggerFactory.getILoggerFactory()獲取到的接口實現類爲jboss中的實現類,因爲jboss對slf4j相關的實現類先加載,因此在此時調用LoggerFactory.getILoggerFactory()獲取到的是jboss的實現,強轉成LoggerContext會報錯,而logback對slf4j的實現通過LoggerFactory.getILoggerFactory()獲取的正是LoggerContext類。

方案一: 從jboss本身入手,排除掉slf4j相關jar的依賴,可能存在潛在問題,畢竟動了jboss
優點:可徹底解決業務系統和jboss的slf4j的任何衝突問題
弊端:可能會影響jboss

  1. 去掉slf4j-api-1.7.2-redhat-1.jar的依賴,註釋掉如下依賴
    /opt/xxxx/product/bdc/eapjboss/modules/system/layers/base/org/slf4j/main/slf4j-api-1.7.2-redhat-1.jar
<module xmlns="urn:jboss:module:1.1" name="org.slf4j">
    <resources>
        <!--<resource-root path="slf4j-api-1.7.2-redhat-1.jar"/>-->
        <!-- Insert resources here -->
    </resources>

    <dependencies>
        <module name="org.slf4j.impl"/>
    </dependencies>
</module>
  1. 去掉slf4j-jboss-logmanager-1.0.2.GA-redhat-1.jar的依賴,註釋掉如下配置
    /opt/xxxx/product/bdc/eapjboss/modules/system/layers/base/org/slf4j/impl/main/slf4j-jboss-logmanager-1.0.2.GA-redhat-1.jar
<module xmlns="urn:jboss:module:1.1" name="org.slf4j.impl">

    <properties>
        <property name="jboss.api" value="private"/>
    </properties>

    <resources>
        <!--<resource-root path="slf4j-jboss-logmanager-1.0.2.GA-redhat-1.jar"/>-->
        <!-- Insert resources here -->
    </resources>

    <dependencies>
        <module name="org.slf4j"/>
        <module name="org.jboss.logmanager"/>
    </dependencies>
</module>

方案二: 從應用着手解決衝突
修改webapp\WEB-INF\jboss-deployment-structure.xml
優點:也可以有效解決與jboss的衝突
弊端:每個業務系統都將增加該配置,如果不是傳統的war服務,暫時可能沒有該配置。
增加依賴排除配置:
<module name="org.slf4j.impl"/>
<module name="org.slf4j"/>
<module name="org.jboss.logmanager"/>

<?xml version='1.0' encoding='UTF-8'?>
<jboss-deployment-structure>    
    <deployment>        
    <!-- Exclusions allow you to prevent the server from automatically adding some dependencies -->        
        <exclusions>            
            <module name="org.slf4j.impl"/>            
            <module name="org.slf4j"/>            
            <module name="org.jboss.logmanager"/>            
            <module name="org.apache.log4j" />             
            <module name="org.apache.commons.logging" />        
        </exclusions>    
    </deployment>
</jboss-deployment-structure>

5.1.2 日誌不打印問題(已修復)

在修復前,橋接類Logger用的是org.slf4j.LoggerFactory.getLogger(),這樣獲得的是jboss對org.slf4j.Logger的實現(slf4j-jboss-logmanager-1.0.2.GA-redhat-1.jar\org\slf4j\impl\Slf4jLogger.class),那就不能按照logback.xml的配置進行日誌輸出,修復後是通過重寫了LoggerFactory類,改了包路徑,目的是爲了橋接類裏面能得到logback對slf4j的實現。

5.2 如何使用LoggerFactory(重要)

  1. 如果容器用的jboss,webapp/WEB-INF/jboss-deployment-structure.xml配置排除了slf4j的依賴,那麼隨便用org.slf4j.LoggerFactory或者org.slf4j.override.LoggerFactory(組件重新實現)都可以。

  2. 如果容器是jboss,但webapp/WEB-INF/jboss-deployment-structure.xml未排除slf4j的依賴,那使用org.slf4j.override.LoggerFactory纔會打印出日誌,用org.slf4j.LoggerFactory不會打印出日誌

  3. 如果容器不是jboss,則沒有限制,使用兩者都可以。

6 附錄:logback取代log4j的理由

  1. 更快的實現:Logback的內核重寫了,在一些關鍵執行路徑上性能提升10倍以上。而且logback不僅性能提升了,初始化內存加載也更小了。
  2. 非常充分的測試:Logback經過了幾年,數不清小時的測試。Logback的測試完全不同級別的。
  3. logback-classic非常自然實現了SLF4j:Logback-classic實現了SLF4j。在使用SLF4j中,你都感覺不到logback-classic。而且因爲logback-classic非常自然地實現了slf4j , 所 以切換到log4j或者其他,非常容易,只需要提供成另一個jar包就OK,根本不需要去動那些通過SLF4JAPI實現的代碼。
  4. 非常充分的文檔 官方網站有兩百多頁的文檔。
  5. 自動重新加載配置文件,當配置文件修改了,Logback-classic能自動重新加載配置文件。掃描過程快且安全,它並不需要另外創建一個掃描線程。這個技術充分保證了應用程序能跑得很歡在JEE環境裏面。
  6. Lilith是log事件的觀察者,和log4j的chainsaw類似。而lilith還能處理大數量的log數據 。
  7. 謹慎的模式和非常友好的恢復,在謹慎模式下,多個FileAppender實例跑在多個JVM下,能 夠安全地寫道同一個日誌文件。RollingFileAppender會有些限制。Logback的FileAppender和它的子類包括 RollingFileAppender能夠非常友好地從I/O異常中恢復。
  8. 配置文件可以處理不同的情況,開發人員經常需要判斷不同的Logback配置文件在不同的環境下(開發,測試,生產)。而這些配置文件僅僅只有一些很小的不同,可以通過,和來實現,這樣一個配置文件就可以適應多個環境。
  9. Filters(過濾器)有些時候,需要診斷一個問題,需要打出日誌。在log4j,只有降低日誌級別,不過這樣會打出大量的日誌,會影響應用性能。在Logback,你可以繼續 保持那個日誌級別而除掉某種特殊情況,如alice這個用戶登錄,她的日誌將打在DEBUG級別而其他用戶可以繼續打在WARN級別。要實現這個功能只需加4行XML配置。可以參考MDCFIlter 。
  10. SiftingAppender(一個非常多功能的Appender):它可以用來分割日誌文件根據任何一個給定的運行參數。如,SiftingAppender能夠區別日誌事件跟進用戶的Session,然後每個用戶會有一個日誌文件。  11. 自動壓縮已經打出來的log:RollingFileAppender在產生新文件的時候,會自動壓縮已經打出來的日誌文件。壓縮是個異步過程,所以甚至對於大的日誌文件,在壓縮過程中應用不會受任何影響。
  11. 堆棧樹帶有包版本:Logback在打出堆棧樹日誌時,會帶上包的數據。
  12. 自動去除舊的日誌文件:通過設置TimeBasedRollingPolicy或者SizeAndTimeBasedFNATP的maxHistory屬性,你可以控制已經產生日誌文件的最大數量。如果設置maxHistory 12,那那些log文件超過12個月的都會被自動移除。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章