Java Logback簡易教程


本作品採用知識共享署名-非商業性使用 4.0 國際許可協議進行許可。

一、前言

本文以一個簡單的項目爲例,一步步展示logback的同步和異步配置方法,並且配置的日誌要求滿足阿里巴巴Java開發手冊-日誌規約 ,因爲對於線上服務,日誌對於排查問題有至關重要的作用,規範的日誌格式配合shell腳本可以快速定位問題。

最開始使用Java日誌系統,最大的疑惑就是分不清楚log4jslf4jlogback等日誌庫之間的關係,不過網上有不少文章介紹這部分相關知識,比如理解Java日誌體系Java日誌框架那些事兒混亂的 Java 日誌體系,可以作爲提前閱讀。

二、配置同步日誌

2.1 日誌要求

首先項目的整體結構如下圖所示:
在這裏插入圖片描述
一共有兩個packageutilhttp,下面分別有兩個類Util.javaHttp.java,我們的日誌要求是:

  • http目錄下的文件產生的日誌全部記錄到:~/logs/${appname}/http.log,級別:INFO
  • util目錄下的文件產生的日誌全部記錄到:~/logs/${appname}/util.log,級別:DEBUG
  • 其餘日誌文件均記錄到:~/logs/${appname}/${appname}.log,級別:INFO
  • 要求所有的日誌均至少保存15天
  • 如果保存15天,內容太大可能造成磁盤風險,則最大保存10GB的日誌。

2.2 添加POM

使用logback+slf4j的組合,需要依賴的pom如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.test</groupId>
    <artifactId>logback-test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.10</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
    </dependencies>
</project>

2.3 添加logback.xml

logback查找配置的順序如下所示:

  • 在系統配置文件System Properties中尋找是否有logback.configurationFile對應的value
  • 在classpath下尋找是否有logback.groovy(即logback支持groovy與xml兩種配置方式)
  • 在classpath下尋找是否有logback-test.xml
  • 在classpath下尋找是否有logback.xml

resources目錄下添加logback.xml,目前內容爲空,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    
</configuration>

在這裏插入圖片描述

2.4 配置appender

appender用來配置日誌的文件名、日誌的寫入策略,滾動策略等,我們按照2.1的要求配置appender如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="APP_NAME" value="logbacktest" />
    <property name="LOG_NAME" value="${user.home}/logs/${APP_NAME}/${APP_NAME}.log" />

    <appender name="APP_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--指定日誌文件名稱-->
        <file>${LOG_NAME}</file>
        <encoder>
            <!--指定日誌內容格式-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>utf8</charset>
        </encoder>
        <rollingPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!--日誌最大保存15天-->
            <maxHistory>15</maxHistory>
            <!--日誌最大的文件大小-->
            <maxFileSize>100MB</maxFileSize>
            <!--日誌最大保存10GB-->
            <totalSizeCap>10GB</totalSizeCap>
        </rollingPolicy>
    </appender>

</configuration>

property可以用來定義變量,我們定義APP_NAMElogbacktest,後面可以用${APP_NAME}來使用這個變量,其它的配置見註釋。

2.5 配置root

上面我們定義了appender,定義了日誌文件名,日誌寫入策略,但是現在還有一個問題就是:哪個路徑下的日誌寫入上面定義的appender?是Main.java下的,還是Util.java下的呢?

當然,logback有配置專門去配置路徑,這裏我們先配置root,即:默認表示所有路徑。我們在2.1中的要求是其餘日誌文件均記錄到:~/logs/${appname}/${appname}.log,級別:INFO,我們目前可以通過配置root來滿足這個需求,如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="APP_NAME" value="logbacktest" />
    <property name="LOG_NAME" value="${user.home}/logs/${APP_NAME}/${APP_NAME}.log" />

    <appender name="APP_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        ...
    </appender>

    <root level="INFO">
        <!--ref表示具體的appender name-->
        <appender-ref ref="APP_LOG" />
    </root>

</configuration>

這樣配置之後,所有的日誌都會以root的level,即INFO去使用APP_LOG這個appender打印。

2.6 打印日誌

編輯Util.java如下所示,打印5個級別的日誌:

package com.test.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author bodong.ybd
 * @date 2019/6/17
 */
public class Util {
    private static final Logger log = LoggerFactory.getLogger(Util.class);

    public static void loginfo() {
        log.trace("trace");
        log.debug("debug");
        log.info("info");
        log.warn("warn");
        log.error("error");
    }
}

編輯Main.java文件內容如下所示:

package com.test;

import com.test.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author bodong.ybd
 * @date 2019/6/17
 */
public class Main {
    private static final Logger log = LoggerFactory.getLogger(Main.class);

    private static void loginfo() {
        log.trace("trace");
        log.debug("debug");
        log.info("info");
        log.warn("warn");
        log.error("error");
    }

    public static void main(String[] args) {
        loginfo();
        Util.loginfo();
    }
}

運行Main.java,打印日誌,效果如下:

➜  ~ ls ~/logs/logbacktest
logbacktest.log
➜  ~ cat ~/logs/logbacktest/logbacktest.log
2019-06-19 19:33:26 [main] INFO  com.test.Main - info
2019-06-19 19:33:26 [main] WARN  com.test.Main - warn
2019-06-19 19:33:26 [main] ERROR com.test.Main - error
2019-06-19 19:33:26 [main] INFO  com.test.util.Util - info
2019-06-19 19:33:26 [main] WARN  com.test.util.Util - warn
2019-06-19 19:33:26 [main] ERROR com.test.util.Util - error

可以看到產生了一個日誌文件logbacktest.log,並且Main.javaUtil.java的日誌都打印在了這個文件中,日誌級別是root配置的INFO,但是這樣並不滿足要求,因爲2.1要求:util目錄下的文件產生的日誌全部記錄到:~/logs/${appname}/util.log,級別:DEBUG,這個問題需要配置logger來解決。

2.7 配置logger

logger用來設置某個包或者類具體日誌的打印級別,下面我們把Util.java配置拆出去。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="APP_NAME" value="logbacktest" />
    <property name="LOG_NAME" value="${user.home}/logs/${APP_NAME}/${APP_NAME}.log" />
    <property name="UTIL_NAME" value="${user.home}/logs/${APP_NAME}/util.log" />

    <appender name="APP_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
            ...
            </appender>

    <appender name="UTIL_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--指定日誌文件名稱-->
        <file>${UTIL_NAME}</file>
        <encoder>
            <!--指定日誌內容格式-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>utf8</charset>
        </encoder>
        <rollingPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${UTIL_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!--日誌最大保存15天-->
            <maxHistory>15</maxHistory>
            <!--日誌最大的文件大小-->
            <maxFileSize>100MB</maxFileSize>
            <!--日誌最大保存10GB-->
            <totalSizeCap>10GB</totalSizeCap>
        </rollingPolicy>
    </appender>

    <!--com.test.util目錄下的文件產生的日誌全部記錄到util.log-->
    <!--默認的日誌級別是DEBUG-->
    <!--additivity=false表示如果能匹配到這條規則就不用往上繼續查找到root節點去-->
    <logger name="com.test.util" level="DEBUG" additivity="false" >
        <appender-ref ref="UTIL_LOG"/>
    </logger>

    <root level="INFO">
        <!--ref表示具體的appender-->
        <appender-ref ref="APP_LOG" />
    </root>

</configuration>

這樣配置完成後就可以把Util.javaMain.java的日誌分開了,效果如下所示:

➜  ~ ls ~/logs/logbacktest
logbacktest.log util.log
➜  ~ cat ~/logs/logbacktest/logbacktest.log
2019-06-19 19:42:51 [main] INFO  com.test.Main - info
2019-06-19 19:42:51 [main] WARN  com.test.Main - warn
2019-06-19 19:42:51 [main] ERROR com.test.Main - error
➜  ~ cat ~/logs/logbacktest/util.log
2019-06-19 19:42:51 [main] DEBUG com.test.util.Util - debug
2019-06-19 19:42:51 [main] INFO  com.test.util.Util - info
2019-06-19 19:42:51 [main] WARN  com.test.util.Util - warn
2019-06-19 19:42:51 [main] ERROR com.test.util.Util - error

好了,看到這裏,詳細Http.java相關的日誌你肯定也會配置了,試試看。

三、配置異步日誌

寫入方式 優點 缺點
同步 一般使用O_SYNC標誌打開文件,即每條日誌會至少寫入磁盤緩存,安全。 慢,每條都會刷盤。
異步 將內容寫入到內存即可返回(不同異步庫可能用不同數據結構),速度快。更多可參考:https://logging.apache.org/log4j/2.x/manual/async.html 不安全,如果內存數據結構滿了或者機器斷電,可能造成數據丟失。

使用logback配置異步日誌可以使用appender:ch.qos.logback.classic.AsyncAppender,將上面的Util.java的log配置爲異步如下所示:

<appender name ="ASYNC_UTIL_LOG" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>512</queueSize>
        <neverBlock>true</neverBlock>
        <appender-ref ref="UTIL_LOG"/>
    </appender>

    <logger name="com.test.util" level="DEBUG" additivity="false" >
        <appender-ref ref="ASYNC_UTIL_LOG"/>
    </logger>

discardingThreshold:默認情況下,當阻塞隊列剩餘20%的容量時,它將丟棄級別TRACE,DEBUG和INFO的事件,僅保留級別WARN和ERROR的事件。要保留所有事件,請將discardingThreshold設置爲0。
queueSize:阻塞隊列的最大容量。默認情況下,queueSize設置爲256。
neverBlock:如果爲false(默認值),appender將阻塞在添加隊列的接口處。設置爲true,appender將刪除消息,不會阻止您的應用程序。
這幾個參數更加詳細的解釋見: https://logback.qos.ch/manual/appenders.html

logback還有另一種異步日誌配置方式,即使用disruptor,可參考這裏配置。

四、總結

本文以一個例子說明了logback+slf4配置同步和異步日誌的方法,使用異步日誌除了可以提高程序性能之外,還可以防止部分磁盤IO Hang導致的問題,水平有限,如有不足,請指出。

[完]

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