一、結論:應用中不可直接使用日志系統(Log4j、Logback)中的API,而應依賴使用日志框架SLF4J中的API,使用門面模式的日志框架,有利於維護和各個類的日志處理方式統一。代碼中的使用格式如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger LOGGER = LoggerFactory.getLogger(Abc.class);
LOGGER.info("訂單{}開始取消訂單,請求參數:{}", orderNo, object.toJSONString());{}可以讀取到后面追加的參數並放置里面。
日志工具對象logger應聲明為private static final:
1、聲明為private是出於安全性考慮,防止logger對象被其他類非法使用。
2、聲明為static是為了防止重復new出logger對象,同時防止logger被序列化,造成安全風險,出於資源利用的考慮,LOGGER的構造方法參數是Class,決定了LOGGER是根據類的結構來進行區分日志,所以一個類只要一個LOGGER就可以了,故static。
3、聲明為final是因為在類的生命周期內無需變更logger,只是記錄該類的信息。
二、SLF4J,全稱為:The Simple Logging Facade for Java,官方解釋為:The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks, such as java.util.logging, logback and log4j. SLF4J allows the end-user to plug in the desired logging framework at deployment time. Note that SLF4J-enabling your library/application implies the addition of only a single mandatory dependency, namely slf4j-api-1.7.21.jar.
slf4j是一系列的日志接口,而log4j logback是具體實現了的日志框架。SLF4J(Simple logging Facade for Java)不是一個真正的日志實現,而是一個抽象層,它允許你在后台使用任意一個日志類庫。slf4j譯為簡單日志門面,是日志框架的抽象。而log4j和logback是眾多日志框架中的幾種。
三、log4j和logback的區別:logback是直接實現了slf4j的接口,是不消耗內存和計算開銷的。而log4j不是對slf4j的原生實現,所以slf4j api在調用log4j時需要一個適配層(適配器模式)。Logback相對log4j而言有了相對多的改進。但是兩者的用法幾乎差別不大。下面是logback的優勢:
•更快的執行速度
•充分的測試
•logback-classic非常自然的實現了SLF4J
•豐富的擴展文檔
•可以使用使用XML配置文件或者Groovy
•自動重新載入配置文件
•優雅地從I/O錯誤中恢復
•自動清除舊的日志歸檔文件
•自動壓縮歸檔日志文件
注意:如果單獨使用slf4j,是無法在控制台輸出日志的。也就是說我們在具體開發中,需要綁定一個具體的日志框架,也就是slf4j的實現,才能正常的使用slf4j。
四、總結如下:
1、slf4j是java的一個日志門面,實現了日志框架一些通用的api,log4j和logback是具體的日志框架。
2、他們可以單獨的使用,也可以綁定slf4j一起使用。
單獨使用。分別調用框架自己的方法來輸出日志信息。綁定slf4j一起使用。調用slf4j的api來輸入日志信息,具體使用與底層日志框架無關(需要底層框架的配置文件)。顯然不推薦單獨使用日志框架。假設項目中已經使用了log4j,而我們此時加載了一個類庫,而這個類庫依賴另一個日志框架。這個時候我們就需要維護兩個日志框架,這是一個非常麻煩的事情。而使用了slf4j就不同了,由於應用調用的抽象層的api,與底層日志框架是無關的,因此可以任意更換日志框架。
五、使用slf4j綁定日志系統的優勢:
1、軟件工程的角度。抽象,解耦,便於維護。在你的開源或內部類庫中使用SLF4J會使得它獨立於任何一個特定的日志實現,這意味着不需要管理多個日志配置或者多個日志類庫。
2、語法設計角度。slf4j有{}占位符,而log4j需要用“+”來連接字符串,既不利於閱讀,同時消耗了內存(heap memory)。在SLF4J,我們不需要字符串連接而且不會導致暫時不需要的字符串消耗。取而代之的,我們在一個以占位符和以參數傳遞實際值的模板格式下寫日志信息,同時提高了代碼的可讀性。
占位符(place holder),在代碼中表示為“{}”的特性。占位符是一個非常類似於在String的format()方法中的%s,因為它會在運行時被某個提供的實際字符串所替換。這不僅降低了你代碼中字符串連接次數,而且還節省了新建的String對象。即使你可能沒需要那些對象,但這個依舊成立,取決於你的生產環境的日志級別,例如在DEBUG或者INFO級別的字符串連接。因為String對象是不可修改的並且它們建立在一個String池中,它們消耗堆內存( heap memory)而且大多數時間他們是不被需要的,例如當你的應用程序在生產環境以ERROR級別運行時候,一個String使用在DEBUG語句就是不被需要的。通過使用SLF4J,你可以在運行時延遲字符串的建立,這意味着只有需要的String對象才被建立。而如果你已經使用log4j,那么你已經對於在if條件中使用debug語句這種變通方案十分熟悉了,但SLF4J的占位符就比這個好用得多。
對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);
3、通過使用SLF4J的日志方法,你可以延遲構建日志信息(Srting)的開銷,直到你真正需要,這對於內存和CPU都是高效的。
4、作為附注,更少的暫時的字符串意味着垃圾回收器(Garbage Collector)需要做更好的工作,這意味着你的應用程序有為更好的吞吐量和性能。
六、slf4j綁定logback
Springboot默認的日志框架為logback,pom添加依賴:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<!-- scan:當此屬性設置為true時,配置文件如果發生改變,將會被重新加載,默認值為true。 scanPeriod:設置監測配置文件是否有修改的時間間隔,如果沒有給出時間單位,默認單位是毫秒當scan為true時,此屬性生效。默認的時間間隔為1分鍾。 debug:當此屬性設置為true時,將打印出logback內部日志信息,實時查看logback運行狀態。默認值為false。 -->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
<!-- 彩色日志 -->
<!-- 彩色日志依賴的渲染類 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%thread]){faint} %clr(%-40.40logger{39}){cyan} %clr([%L]) %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" />
<!-- Console 輸出設置 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- 定義日志級別 -->
<property name="log.level" value="INFO" />
<!-- 定義日志文件名稱,注意修改此處!!! -->
<property name="log.name" value="shop" />
<!-- 定義日志文件的存儲地址 -->
<!-- <property name="log.path" value="/app/wo_sale/servers/logs/${log.name}" /> -->
<property name="log.path" value="logs/${log.name}" />
<!-- 定義日志文件名格式化 -->
<property name="log.timeFormat" value="yyyy-MM-dd" />
<!-- 定義日志文件的輸出格式。%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度,%logger{50} 表示logger名字最長50個字符,否則按照句點分割。%msg:日志消息,%n是換行符 -->
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n" />
<!-- 定義日志文件保留天數 -->
<property name="log.maxHistory" value="30" />
<!-- 定義日志文件最大限制 -->
<property name="log.maxFileSize" value="10MB" />
<!-- 控制台輸出 -->
<!-- <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${log.pattern}</pattern>
</encoder>
</appender> -->
<!-- 滾動記錄文件,先將日志記錄到指定文件,當符合某個條件時,將日志記錄到其他文件,並按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/${log.name}.log</file>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>${log.level}</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 滾動時產生的文件的存放位置及文件名稱 %d{yyyy-MM-dd}:按天進行日志滾動 %i:當文件大小超過maxFileSize時,按照i進行文件滾動 -->
<FileNamePattern>${log.path}/${log.name}.%d{${log.timeFormat}}-%i.log</FileNamePattern>
<MaxHistory>${log.maxHistory}</MaxHistory>
<TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<MaxFileSize>${log.maxFileSize}</MaxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 按照每天生成日志文件。僅記錄錯誤日志 -->
<appender name="FILE-ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/error/${log.name}_error.log</file>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${log.path}/error/${log.name}_error.%d{${log.timeFormat}}-%i.log</FileNamePattern>
<MaxHistory>${log.maxHistory}</MaxHistory>
<TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<MaxFileSize>${log.maxFileSize}</MaxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 日志輸出級別,root與logger是父子關系,沒有特別定義則默認為root,任何一個類只會和一個logger對應, 要么是定義的logger,要么是root,判斷的關鍵在於找到這個logger,然后判斷這個logger的appender和level。 -->
<root level="${log.level}">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
<appender-ref ref="FILE-ERROR" />
</root>
</configuration>