Logback入門教程學習筆記

1 概述

Logback是在java社區廣泛使用的日誌框架之一,它是Log4j的替代品。Logback提供了比Log4j更快的實現,更多可選擇的配置,以及歸檔log文件更大的靈活性。

2 Logback的架構

包括三個類:Logger,Appender和Layout

  • Logger, 是log信息的上下文,是跟應用交互以生成log信息之用;
  • Appender,負責日誌輸出的組件;
  • Layout,用來格式化日誌信息等。

3 例子

這裏創建一個springboot工程,添加以下依賴

3.1 Maven依賴

	<dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.30</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>1.2.3</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>

3.2 基本的配置和代碼demo

在resources下面創建logback.xml文件,添加以下配置:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="debug">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

創建一個Example:

@Slf4j
public class Example {
    public static void main(String[] args) {
        log.info("Example log from {}", Example.class.getSimpleName());
    }
}

運行得到以下結果:
09:59:36.226 [main] INFO com.logback.demo.Example - Example log from Example。

4 Logger context

@Slf4j是來自lombok的註解,相當於如下代碼:

private static final Logger log = LoggerFactory.getLogger(Example.class);

可以從編譯後的class文件,反編譯後看到。

4.1 logger繼承結構

Logger context的繼承類似於java的繼承。

  • 所有的logger都是root logger的後代;
  • 如果一個logger沒有指定level,那麼它的level將繼承上一個祖先logger.
  • root logger的默認level是DEBUG。

注意:日誌級別分爲5種,從小到大依次是TRACE, DEBUG, INFO, WARN 和ERROR。低於指定級別的日誌將不打印,例如我們指定日誌級別是INFO,那麼低於INFO的TRACE和DEBUG都不打印。

上代碼:

public class LoggerHierarchyExample {

    @Test
    public void testLoggerHierarchy() {
        // 祖先logger,未指定級別,它將繼承root logger的級別,root logger默認debug
        // SLF4J's的抽象logger是沒有實現setLevel方法,這裏是用ch.qos.logback.classic.Logger
        Logger ancestorLogger = (Logger) LoggerFactory.getLogger("com");
        // 父logger,指定級別爲info,不繼承祖先logger的級別
        Logger parentLogger = (Logger) LoggerFactory.getLogger("com.logback");
        parentLogger.setLevel(Level.INFO);
        // 子logger,未指定級別,繼承最近的父logger的日誌級別,爲info。
        Logger childLogger = (Logger) LoggerFactory.getLogger("com.logback.demo");

        ancestorLogger.debug("This message is logged because debug == debug");
        ancestorLogger.trace("This message is not logged because trace < debug");
        parentLogger.warn("This message is logged because WARN > INFO.");
        parentLogger.debug("This message is not logged because DEBUG < INFO.");
        childLogger.info("INFO == INFO");
        childLogger.debug("DEBUG < INFO");
        
    }


    @Test
    public void testRootLogger() {
        // 沒有指定level,繼承root logger的level爲debug
        Logger logger = (Logger) LoggerFactory.getLogger("com.logback");
        logger.debug("Hi there!");

        // root logger,重新設定level爲error
        Logger rootLogger = (Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
        logger.debug("This message is logged because DEBUG == DEBUG.");
        rootLogger.setLevel(Level.ERROR);

        logger.warn("This message is not logged because WARN < ERROR.");
        logger.error("This is logged.");
    }
}

testLoggerHierarchy()結果爲:

10:49:16.713 [main] DEBUG com - This message is logged because debug == debug
10:49:16.715 [main] WARN  com.logback - This message is logged because WARN > INFO.
10:49:16.715 [main] INFO  com.logback.demo - INFO == INFO

testRootLogger() 結果爲:

10:50:06.087 [main] DEBUG com.logback - Hi there!
10:50:06.089 [main] DEBUG com.logback - This message is logged because DEBUG == DEBUG.
10:50:06.089 [main] ERROR com.logback - This is logged.

4.2 參數化打印日誌

實際編碼中,看到很多如下日誌打印方式:

log.debug("Current count is " + count);

這種方式的弊端:即使日誌打印level設定爲info,都會執行字符串的拼接,無疑會白白的損耗性能。

以下方式也不可取,雖然不執行拼接,但仍然做了一次判斷。

if(log.isDebugEnabled()) { 
  log.debug("Current count is " + count);
}

更好的方式是使用佔位符的方式 , 避免字符串拼接,避免日誌level的判斷。

log.debug("Current count is {}" , count);

佔位符{}允許接收任何object,並且調用其toString方法來記錄日誌。

@Slf4j
public class ParameterizedExample {

    @Test
    public void testParameterizedExample() {
        String message = "This is a String";
        Integer zero = 0;
        User user = new User().setId("123").setName("Tim");
        try {
            log.debug("Logging message: {}", message);
            log.debug("Going to divide {} by {}", 42, zero);
            log.debug("user is {}", user);
            int result = 42 / zero;
        } catch (Exception e) {
            log.error("Error dividing {} by {} ", 42, zero, e);
        }
    }
}

@Data
@Accessors(chain = true)
public class User {
    private String id;
    private String name;
}

5 詳細配置

logback默認行爲:假如它沒有找到任何配置文件,它將默認創建一個ConsoleAppender,並且關聯到root logger。

5.1 logback查找配置文件順序

  • logback按照順序在classpath中查找配置文件:logback-test.xml, logback.groovy, logback.xml ;
  • 假如上面的file都沒有找到,則啓動Java的 ServiceLoader 查找com.qos.logback.classic.spi.Configurator的實現;
  • 假如以上都沒有,則啓動logback默認的行爲: 創建一個ConsoleAppender,並且關聯到root logger。

5.2 基本配置解釋

所有的配置都在裏面。

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

這裏配置了一個Appender,類型是ConsoleAppender,名稱是STDOUT。

 <root level="debug">
        <appender-ref ref="STDOUT"/>
    </root>

標籤是root logger,level是debug,關聯到STDOUT的Appender。

5.3 解決日誌故障配置

logback的配置文件可以很複雜,因此logback提供內置機制來排查故障。爲查看logback自身的日誌,可以打開debug模式:

<configuration debug="true">

打印出的日誌如下所示:

15:17:24,550 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
15:17:24,550 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
15:17:24,550 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback.xml] at [file:/E:/10_git/LogbackDemo/target/classes/logback.xml]
15:17:24,699 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
15:17:24,703 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [STDOUT]
15:17:24,733 |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property
15:17:24,775 |-INFO in ch.qos.logback.classic.joran.action.RootLoggerAction - Setting level of ROOT logger to DEBUG
15:17:24,775 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [STDOUT] to Logger[ROOT]
15:17:24,776 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
15:17:24,778 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@5606c0b - Registering current configuration as safe fallback point
15:17:24.782 [main] INFO  com.logback.demo.Example - Example log from Example
Disconnected from the target VM, address: '127.0.0.1:50977', transport: 'socket'

Process finished with exit code 0

5.4 自動重載配置

<configuration debug="true" scan="true" scanPeriod="15 seconds">

scan=true,表示打開自動掃描配置,默認爲false。scanPeriod="15 seconds"表示每15秒掃描一次,也可以設置milliseconds, seconds, minutes, or hour,如果沒設置,默認爲1分鐘。

5.5 修改Loggers

我們可以給任何logger配置level,如下配置:

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="true" scanPeriod="15 seconds">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <logger name="com.logback.other" level="INFO"/>
    <logger name="com.logback.demo.tests" level="WARN"/>
    <root level="debug">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>
public class ModifyingLoggersExample {
    @Test
    public void testModify() {
        Logger otherLogger = LoggerFactory.getLogger("com.logback.other");
        Logger demoLogger = LoggerFactory.getLogger("com.logback.demo");
        Logger testsLogger = LoggerFactory.getLogger("com.logback.demo.tests");

        otherLogger.debug("otherLogger is logged debug == debug");
        demoLogger.debug("demoLogger is not logged debug < info");
        demoLogger.info("demoLogger is logged info == info");
        testsLogger.info("testsLogger is not logged warn > info");
        testsLogger.warn("testsLogger is logged warn = warn");
    }
}

結果:

15:52:17.359 [main] DEBUG com.logback.other - otherLogger is logged debug == debug
15:52:17.361 [main] INFO  com.logback.demo - demoLogger is logged info == info
15:52:17.362 [main] WARN  com.logback.demo.tests - testsLogger is logged warn = warn

Logger同樣能繼承root logger的appender-ref。

5.6 變量替代

<property name="LOG_DIR" value="/var/log/application" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>${LOG_DIR}/tests.log</file>
    <append>true</append>
    <encoder>
        <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
</appender>

我們聲明一個 名稱是LOG_DIR, 值是/var/log/application,logback會將它的值注入到${LOG_DIR}。

6 Appenders

logback支持不進支持log以文件形式輸出,還支持其他的形式。

6.1 ConsoleAppender

控制檯輸出,利用System.out 或 System.err輸出。

6.2 FileAppender

FileAppender是把log輸出到文件。

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="true" scanPeriod="15 seconds">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>tests.log</file>
        <append>true</append>
        <encoder>
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <logger name="com.logback.demo" level="INFO"/>
    <logger name="com.logback.demo.tests" level="WARN">
        <appender-ref ref="FILE"/>
    </logger>
    <root level="debug">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

運行ModifyingLoggersExample的testModify可以看到工程中多了一個文件tests.log.
我們可以同時在tests.log文件和控制檯看到同樣的輸出信息;
test
在這裏插入圖片描述
這是因爲logger繼承了來自 的<appender-ref , 如果要阻止,只需如下配置:添加additivity=“false”

<logger name="com.baeldung.logback.tests" level="WARN" additivity="false" > 
    <appender-ref ref="FILE" /> 
</logger> 

6.3 RollingFileAppender

有時候我們不需要將log信息一直輸出到同個文件,而是根據時間,文件大小或者兩者,將歷史文件打包輸出。

<property name="LOG_FILE" value="LogFile" />
    <property name="LOG_DIR" value="/var/logs/application" />
    <appender name="ROLL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_DIR}/${LOG_FILE}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每日滾動打包 -->
            <fileNamePattern>${LOG_DIR}/${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
            <!-- 保存30天日誌,文件總大小在3GB -->
            <maxHistory>30</maxHistory>
            <totalSizeCap>3GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

這裏定義了文件路徑和文件名,使用基於時間的TimeBasedRollingPolicy,不僅定義文件名,而且定義了每天打包一次文件。也可以按月打包一次:

<fileNamePattern>${LOG_DIR}/%d{yyyy/MM}/${LOG_FILE}.gz</fileNamePattern>

6.4 自定義Appender

參考這裏:https://www.baeldung.com/custom-logback-appender。

7 Layouts

Layouts用於格式化log信息。也可以自定義Layout,但通常我們使用默認的PatternLayout 。

<encoder>
    <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
  • %d{HH:mm:ss.SSS},表示小時,分鐘,秒,毫秒;
  • [%thread] ,表示線程;
  • %-5level,表示日誌level;
  • %logger{36},表示logger的名,截取35個字符;
  • %msg%n,%msg日誌信息,%n表示換行。
    可以參考:http://logback.qos.ch/manual/layouts.html#conversionWord

8 總結

我們學習了Logback的基本功能,瞭解了三個組件:Logger,Appender和Layouts,比較詳細講解了主要使用的FileAppender和RollingFileAppender.
代碼地址:https://github.com/tobebetter9527/LogbackDemo。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章