带你理清 Java 混乱的日志体系 - log4j、logback、log4j2、jcl、SLFJ 究竟是啥关系?
1.JAVA混亂的日志體系
換亂的java日志體系
case:
SLF4J-JCL
LOG4J-CORE
LOGBACK
SLF4J-SIMPLE
JCL-OVER-SLF4J
LOGBACK-CORE
LOG4J
LOG4J-API
LOG4J-JUL
LOG4J-JCL
LOGBACK-ACCESS
LOGBACK-CLASSIC
SLF4-API
SLF4J-LOGJ12
LOGBACK-CLASSIC
LOG4J-SLF4J-IMPL
1.1 JAVA日志體系概述
問題:
常用的日志框架有哪些?
大家目前正在使用的
| log4j | log4j-1.2.17 | 早期常用的日志組件 |
| logback | logback-core,logback-classic,logback-access | 性能由于log4j |
| log4j2 | log4j,log4j-api,log4j-core | log4j升級 |
| Java.util.logging | jdk | jdk實現,tomcat默認實現 |
開發一個類似spring框架,或者開發一個組件,如何選擇
選擇任何一種實現,都不太好,不同的日志輸出不一樣,日志也會打印多份。如何解決,日志做抽象層
1.1.1 Apache Commons Logging(JCL)
1-官網介紹 記錄組件 在編寫一個庫時,記錄信息是非常有用的。然而,外面有很多日志實現,一個庫不能把某一個特定的日志實現強加給作為庫一部分的整體應用。Logging包是不同日志實現之間的一個超薄橋梁。一個使用commons-logging API的庫可以在運行時與任何日志實現一起使用。Common-logging自帶對許多流行的日志實現的支持,為其他的日志實現編寫適配器是一項相當簡單的任務。應用程序(而不是庫)也可以選擇使用commonons-logging。雖然日志記錄實現的獨立性對應用程序來說不像庫那樣重要,但使用commons-logging確實允許應用程序在不重新編譯代碼的情況下改變到不同的日志實現。請注意,commons-logging不會嘗試初始化或終止運行時使用的底層日志實現,這是應用程序的責任。然而,許多常用的日志實現都會自動初始化;在這種情況下,應用程序可以避免包含任何特定于所使用的日志實現的代碼。 2-實現原理 org.apache.commons.logging.impl.LogFactoryImpl#discoverLogImplementation 3-缺點,只能實現一種,通過靜態綁定實現,不易擴展,適配器模式。1.1.2 SLFJ
slf4j全 稱為Simple Logging Facade for JAVA,java簡單日志門面。類似于Apache Common-Logging,是對不同日志框架提供的一個門面封裝(是接口而非實現),可以在部署的時候不修改任何配置即可接入一種日志實現方案。但是,他在編譯時靜態綁定真正的Log庫。使用SLF4J時,如果需要使用某一種日志實現,那么你必須選擇正確的SLF4J的jar包的集合(各種橋接包)。 ----基于OSGI模塊化框架詳解 特點: 動態加載、更新、和卸載模塊而不用停止服務 實現系統的模塊化、版本化,允許多版本bundule(模塊)同時服務--注釋,很多三方jar中的MANIFEST.MF進行子描述,就是一個例子Manifest-Version: 1.0Archiver-Version: Plexus ArchiverCreated-By: Apache MavenBuilt-By: cekiBuild-Jdk: 1.8.0_121Bundle-Description: The slf4j APIBundle-Version: 1.7.30Implementation-Version: 1.7.30X-Compile-Source-JDK: 1.5X-Compile-Target-JDK: 1.5Implementation-Title: slf4j-apiBundle-ManifestVersion: 2Bundle-SymbolicName: slf4j.apiBundle-Name: slf4j-apiBundle-Vendor: SLF4J.ORGBundle-RequiredExecutionEnvironment: J2SE-1.5Automatic-Module-Name: org.slf4jExport-Package: org.slf4j;version=1.7.30, org.slf4j.spi;version=1.7.30, org.slf4j.helpers;version=1.7.30, org.slf4j.event;version=1.7.30Import-Package: org.slf4j.impl;version=1.6.0Service model允許模塊、插件相互依賴但松耦合,分享服務更簡單1.2 常用的日志組成方案與應用場景
目前最優的方案:slf4j的異步模式+log4j2,性能好
例如,springboot+mybatis+slf4j+log4j2
1.2.1 jcl
1.2.2 slfj
jcl本來就是抽象的,slf4j能橋接jcl,是為了能夠適配。
1.2.3 日志切換,適配器
如果當前系統之中再用jcl打印日志,比如spring4,但這是想加入slfj來打印日志,就會出現兩類日志輸出,如何既覺只要classpath當中指定了slfj適配器,包,即可無縫江源日志輸出轉移到slfj上來 jcl-over-slfj:轉移 jcl日志至slf4j舉例子: 組件1:mybatis---jcl+log4j 組件2:springboot----sjlf+logback 組件2:shiro----jcl+logback 默認:jcl+jul 期望結果:slfj+logback1:slfj+橋接器+實現log4j-over-slfj:轉移log4j日志至slf4jjul-over-slfj:轉移jul日志至slf4j| 門面 | slf4jAPI接口 | slf4j-api.jar |
| 橋接 | 用于slf4j連接對應日志實現 | slfj-log4j12.jar,slfj-jdk14.jar,log4j-slf4j-impl,logback-classic,slf4j-jcl.jar |
| 適配器 | 用于將原日志輸出無縫轉移到slf4j | cl-over-slf4j.jar,log4j-over-slfj,jul-over-slfj, |
| 具體實現 | 日志的具體實現 | log4j.jar,logback,log4j2,java.util.logging |
1.2.4 循環依賴
如果clappsth中既有橋接器也有適配器,日志會被踢來踢去,陷入死循環
1.3.slfj+log4j2 統一系統應用日志
由于系統組件中,可能采用了不同的日志體系,spring5之前,spring采用的是apache-common-log,spring5之后,采用spring-jcl
1.4 日志規范
1. 【強制】應用中不可直接使用日志系統(Log4j、Logback)中的 API,而應依賴使用日志框架 (SLF4J、JCL--Jakarta Commons Logging)中的 API,使用門面模式的日志框架,有利于維護和 各個類的日志處理方式統一。 說明:日志框架(SLF4J、JCL--Jakarta Commons Logging)的使用方式(推薦使用 SLF4J)使用 SLF4J: import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger logger = LoggerFactory.getLogger(Test.class); 使用 JCL: import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; private static final Log log = LogFactory.getLog(Test.class); 2. 【強制】所有日志文件至少保存 15 天,因為有些異常具備以“周”為頻次發生的特點。對于 當天日志,以“應用名.log”來保存,保存在/home/admin/應用名/logs/</font>目錄下, 過往日志格式為: {logname}.log.{保存日期},日期格式:yyyy-MM-dd 說明:以 mppserver 應用為例,日志保存在/home/admin/mppserver/logs/mppserver.log,歷史日志 名稱為 mppserver.log.2016-08-01 3. 【強制】應用中的擴展日志(如打點、臨時監控、訪問日志等)命名方式: appName_logType_logName.log。logType:日志類型,如 stats/monitor/access 等;logName:日志描 述。這種命名的好處:通過文件名就可知道日志文件屬于什么應用,什么類型,什么目的,也有利于歸類查 找。 說明:推薦對日志進行分類,如將錯誤日志和業務日志分開存放,便于開發人員查看,也便于通過日志對系 統進行及時監控。 正例:mppserver 應用中單獨監控時區轉換異常,如:mppserver_monitor_timeZoneConvert.log 4. 【強制】在日志輸出時,字符串變量之間的拼接使用占位符的方式。 說明:因為 String 字符串的拼接會使用 StringBuilder 的 append()方式,有一定的性能損耗。使用占位符僅 是替換動作,可以有效提升性能。 正例:logger.debug("Processing trade with id: {} and symbol: {}", id, symbol); 5. 【強制】對于 trace/debug/info 級別的日志輸出,必須進行日志級別的開關判斷。 說明:雖然在 debug(參數)的方法體內第一行代碼 isDisabled(Level.DEBUG_INT)為真時(Slf4j 的常見實現 Log4j 和 Logback),就直接 return,但是參數可能會進行字符串拼接運算。此外,如果 debug(getName()) 這種參數內有 getName()方法調用,無謂浪費方法調用的開銷。 正例: // 如果判斷為真,那么可以輸出 trace 和 debug 級別的日志 if (logger.isDebugEnabled()) {logger.debug("Current ID is: {} and name is: {}", id, getName()); } 6. 【強制】避免重復打印日志,浪費磁盤空間,務必在 log4j.xml 中設置 additivity=false。 正例:<logger name="com.taobao.dubbo.config" additivity="false">case:<logger name="com.jd" level="DEBUG"><AppenderRef ref="console"/></logger>7. 【強制】生產環境禁止直接使用 System.out 或 System.err 輸出日志或使用 e.printStackTrace()打印異常堆棧。 說明:標準日志輸出與標準錯誤輸出文件每次 Jboss 重啟時才滾動,如果大量輸出送往這兩個文件,容易 造成文件大小超過操作系統大小限制。 8. 【強制】異常信息應該包括兩類信息:案發現場信息和異常堆棧信息。如果不處理,那么通過 關鍵字 throws 往上拋出。 正例:logger.error(各類參數或者對象 toString() + "_" + e.getMessage(), e); case:不允許記錄日志后又拋出異常,因為這樣會多次記錄日志,只允許記錄一次日志 9. 【強制】日志打印時禁止直接用 JSON 工具將對象轉換成 String。 說明:如果對象里某些 get 方法被重寫,存在拋出異常的情況,則可能會因為打印日志而影響正常業務流 程的執行。 正例:打印日志時僅打印出業務相關屬性值或者調用其對象的 toString()方法。 10.【推薦】謹慎地記錄日志。生產環境禁止輸出 debug 日志;有選擇地輸出 info 日志;如果使用 warn 來記錄剛上線時的業務行為信息,一定要注意日志輸出量的問題,避免把服務器磁盤撐 爆,并記得及時刪除這些觀察日志。 說明:大量地輸出無效日志,不利于系統性能提升,也不利于快速定位錯誤點。記錄日志時請思考:這些 日志真的有人看嗎?看到這條日志你能做什么?能不能給問題排查帶來好處? 11.【推薦】可以使用 warn 日志級別來記錄用戶輸入參數錯誤的情況,避免用戶投訴時,無所適 從。如非必要,請不要在此場景打出 error 級別,避免頻繁報警。 說明:注意日志輸出的級別,error 級別只記錄系統邏輯出錯、異常或者重要的錯誤信息。 12.【推薦】盡量用英文來描述日志錯誤信息,如果日志中的錯誤信息用英文描述不清楚的話使用 中文描述即可,否則容易產生歧義。 說明:國際化團隊或海外部署的服務器由于字符集問題,使用全英文來注釋和描述日志錯誤信息。 if (logger.isDebugEnabled()) {logger.debug(); }1.5 性能測試
1.5.1 單線程
| log4j | 87.342秒 | 10.757秒 | 91.752 | 10.058秒 |
| logback | 81.617 | 5.547 | 100.245 | 10.69 |
| log4j2 | 5.272/5.614/5.196 | 5.502/5.53/5.453 | 5.423/5.378/4.953 | 5.063/4.74/5.246(AsyncRoot)==>5.348/5.712/4.818(Async) |
1.5.2 多線程
| log4j | 102.823 | 13.324 | 87.966 | 10.651 |
| logback | 100.853 | 8.238 | 111.272 | 55.813 |
| log4j2 | 8.164/6.94/7.073 | 8.02/6.597/8.009 | 8.11/7.485 | 8.178/8.226 |
架構圖對比:
log4j2-AsyncLogger
1.6 日志的全鏈路追蹤 簡介
1.6.1 全鏈路追蹤的解決方案
全鏈路追蹤的背景
上圖是一個典型的微服務調用鏈路,面對的場景問題如下:
1-如果D服務是一個關鍵服務,返回結果錯誤,無論是日志,還是監控平臺,并不能很快捷的定位問題出現在了那里,因為不能串聯整個調用鏈路的流程
2-當對某一個服務架構升級或者改造的時候,不好評估影響范圍,不明確服務之間的依賴關系,給技術決策帶來了困難
3-性能瓶頸,整個調用鏈路那個環節耗時比較久
4-當一次請求結束后,不好確定執行順序,都給業務邏輯上的理解帶來了困難
需要解決問題:
1-串用調用鏈,快速定位問題
2-厘清服務依賴關系
3-進行各個服務接口的性能分析
4-跟蹤業務流的處理順序
已有方案
1-Google Dapper
2-Twitter Zipkin
3-Spring Cloud Sleuth
? 3-1 與springboot及spring組件無縫集成
? 3-2支持Zipkin輸出(mysql,es)
? 3-3 支持MQ和HTTP方式傳輸
1.6.2 Spring Cloud Sleuth 介紹
基本概念:
? Trace(鏈路)
? Span(跨度)
? Annotation(標注):CS(發送請求),SR(接受請求),SS(相應發送),CR(相應被客戶端接收)
架構圖
流程圖
基本概念
1.6.3 分布式日志檢索解決方案-ELK
E:Elasticsearch —存儲
L:LogStash —收集
K:Kibana —展示
流程
補充
JCL
slf4j
+jcl 或其它
總結
以上是生活随笔為你收集整理的带你理清 Java 混乱的日志体系 - log4j、logback、log4j2、jcl、SLFJ 究竟是啥关系?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 左神算法课笔记(二):链表、栈和队列、递
- 下一篇: Java序列化 - 分析不同序列化方式的