本作品採用知識共享署名-非商業性使用 4.0 國際許可協議進行許可。
一、前言
本文以一個簡單的項目爲例,一步步展示logback的同步和異步配置方法,並且配置的日誌要求滿足阿里巴巴Java開發手冊-日誌規約 ,因爲對於線上服務,日誌對於排查問題有至關重要的作用,規範的日誌格式配合shell腳本可以快速定位問題。
最開始使用Java日誌系統,最大的疑惑就是分不清楚log4j
、slf4j
、logback
等日誌庫之間的關係,不過網上有不少文章介紹這部分相關知識,比如理解Java日誌體系、Java日誌框架那些事兒、混亂的 Java 日誌體系,可以作爲提前閱讀。
二、配置同步日誌
2.1 日誌要求
首先項目的整體結構如下圖所示:
一共有兩個package
,util
和http
,下面分別有兩個類Util.java
和Http.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_NAME
爲logbacktest
,後面可以用${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.java
和Util.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.java
和Main.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導致的問題,水平有限,如有不足,請指出。
[完]