(點選上方公眾號,可快速關註)
來源:五月的倉頡 ,
www.cnblogs.com/xrq730/p/8628945.html
為什麼使用logback
記得前幾年工作的時候,公司使用的日誌框架還是log4j,大約從16年中到現在,不管是我參與的別人已經搭建好的專案還是我自己主導的專案,日誌框架基本都換成了logback,總結一下,logback大約有以下的一些優點:
-
核心重寫、測試充分、初始化記憶體載入更小,這一切讓logback效能和log4j相比有諸多倍的提升
-
logback非常自然地直接實現了slf4j,這個嚴格來說算不上優點,只是這樣,再理解slf4j的前提下會很容易理解logback,也同時很容易用其他日誌框架替換logback
-
logback有比較齊全的200多頁的檔案
-
logback當配置檔案修改了,支援自動重新載入配置檔案,掃描過程快且安全,它並不需要另外建立一個掃描執行緒
-
支援自動去除舊的日誌檔案,可以控制已經產生日誌檔案的最大數量
總而言之,如果大家的專案裡面需要選擇一個日誌框架,那麼我個人非常建議使用logback。
logback載入
我們簡單分析一下logback載入過程,當我們使用logback-classic.jar時,應用啟動,那麼logback會按照如下順序進行掃描:
-
在系統配置檔案System Properties中尋找是否有logback.configurationFile對應的value
-
在classpath下尋找是否有logback.groovy(即logback支援groovy與xml兩種配置方式)
-
在classpath下尋找是否有logback-test.xml
-
在classpath下尋找是否有logback.xml
以上任何一項找到了,就不進行後續掃描,按照對應的配置進行logback的初始化,具體程式碼實現可見ch.qos.logback.classic.util.ContextInitializer類的findURLOfDefaultConfigurationFile方法。
當所有以上四項都找不到的情況下,logback會呼叫ch.qos.logback.classic.BasicConfigurator的configure方法,構造一個ConsoleAppender用於向控制檯輸出日誌,預設日誌輸出格式為”%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} – %msg%n”。
logback的configuration
logback的重點應當是Appender、Logger、Pattern,在這之前先簡單瞭解一下logback的
-
scan:當scan被設定為true時,當配置檔案發生改變,將會被重新載入,預設為true
-
scanPeriod:檢測配置檔案是否有修改的時間間隔,如果沒有給出時間單位,預設為毫秒,當scan=true時這個值生效,預設時間間隔為1分鐘
-
debug:當被設定為true時,將打印出logback內部日誌資訊,實時檢視logback執行資訊,預設為false
先從最基本的
-
name:用來指定受此logger約束的某一個包或者具體的某一個類
-
level:用來設定列印級別,五個常用列印級別從低至高依次為TRACE、DEBUG、INFO、WARN、ERROR,如果未設定此級別,那麼當前logger會繼承上級的級別
-
additivity:是否向上級logger傳遞列印資訊,預設為true
public LoggerContext() {
super();
this.loggerCache = new ConcurrentHashMap
();
this.loggerContextRemoteView = new LoggerContextVO(this);
this.root = new Logger(Logger.ROOT_LOGGER_NAME, null, this);
this.root.setLevel(Level.DEBUG);
loggerCache.put(Logger.ROOT_LOGGER_NAME, root);
initEvaluatorMap();
size = 1;
this.frameworkPackages = new ArrayList
(); }
Logger的建構式為:
Logger(String name, Logger parent, LoggerContext loggerContext) {
this.name = name;
this.parent = parent;
this.loggerContext = loggerContext;
}
看到第一個引數就是Root的name,而這個Logger.ROOT_LOGGER_NAME的定義為final public String ROOT_LOGGER_NAME = “ROOT”,由此可以看出
接著寫一段程式碼來測試一下:
public class Slf4jTest {
@Test
public void testSlf4j() {
Logger logger = LoggerFactory.getLogger(Object.class);
logger.trace(“=====trace=====”);
logger.debug(“=====debug=====”);
logger.info(“=====info=====”);
logger.warn(“=====warn=====”);
logger.error(“=====error=====”);
}
}
logback.xml的配置為:
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger – %msg%n
root將列印級別設定為”info”級別,
2018-03-26 22:57:48.779 [main] INFO java.lang.Object – =====info=====
2018-03-26 22:57:48.782 [main] WARN java.lang.Object – =====warn=====
2018-03-26 22:57:48.782 [main] ERROR java.lang.Object – =====error=====
logback.xml的意思是,當Test方法執行時,root節點將日誌級別大於等於info的交給已經配置好的名為”STDOUT”的
接著理解一下
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger – %msg%n
註意這個name表示的是LoggerFactory.getLogger(XXX.class),XXX的包路徑,包路徑越少越是父級,我們測試程式碼裡面是Object.class,即name=”java”是name=”java.lang”的父級,root是所有
2018-03-27 23:02:02.963 [main] DEBUG java.lang.Object – =====debug=====
2018-03-27 23:02:02.965 [main] INFO java.lang.Object – =====info=====
2018-03-27 23:02:02.966 [main] WARN java.lang.Object – =====warn=====
2018-03-27 23:02:02.966 [main] ERROR java.lang.Object – =====error=====
出現這樣的結果是因為:
-
中沒有配置level,即繼承父級的level, 的父級為 ,那麼level=debug -
沒有配置additivity,那麼additivity=true,表示此
的列印資訊向父級 傳遞 -
沒有配置
,表示此 不會打印出任何資訊
由此可知,
接著,我們再配置一個
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger – %msg%n
如果讀懂了上面的例子,那麼這個例子應當很好理解:
-
LoggerFactory.getLogger(Object.class),首先找到name=”java.lang”這個
,將日誌級別大於等於warn的使用”STDOUT”這個 打印出來 -
name=”java.lang”這個
沒有配置additivity,那麼additivity=true,列印資訊向上傳遞,傳遞給父級name=”java”這個 -
name=”java”這個
的additivity=false且不關聯任何 ,那麼name=”java”這個 不會列印任何資訊
由此分析,得出最終的列印結果為:
2018-03-27 23:12:16.147 [main] WARN java.lang.Object – =====warn=====
2018-03-27 23:12:16.150 [main] ERROR java.lang.Object – =====error=====
舉一反三,上面的name=”java”這個
接著看一下
-
name指定
的名稱 -
class指定
的全限定名
1
2
3
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger – %msg%n 4
5
其中,encoder表示對引數進行格式化。我們和上一部分的例子對比一下,發現這裡是有所區別的,上面使用了
-
是0.9.19版本之後引進的,以前的版本使用 ,logback極力推薦的是使用 而不是 -
最常用的FileAppender和它的子類的期望是使用
而不再使用
關於
D:/123.log
true
%-4relative [%thread] %-5level %logger{35} – %msg%n
它的幾個節點為:
-
表示寫入的檔案名,可以使相對目錄也可以是絕對目錄,如果上級目錄不存在則自動建立 -
如果為true表示日誌被追加到檔案結尾,如果是false表示清空檔案 -
表示輸出格式,後面說 -
如果為true表示日誌會被安全地寫入檔案,即使其他的FileAppender也在向此檔案做寫入操作,效率低,預設為false
接著來看一下RollingFileAppender,RollingFileAppender的作用是滾動記錄檔案,先將日誌記錄到指定檔案,當符合某個條件時再將日誌記錄到其他檔案,RollingFileAppender配置比較靈活,因此使用得更多,示例為:
rolling-file-%d{yyyy-MM-dd}.log
30
這種是僅僅指定了
-
,必要節點,包含檔案名及”%d”轉換符,”%d”可以包含一個Java.text.SimpleDateFormat指定的時間格式,如%d{yyyy-MM},如果直接使用%d那麼格式為yyyy-MM-dd。RollingFileAppender的file子節點可有可無,透過設定file可以為活動檔案和歸檔檔案指定不同的位置 -
,可選節點,控制保留的歸檔檔案的最大數量,如果超出數量就刪除舊檔案,假設設定每個月滾動且 是6,則只儲存最近6個月的檔案
向其他還有SizeBasedTriggeringPolicy,用於按照檔案大小進行滾動,可以自己查閱一下資料。
非同步寫日誌
日誌通常來說都以檔案形式記錄到磁碟,例如使用
接著我們看下如何使用logback進行非同步寫日誌配置:
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger – %msg%n
即,我們引入了一個AsyncAppender,先說一下AsyncAppender的原理,再說一下幾個引數:
當我們配置了AsyncAppender,系統啟動時會初始化一條名為”AsyncAppender-Worker-ASYNC”的執行緒
當Logging Event進入AsyncAppender後,AsyncAppender會呼叫appender方法,appender方法中再將event填入Buffer(使用的Buffer為BlockingQueue,具體實現為ArrayBlockingQueye)前,會先判斷當前Buffer的容量以及丟棄日誌特性是否開啟,當消費能力不如生產能力時,AsyncAppender會將超出Buffer容量的Logging Event的級別進行丟棄,作為消費速度一旦跟不上生產速度導致Buffer上限溢位處理的一種方式。
上面的執行緒的作用,就是從Buffer中取出Event,交給對應的appender進行後面的日誌推送
從上面的描述我們可以看出,AsyncAppender並不處理日誌,只是將日誌緩衝到一個BlockingQueue裡面去,併在內部建立一個工作執行緒從佇列頭部獲取日誌,之後將獲取的日誌迴圈記錄到附加的其他appender上去,從而達到不阻塞主執行緒的效果。因此AsyncAppender僅僅充當的是事件轉發器,必須取用另外一個appender來做事。
從上述原理,我們就能比較清晰地理解幾個引數的作用了:
-
discardingThreshold,假如等於20則表示,表示當還剩20%容量時,將丟棄TRACE、DEBUG、INFO級別的Event,只保留WARN與ERROR級別的Event,為了保留所有的events,可以將這個值設定為0,預設值為queueSize/5
-
queueSize比較好理解,BlockingQueue的最大容量,預設為256
-
includeCallerData表示是否提取呼叫者資料,這個值被設定為true的代價是相當昂貴的,為了提升效能,預設當event被加入BlockingQueue時,event關聯的呼叫者資料不會被提取,只有執行緒名這些比較簡單的資料
-
appender-ref表示AsyncAppender使用哪個具體的
進行日誌輸出
-
把日誌資訊轉換為位元組陣列
-
把位元組陣列寫到輸出流
目前PatternLayoutEncoder是唯一有用的且預設的encoder,有一個
看到最後一列是”是否避免使用”,這是因為這些資訊是無法直接拿到的(比如請求行號、呼叫方法名),logback必須透過一些特殊手段去獲取這些資料(比如在日誌打印出產生一個堆疊資訊),這種操作會比較影響效率,因此除非必要,否則不建議列印這些資料。
Filter
最後來看一下
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger – %msg%n
看一下輸出:
2018-03-31 22:22:58.843 [main] WARN java.lang.Object – =====warn=====
看到儘管
再看一下ThresholdFilter,配置為:
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger – %msg%n
看一下輸出為:
2018-03-31 22:41:32.353 [main] INFO java.lang.Object – =====info=====
2018-03-31 22:41:32.358 [main] WARN java.lang.Object – =====warn=====
2018-03-31 22:41:32.359 [main] ERROR java.lang.Object – =====error=====
因為ThresholdFilter的策略是,會將日誌級別小於
看完本文有收穫?請轉發分享給更多人
關註「ImportNew」,提升Java技能