本文是一個系列,歡迎關注html
想必你們都有過使用System.out
來進行輸出調試,開發開發環境下這樣作固然很方便,可是線上這樣作就有麻煩了:java
顯然System.out
解決不了咱們的問題,可是咱們遇到的問題必定會有前人遇到過,日誌也不例外,其中就有一個大牛 Ceki,整個Java的日誌體系幾乎都有Ceki參與或者受到了Ceki的深度影響。固然Java日誌體系的複雜度也有一部分緣由是拜這位大牛所賜。spring
讓咱們來瞻仰一下大神,哈哈:apache
其實在Ceki設計的體系下,日誌如同Java的JDBC、Servelt等同樣,定義好標準後實現能夠互相切換,問題在於定標準的人各自爲政搞出來好多標準,JCL、SLF4j等等,官方(Sun公司)又晚又不給力,發展到如今終於被SLF4j以一種巧妙的方式(橋接、綁定,見下文)統一了,標準使用方式以下圖:編程
這個圖截取自slf4j手冊,簡化了多餘部分,很清晰的表示了使用方式:安全
應用引用SLF4j-API(編碼時使用SLF4j的接口org.slf4j.Logger
,而非logback或log4j的實現)框架
具體依賴以下maven
logbackspring-boot
<dependency> <groupid>ch.qos.logback</groupid> <artifactid>logback-classic</artifactid> <version>1.2.3</version> </dependency>
log4j2工具
<dependency> <groupid>org.apache.logging.log4j</groupid> <artifactid>log4j-slf4j-impl</artifactid> <version>2.12.1</version> </dependency> <dependency> <groupid>org.apache.logging.log4j</groupid> <artifactid>log4j-core</artifactid> <version>2.12.1</version> </dependency>
得益於maven的依賴傳遞機制,咱們不須要顯示聲明依賴SLF4j-API.jar。
能夠看到,log4j 多依賴了 log4j-slf4j-impl.jar,其實就是上圖所示的適配器層,讀者可能好奇,爲何使用 log4j 會有適配器層?其緣由在於,slf4j 並非官方規範,因此沒人遵照(也就是本身的日誌框架中沒有原生實現org.slf4j.Logger
接口,如 log4j ),而綁定層( log4j-slf4j-impl.jar)的做用就是經過靜態查找的方式將使用log4j做爲實現(具體原理請關注後續文章),這樣就是實現了不依賴log4j而使用log4j輸出日誌(面向接口編程的最佳實踐,Ceki 大神就是用這套思想將 slf4j 作成了 Java 日誌的標準,爛牌翻盤的典範)。
上面這一段講解了綁定(concrete-bindings)思想,是本文的精髓,讀者必定要理解這裏,後面還有橋接思想與之相似,請繼續閱讀。
至此咱們已經完成了日誌的整合,可是事情真的這麼簡單嗎?
先梳理一下,如此混亂的日誌體系下(slf4j,jul,jcl,logback,log4j)會不會會產生什麼問題?答案是必定的,各類第三方庫使用了不一樣的日誌框架,若是咱們依賴 Spring ,Spring(非boot)的默認日誌實現是JCL、又或者咱們已有項目已經使用了Log4j,想使用logback的話,難道要逐個類改代碼嗎(官方有遷移工具)?咱們能不能只用一種框架來處理JUL(java.util.logging)、JCL(Jakarta Commons Logging)、Log4j一、Log4j2 呢?
答案是確定的,Ceki 的 Slf4j 給出瞭解決方案,就是上文所說的橋接( Bridging legacy),簡單來講就是劫持以上因此第三方日誌輸出並重定向至 SLF4j,最終實現統一日誌上層 API(編碼) 與下層實現(輸出日誌位置、格式統一)。咱們來看一下圖示:
上圖左側就是前一張圖的 logback 日誌實現,爲了兼容其餘日誌,咱們須要引用右側的橋接包:xxx-over/to-slf4j.jar ,xxx對應日誌框架,使用 logback 的狀況下,除了上文的 logback 依賴,還須要引入如下依賴才能保證全部日誌都被橋接至slf4j。
如何橋接?
logback 以下
<dependency> <groupid>ch.qos.logback</groupid> <artifactid>logback-classic</artifactid> <version>1.2.3</version> </dependency> <dependency> <groupid>org.slf4j</groupid> <artifactid>jcl-over-slf4j</artifactid> </dependency> <dependency> <groupid>org.slf4j</groupid> <artifactid>jul-to-slf4j</artifactid> </dependency> <!-- log4j 橋接包,slf4j官方實現,另有log4j官方實現, 二選一便可 log4j-to-slf4j--> <dependency> <groupid>org.slf4j</groupid> <artifactid>log4j-over-slf4j</artifactid> </dependency>
log4j2 以下
<dependency> <groupid>org.apache.logging.log4j</groupid> <artifactid>log4j-slf4j-impl</artifactid> <version>2.12.1</version> </dependency> <dependency> <groupid>org.apache.logging.log4j</groupid> <artifactid>log4j-core</artifactid> <version>2.12.1</version> </dependency> <!-- 如下是橋接包,使用了log4j做爲底層實現, 不能再橋接log4j,不然會出現無限遞歸的狀況(具體緣由請關注後續文章) --> <dependency> <groupid>org.slf4j</groupid> <artifactid>jcl-over-slf4j</artifactid> </dependency><dependency> <groupid>org.slf4j</groupid> <artifactid>jul-to-slf4j</artifactid> </dependency>
SpringBoot 項目引用了一部分依賴,因此使用起來略微有些不一樣:
logback 以下
<!-- logback做爲內置實現,使用相對簡單 --> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter</artifactid> </dependency> <!-- 引入缺乏的橋接包 --> <dependency> <groupid>org.slf4j</groupid> <artifactid>jcl-over-slf4j</artifactid> </dependency>
log4j2 以下
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter</artifactid> <!-- 使用log4j2要排除logback依賴 --> <exclusions> <exclusion> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-logging</artifactid> </exclusion> </exclusions> </dependency> <!-- Spring已經寫好了一個log4j2-starter但缺乏橋接包 --> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-log4j2</artifactid> </dependency> <!-- 引入缺乏的橋接包 --> <dependency> <groupid>org.slf4j</groupid> <artifactid>jcl-over-slf4j</artifactid> </dependency>
以上兩種纔是項目中的最佳使用方式,其餘筆者不推薦使用。
最後來看一下 slf4j 如何使用:
static final org.slf4j.Logger logger = LoggerFactory.getLogger(TestLog.class); logger.trace("A TRACE Message"); logger.debug("A DEBUG Message"); logger.info("An INFO Message"); logger.warn("A WARN Message"); logger.error("An ERROR Message");
這樣使用咱們就能夠隨意切換日誌實現而無需改動代碼,操做起來也簡單,只須要按照上文切換依賴便可。至於其餘使用細節本文不在贅述,關注後續文章(最佳實踐、配置文件、原理、擴展等)。
若是以爲寫的不錯,求關注、求點贊、求轉發,若是有問題或者文中有錯誤,歡迎留言討論。
掃描關注公衆號,第一時間得到更新
參考:
轉載請註明出處。