簡單的說,日志就是記錄程序的運行軌跡,方便查找關鍵信息,也方便快速定位解決問題。
本篇文章分為三部分講解:
- 常用日志框架
- SpringBoot 配置Logback
- 阿里日志規約
常用日志框架
-
Logging
這是 Java 自帶的日志工具類,在 JDK 1.5 開始就已經有了,在 java.util.logging 包下。 -
Log4j
Log4j 是 Apache 的一個開源日志框架,也是市場占有率最多的一個框架。大多數沒用過 Java Logging, 但沒人敢說沒用過 Log4j 吧,反正從我接觸 Java 開始就是這種情況,做 Java 項目必有 Log4j 日志框架。 -
commons-logging
commons-logging 就是日志的門面接口,它也是 apache 最早提供的日志門面接口,用戶可以根據喜好選擇不同的日志實現框架,而不必改動日志定義,這就是日志門面的好處,符合面對接口抽象編程。 -
Slf4j
全稱:Simple Logging Facade for Java,即簡單日志門面接口,和 Apache 的 commons-logging 是一樣的概念,它們都不是具體的日志框架,你可以指定其他主流的日志實現框架。 -
Logback
Logback 是 Slf4j 的原生實現框架,同樣也是出自 Log4j 一個人之手,但擁有比 log4j 更多的優點、特性和更做強的性能,現在基本都用來代替 log4j 成為主流。
為什么 Logback 會成為主流?
無論從設計上還是實現上,Logback相對log4j而言有了相對多的改進。
更快的執行速度
基于我們先前在log4j上的工作,logback 重寫了內部的實現,在某些特定的場景上面,甚至可以比之前的速度快上10倍。在保證logback的組件更加快速的同時,同時所需的內存更加少。
日志框架總結
- commons-loggin、slf4j 只是一種日志抽象門面,不是具體的日志框架。
- log4j、logback 是具體的日志實現框架。
- 一般首選強烈推薦使用 slf4j + logback。當然也可以使用slf4j + log4j、commons-logging + log4j 這兩種日志組合框架。
SpringBoot logback
Spring Boot會用Logback來記錄日志,并用INFO級別輸出到控制臺。在運行應用程序和其他例子時,你應該已經看到很多INFO級別的日志了。
日志依賴
依賴 spring-boot-starter-web 默認包含spring-boot-starter-logging
那么,我們的Spring Boot應用將自動使用logback作為應用日志框架,Spring Boot啟動的時候,由org.springframework.boot.logging.Logging-Application-Listener根據情況初始化并使用。
默認配置屬性支持
Spring Boot為我們提供了很多默認的日志配置,所以,只要將spring-boot-starter-logging作為依賴加入到當前應用的classpath,則“開箱即用”。 下面介紹幾種在application.properties就可以配置的日志相關屬性。
控制臺輸出
日志級別從低到高分為TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果設置為WARN,則低于WARN的信息都不會輸出。 Spring Boot中默認配置ERROR、WARN和INFO級別的日志輸出到控制臺。您還可以通過啟動您的應用程序–debug標志來啟用“調試”模式(開發的時候推薦開啟),以下兩種方式皆可:
- 在運行命令后加入–debug標志,如:$ java -jar springTest.jar --debug。
- 在application.properties中配置debug=true,該屬性置為true的時候,核心Logger(包含嵌入式容器、hibernate、spring)會輸出更多內容,但是你自己應用的日志并不會輸出為DEBUG級別。
文件輸出
默認情況下,Spring Boot將日志輸出到控制臺,不會寫到日志文件。如果要編寫除控制臺輸出之外的日志文件,則需在application.properties中設置logging.file或logging.path屬性。
- logging.file,設置文件,可以是絕對路徑,也可以是相對路徑。如:logging.file=my.log。
- logging.path,設置目錄,會在該目錄下創建spring.log文件,并寫入日志內容,如:logging.path=/var/log。
如果只配置 logging.file,會在項目的當前路徑下生成一個 xxx.log 日志文件。
如果只配置 logging.path,在 /var/log文件夾生成一個日志文件為 spring.log。
級別控制
所有支持的日志記錄系統都可以在Spring環境中設置記錄級別(例如在application.properties中) 格式為:’logging.level.* = LEVEL’
- logging.level:日志級別控制前綴,*為包名或Logger名
- LEVEL:選項TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF
舉例:
- logging.level.com.mrbird=DEBUG:com.mrbird包下所有class以DEBUG級別輸出。
- logging.level.root=WARN:root日志以WARN級別輸出。
自定義日志配置
由于日志服務一般都在ApplicationContext創建前就初始化了,它并不是必須通過Spring的配置文件控制。因此通過系統屬性和傳統的Spring Boot外部配置文件依然可以很好的支持日志控制和管理。
根據不同的日志系統,你可以按如下規則組織配置文件名,就能被正確加載:
- Logback:logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
- Log4j:log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
- Log4j2:log4j2-spring.xml, log4j2.xml
- JDK (Java Util Logging):logging.properties
Spring Boot官方推薦優先使用帶有-spring的文件名作為你的日志配置(如使用logback-spring.xml,而不是logback.xml),命名為logback-spring.xml的日志配置文件,spring boot可以為它添加一些spring boot特有的配置項(下面會提到)。
上面是默認的命名規則,并且放在src/main/resources下面即可。
如果你即想完全掌控日志配置,但又不想用logback.xml作為Logback配置的名字,可以在application.properties配置文件里面通過logging.config屬性指定自定義的名字:
logging.config=classpath:logging-config.xml
雖然一般并不需要改變配置文件的名字,但是如果你想針對不同運行時Profile使用不同的日 志配置,這個功能會很有用。
下面我們來看看一個普通的logback-spring.xml例子:
<?xml version="1.0" encoding="UTF-8"?>
<!-- scan:當此屬性設置為true時,配置文件如果發生改變,將會被重新加載,默認值為true。
scanPeriod:設置監測配置文件是否有修改的時間間隔,如果沒有給出時間單位,默認單位是毫秒。當scan為true時,此屬性生效。默認的時間間隔為1分鐘。
debug:當此屬性設置為true時,將打印出logback內部日志信息,實時查看logback運行狀態。默認值為false。-->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 每個logger都關聯到logger上下文,默認上下文名稱為“default”。但可以使用設置成其他名字,用于區分不同應用程序的記錄。
一旦設置,不能修改,可以通過%contextName來打印日志上下文名稱。-->
<contextName>logback</contextName>
<!-- 設置變量<property> 用來定義變量值的標簽,有兩個屬性,name和value;其中name的值是變量的名稱,value的值時變量定義的值。
通過定義的值會被插入到logger上下文中。定義變量后,可以使“${}”來使用變量。-->
<property name="log.path" value="log" />
<!-- appender用來格式化日志輸出節點,有倆個屬性name和class,class用來指定哪種輸出策略,常用就是控制臺輸出策略和文件輸出策略。-->
<!-- <encoder>表示對日志進行編碼:
%d{HH: mm:ss.SSS}——日志輸出時間。
%thread——輸出日志的進程名字,這在Web應用以及異步任務處理中很有用。
%-5level——日志級別,并且使用5個字符靠左對齊。
%logger{36}——日志輸出者的名字。
%msg——日志消息。
%n——平臺的換行符。
ThresholdFilter為系統定義的攔截器,例如我們用ThresholdFilter來過濾掉ERROR級別以下的日志不輸出到文件中。如果不用記得注釋掉,不然你控制臺會發現沒日志~-->
<!--輸出到控制臺-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!-- <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>-->
<encoder>
<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- <fileNamePattern>${log.path}/logback.%d{yyyy-MM-dd}.log</fileNamePattern>定義了日志的切分方式——把每一天的日志歸檔到一個文件中;
<maxHistory>30</maxHistory>表示只保留最近30天的日志,以防止日志填滿整個磁盤空間。同理,可以使用%d{yyyy-MM-dd_HH-mm}來定義精確到分的日志切分方式;
<totalSizeCap>1GB</totalSizeCap>用來指定日志文件的上限大小,例如設置為1GB的話,那么到了這個值,就會刪除舊的日志。-->
<!--輸出到文件-->
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/logback.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- root節點是必選節點,用來指定最基礎的日志輸出級別,只有一個level屬性,
用來設置打印級別,大小寫無關:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,不能設置為INHERITED或者同義詞NULL。-->
<root level="info">
<appender-ref ref="console" />
<appender-ref ref="file" />
</root>
<!-- <logger>用來設置某一個包或者具體的某一個類的日志打印級別、以及指定<appender>。<logger>僅有一個name屬性,一個可選的level和一個可選的addtivity屬性。-->
<!-- logback為java中的包 -->
<logger name="top.lconcise.controller.HelloController"/>
<!-- name:用來指定受此logger約束的某一個包或者具體的某一個類。
level:用來設置打印級別,大小寫無關:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,還有一個特俗值INHERITED或者同義詞NULL,
代表強制執行上級的級別。如果未設置此屬性,那么當前logger將會繼承上級的級別。
addtivity:是否向上級logger傳遞打印信息。默認是true。-->
<!--logback.LogbackDemo:類的全路徑 -->
<logger name="top.lconcise.controller.HelloController2" level="WARN" additivity="false">
<appender-ref ref="console"/>
</logger>
</configuration>
阿里日志規約
1.【強制】應用中不可直接使用日志系統(Log4j、 Logback) 中的 API,而應依賴使用日志框架
SLF4J 中的 API,使用門面模式的日志框架,有利于維護和各個類的日志處理方式統一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(A.class);
2.【強制】日志文件推薦至少保存 15 天,因為有些異常具備以“周”為頻次發生的特點。
3.【強制】應用中的擴展日志(如打點、臨時監控、訪問日志等) 命名方式:
appName_logType_logName.log。 logType:日志類型,推薦分類有
stats/desc/monitor/visit 等; logName:日志描述。這種命名的好處:通過文件名就可知
道日志文件屬于什么應用,什么類型,什么目的,也有利于歸類查找。
正例: mppserver 應用中單獨監控時區轉換異常,如:
mppserver_monitor_timeZoneConvert.log
說明: 推薦對日志進行分類, 如將錯誤日志和業務日志分開存放,便于開發人員查看,也便于
通過日志對系統進行及時監控。
4.【強制】對 trace/debug/info 級別的日志輸出,必須使用條件輸出形式或者使用占位符的方式。
說明:
logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
如果日志級別是 warn,上述日志不會打印,但是會執行字符串拼接操作,如果 symbol 是對象,會執行 toString()方法,浪費了系統資源,執行了上述操作,最終日志卻沒有打印。
正例: (條件)
if (logger.isDebugEnabled()) {
logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
}
正例: (占位符)
logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
5.【強制】避免重復打印日志,浪費磁盤空間,務必在 log4j.xml 中設置 additivity=false。
正例: <logger name="com.taobao.dubbo.config" additivity="false">
6.【強制】異常信息應該包括兩類信息:案發現場信息和異常堆棧信息。如果不處理,那么通過
關鍵字 throws 往上拋出。
正例: logger.error(各類參數或者對象 toString + "_" + e.getMessage(), e);
7.【推薦】謹慎地記錄日志。生產環境禁止輸出 debug 日志; 有選擇地輸出 info 日志; 如果使
用 warn 來記錄剛上線時的業務行為信息,一定要注意日志輸出量的問題,避免把服務器磁盤
撐爆,并記得及時刪除這些觀察日志。
說明: 大量地輸出無效日志,不利于系統性能提升,也不利于快速定位錯誤點。 記錄日志時請
思考:這些日志真的有人看嗎?看到這條日志你能做什么?能不能給問題排查帶來好處?
8.【參考】可以使用 warn 日志級別來記錄用戶輸入參數錯誤的情況,避免用戶投訴時,無所適
從。注意日志輸出的級別, error 級別只記錄系統邏輯出錯、異常等重要的錯誤信息。如非必
要,請不要在此場景打出 error 級別。
測試源碼:https://github.com/lbshold/springboot/tree/master/Spring-Boot-Log
參考文章:https://mrbird.cc/Spring-Boot-logback.html