一:日志基本概念及框架
1:什么是日志
Java程序員在開發項目時都是依賴Eclipse/IDEA等集成開發工具的Debug調試功能來跟蹤解決Bug,但項目打包部署發布到了測試環境和生產環境怎么辦?難道連接到生產服務器裝個IDEA做遠程調試,實際並不能允許讓你這么做。所以,日志的作用就是在測試環境和生產環境沒有Debug調試工具時為開發人員和測試人員定位問題的手段。日志打得好,就能根據日志的軌跡快速定位並解決線上問題,反之,日志輸出不好,不僅無法輔助定位問題反而可能會影響到程序的運行性能和穩定性。
很多介紹 AOP 的地方都采用日志來作為介紹,實際上日志要采用切面的話是極其不科學的!對於日志來說,只是在方法開始、結束、異常時輸出一些日志信息,那是絕對不夠的,這樣的日志對於日志分析沒有任何意義。如果在方法的開始和結束記錄個日志,那方法中呢?如果方法中沒有日志的話,那就完全失去了日志的意義!如果應用出現問題要查找由什么原因造成的,也沒有什么作用。這樣的日志還不如不用!
2:日志的作用
不管是使用何種編程語言,日志輸出幾乎無處不再。總結起來,日志大致有以下幾種用途: 「問題追蹤」:輔助排查和定位線上問題,優化程序運行性能。 「狀態監控」:通過日志分析,可以監控系統的運行狀態。 「安全審計」:審計主要體現在安全上,可以發現非授權的操作。
日志在應用程序中是非常重要的,好的日志信息能有助於我們在程序出現BUG時能快速進行定位,並能找出其中的原因。
3:常用日志框架
按照出來的時間順序排列:Log4j(reload4j) --> JUL --> JCL --> SLF4J --> Logback --> Log4j2
二:JUL日志框架
JUL全稱Java Util Logging,是Java原生的日志框架,使用時不需要另外引入第三方類庫,相對於其他日志框架來說其特點是使用方便,能夠在小型應用中靈活應用。但JUL日志框架使用的頻率並不高,但一旦需要解除此類的代碼,仍然要求開發人員能夠迅速看懂代碼,並理解。
1:框架結構圖
Loggers: 被稱為記錄器,應用程序通過獲取Logger對象,調用其API來發布日志信息,Logger通常是應用程序訪問日志系統的入口程序; Appenders: 也被稱為Handlers,每個Logger都會關聯一組Handlers,Logger會將日志交給關聯的Handlers處理,由Handlers負責將日志記錄; Handlers在此是一個抽象類,由其具體的實現決定日志記錄的位置是控制台、文件、網絡上的其他日志服務異或是操作系統日志; Layouts: 也被稱為Formatters,它負責對日志事件中的數據進行轉換和格式化,Layouts決定了記錄的數據在一條日志記錄中的最終顯示形式; Level: 每條日志消息都有一個關聯的日志級別。該級別粗略指導了日志消息的重要性和緊迫,可以將Level和Loggers或Appenders做關聯以便於我們
過濾消息; Filters: 過濾器,根據需要定制哪些信息會被記錄,哪些信息會被放過。 一個完整的日志記錄過程如下: 用戶使用Logger來進行日志記錄的行為,Logger可以同時持有若干個Handler,日志輸出操作是由Handler完成的;在Handler輸出日志前,
會經過Filter的過濾,判斷哪些日志級別放行、哪些攔截,Handler會將日志內容輸出到指定位置(日志文件、控制台等);Handler在輸出日
志時會使用Layout對日志內容進行排版,之后再輸出到指定的位置。
2:入門案例
@Test public void demoA() { // 1.創建JUL Logger對象無法傳入class對象,只能傳如:"cn.xw.testDemo" Logger logger = Logger.getLogger(TestDemo.class.getName()); // 2.輸出日志的兩種方式,其中日志級別共有7種,還有2種特殊級別 logger.severe("[SEVERE 1000] 表明程序嚴重的失敗或錯誤"); logger.warning("[WARNING 900] 表明潛在的問題"); // 帶有占位符的日志打印 String name = "螞蟻小哥", age = "24"; logger.log(Level.INFO, "姓名:{0} 年齡:{1}", new Object[]{name, age}); }
3:日志級別
JUL內置工七種日志級別,另外有兩種特殊級別(共9個) java.util.logging.Level中定義了七種基本的日志級別: SEVERE【致命級別 級別碼:1000】(最高級別):表明程序嚴重的失敗或錯誤 WARNING【警告級別 級別碼:900】:表明潛在的問題 INFO【信息級別 級別碼:800】(默認):通過消息會被輸入到控制台,所以信息級別一般記錄對最終用戶或系統管理員有用的信息 CONFIG【配置級別 級別碼:700】:用於表明系統靜態配置的信息 FINE【調試級別 級別碼:500】:輸出開發人員關注的信息 FINER【調試級別 級別碼:400】:輸出進入方法或者出入方法的一些信息和異常捕捉 FINEST【調試級別 級別碼:300】(最低等級):輸出最細的信息 java.util.logging.Level中定義了兩種基本的日志級別: OFF:可用於關閉日志記錄 ALL:啟用所有消息的日志記錄 注:默認的日志日志級別由RootLogger決定的所有的Logger對象默認都會繼承並使用RootLogger所提供的控制台輸出處理器 對象ConsoleHandler同時,RootLogger的默認日志輸出等級為INFO,則所有未經配置的Logger默認也是使用該日志級別。 跟蹤代碼: Logger logger = Logger.getLogger(TestDemo.class.getName()); ==>進入getLogger return demandLogger(name, null, Reflection.getCallerClass()); ==>進入demandLogger LogManager manager = LogManager.getLogManager(); ==>進入getLogManager manager.ensureLogManagerInitialized(); ==>進入ensureLogManagerInitialized owner.readPrimordialConfiguration(); //讀取配置文件 logging.properties owner.rootLogger = owner.new RootLogger(); owner.addLogger(owner.rootLogger); //判斷日志級別不存在的話則默認defaultLevel // private final static Level defaultLevel = Level.INFO; //默認就是INFO if (!owner.rootLogger.isLevelInitialized()) { owner.rootLogger.setLevel(defaultLevel); }
4:自定義日志(控制台記錄和日志文件記錄)
自定義日志通常會在單獨的配置文件中去配置Logger的日志等級和處理器類型等,但作為入門,需要了解如何在Java代碼中,通過更改Logger日志級別和配置自定義ConsoleHandler的方式,去影響日志輸出。如果不希望Logger對象使用RootLogger中的日志級別進行輸出,則需要對Logger進行以下配置:重新設置Logger的日志輸出等級;重新配置Logger的處理器Handler類型,並不再使用RootLogger中提供的默認處理器。
@Test public void demoA() throws IOException { //獲取日志記錄器 Logger logger = Logger.getLogger(TestDemo.class.getName()); //設置false;指定此記錄器是否應將其輸出發送到其父記錄器(是否繼承父記錄器配置)(必須設置) logger.setUseParentHandlers(false); //創建日志格式化組件對象 //若想自定義格式化則通過simpleFormatter.format(java.util.logging.LogRecord record) SimpleFormatter simpleFormatter = new SimpleFormatter(); //創建Handler(ConsoleHandler為控制台輸出處理器) ConsoleHandler consoleHandler = new ConsoleHandler(); //創建文件處理器Handler fileLog.log文件名稱 FileHandler fileHandler = new FileHandler("./fileLog.log"); //設置處理器中的內容輸出格式及輸出級別 consoleHandler.setFormatter(simpleFormatter); consoleHandler.setLevel(Level.CONFIG); fileHandler.setFormatter(simpleFormatter); fileHandler.setLevel(Level.CONFIG); //設置記錄器中的處理器(控制台記錄器、文件記錄器)及記錄器的輸出級別 logger.addHandler(consoleHandler); logger.addHandler(fileHandler); logger.setLevel(Level.CONFIG); //輸出打印 logger.severe("[SEVERE 1000] 表明程序嚴重的失敗或錯誤"); logger.warning("[WARNING 900] 表明潛在的問題"); logger.info("[INFO 800] 信息級別,通過消息會被輸入到控制台,所以信息級別一般記錄對最終用戶或系統管理員有用的信息"); logger.config("[CONFIG 700] 配置級別,用於表明系統靜態配置的信息"); //這時默認級別為 CONFIG logger.fine("[FINE 500] 輸出開發人員關注的信息"); logger.finer("[FINER 400] 輸出進入方法或者出入方法的一些信息和異常捕捉"); logger.finest("[FINEST 300] 輸出最細的信息"); }
5:JUL(Logging)父子關系
從上面我們可以看出,默認日志級別為INFO,但是更改日志級別為CONFIG時輸出還是INFO,未起效果;這是因為我們創建Logger對象時繼承並使用了RootLogger中的日志等級和處理器對象(子類無法覆蓋父類設置的默認配置)
我們可以通過如下代碼來看出Logger日志的繼承關系,繼承關系就由獲取getLogger("xxx.xxx.xxx")中的點來區分繼承關系
@Test public void demoA(){ //創建3個日志記錄器分別是 RootManger -> logger1 -> logger2 //子logger也間接繼承RootManger Logger logger1 = Logger.getLogger("cn.xw"); Logger logger2 = Logger.getLogger("cn.xw.TestDemo"); //對比繼承關系 System.out.println(logger2.getParent() == logger1); // true //這里我針對 logger1 進行了自定義日志記錄編寫(所以logger2默認繼承 logger1才是正確的) // logger1 不在繼承RootLogger 所以繼承關系就變為 logger1 -> logger2 logger1.setUseParentHandlers(false); SimpleFormatter simpleFormatter = new SimpleFormatter(); ConsoleHandler consoleHandler = new ConsoleHandler(); consoleHandler.setFormatter(simpleFormatter); consoleHandler.setLevel(Level.CONFIG); logger1.addHandler(consoleHandler); logger1.setLevel(Level.CONFIG); //logger2記錄器記錄日志 logger2.severe("[SEVERE 1000] 表明程序嚴重的失敗或錯誤"); logger2.warning("[WARNING 900] 表明潛在的問題"); logger2.info("[INFO 800] 信息級別,通過消息會被輸入到控制台,所以信息級別一般記錄對最終用戶或系統管理員有用的信息"); logger2.config("[CONFIG 700] 配置級別,用於表明系統靜態配置的信息"); //這是要設置的級別為 CONFIG logger2.fine("[FINE 500] 輸出開發人員關注的信息"); logger2.finer("[FINER 400] 輸出進入方法或者出入方法的一些信息和異常捕捉"); logger2.finest("[FINEST 300] 輸出最細的信息"); }
6:JUL使用自定義配置文件
代碼去顯示加載logging.properties文件以啟用自定義的配置。
Handler是單獨進行配置的,開發人員可以單獨定義控制台輸出日志的處理器對象ConsoleHandler或文件輸出日志的處理器對象FileHandler等;具備相關自定義Handler后,需要將Logger與Handler進行關聯,配置文件支持RootLogger或指定名稱的Logger與自定義Handler進行關聯;無論任何時候,都需要明確日志最終的輸出等級,是同時由Logger與其相關聯的Handler所決定的。

@Test public void demoA() throws IOException { //獲取LogManager,LogManager是單例對象 LogManager logManager = LogManager.getLogManager(); //讀取配置文件,並加載應用配置文件logging.properties InputStream resourceAsStream = TestDemo.class.getClassLoader().getResourceAsStream("logging.properties"); logManager.readConfiguration(resourceAsStream); //獲取日志記錄器 Logger logger = Logger.getLogger("cn.xw.TestDemo"); //輸出打印 logger.severe("[SEVERE 1000] 表明程序嚴重的失敗或錯誤"); logger.warning("[WARNING 900] 表明潛在的問題"); logger.info("[INFO 800] 信息級別,通過消息會被輸入到控制台,所以信息級別一般記錄對最終用戶或系統管理員有用的信息"); logger.config("[CONFIG 700] 配置級別,用於表明系統靜態配置的信息"); logger.fine("[FINE 500] 輸出開發人員關注的信息"); logger.finer("[FINER 400] 輸出進入方法或者出入方法的一些信息和異常捕捉"); logger.finest("[FINEST 300] 輸出最細的信息"); //這時默認級別為 ALL System.out.println("============================================="); //指定自定義日志對象的名稱,配置文件中對com.anhui名稱的Logger進行了特殊配置 自定義 Logger loggerCustomize = Logger.getLogger("com.anhui"); loggerCustomize.severe("[SEVERE 1000] 表明程序嚴重的失敗或錯誤"); loggerCustomize.warning("[WARNING 900] 表明潛在的問題"); loggerCustomize.info("[INFO 800] 信息級別,通過消息會被輸入到控制台,所以信息級別一般記錄對最終用戶或系統管理員有用的信息"); loggerCustomize.config("[CONFIG 700] 配置級別,用於表明系統靜態配置的信息"); //這時默認級別為 CONFIG loggerCustomize.fine("[FINE 500] 輸出開發人員關注的信息"); loggerCustomize.finer("[FINER 400] 輸出進入方法或者出入方法的一些信息和異常捕捉"); loggerCustomize.finest("[FINEST 300] 輸出最細的信息"); }
三:Log4j日志框架
Log4j全稱是Log for Java,它是Apache的一個開源項目,通過使用Log4j,我們可以控制日志信息輸出的位置是控制台、文件還是GUI組件,輸出位置甚至可以是套接口服務器、NT的事件記錄器、UNIX Syslog守護進程等;Log4j也可以控制每一條日志的輸出格式;通過定義每一條日志信息的級別,我們能夠更加細致地控制日志的生成過程。學習Log4j最好有JUL日志框架基礎,因為大部分都類似。
1:入門案例
<!--Log4j日志坐標導入--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
//具體測試代碼Demo @Test public void demoA() { //創建日志記錄器對象 注意是 org.apache.log4j.Logger 包下的 Logger logger = Logger.getLogger(ClientDemo.class); //打印日志 logger.fatal("嚴重錯誤,一般造成系統崩潰並終止運行"); logger.error("錯誤信息,不會影響系統運行"); logger.warn("警告信息,可能會發生問題"); logger.info("運行信息,數據連接,網絡連接,IO操作等"); logger.debug("調試信息,一般在開發中使用,記錄程序變量傳遞信息等等"); logger.trace("追蹤信息,記錄程序所有的流程信息"); } 控制台並未打印日志且打印警告: log4j:WARN No appenders could be found for logger (cn.xw.ClientDemo). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
用過JUL的都知道,當普通的Logger沒有進行額外的配置時,其默認會繼承並使用RootLogger的配置。Log4j中也存在RootLogger, 但由於默認情況下RootLogger不具有任何的Appender(即Handler)。 如果代碼僅為了測試某項功能,並不想編寫復雜的log4j.properties,可以使用Log4j提供的默認配置,在獲取Logger前添加以下代碼加載
默認配置:BasicConfigurator.configure();
查看configure()方法的源碼: public static void configure() { Logger root = Logger.getRootLogger(); root.addAppender(new ConsoleAppender(new PatternLayout("%r [%t] %p %c %x - %m%n"))); } 早期的源碼格式化有點不太現代,但意義明確:為RootLogger對象添加一個Appender,其中Appender的類型為控制器輸出的ConsoleAppender 輸出的格式使用new PatternLayout("%r [%t] %p %c %x - %m%n") 所以應急解決方案是在 Logger logger = Logger.getLogger(ClientDemo.class); 上加個 BasicConfigurator.configure();
2:日志級別
Log4j中的日志級別與JUL的不同,Log4j一共提供了六種日志級別: FATAL【嚴重錯誤】 指出每個嚴重的錯誤事件將會導致應用程序的退出。這個級別比較高了。重大錯誤,這種級別你可以直接停止程序了 ERROR【錯誤信息】: 指出雖然發生錯誤事件,但仍然不影響系統的繼續運行。打印錯誤和異常信息,如果不想輸出太多的日志,可以使用這個級別 WARN【警告信息】: 表明會出現潛在錯誤的情形,有些信息不是錯誤信息,但是也要給程序員的一些提示 INFO【運行信息】: 消息在粗粒度級別上突出強調應用程序的運行過程。打印一些你感興趣的或者重要的信息,這個可以用於生產環境中輸出程序 運行的一些重要信息,但是不能濫用,避免打印過多的日志。 DEBUG【調試信息】【默認級別】: 指出細粒度信息事件對調試應用程序是非常有幫助的,主要用於開發過程中打印一些運行信息 TRACE【追蹤信息】: 記錄程序所有的流程信息,很低的日志級別,一般不會使用 ALL:最低等級的,用於打開所有日志記錄。 OFF:最高等級的,用於關閉所有日志記錄。 為了測試默認日志級別,可以使用以下代碼測試RootLogger: @Test public void demoA () { // 初始化系統配置,不需要配置文件(這里直接使用默認配置) BasicConfigurator.configure(); // 獲取日志記錄器對象RootLogger final Logger rootLogger = Logger.getRootLogger(); // 輸出配置詳情 System.out.println("Logger level: " + rootLogger.getLevel()); // 獲取全部Appends final Enumeration allAppenders = rootLogger.getAllAppenders(); while (allAppenders != null && allAppenders.hasMoreElements()) { final Appender appender = (Appender) allAppenders.nextElement(); System.out.println("Appender is: " + appender.getClass().getSimpleName()); } } //輸出: //Logger level: TRACE //Appender is: ConsoleAppender
3:Log4j相關組件(重點)
Log4j主要由Loggers(日志記錄器)、Appenders(輸出端)和Layout(日志格式化器)組成: Loggers:控制日志的輸出級別與日志是否輸出 Appenders:指定日志的輸出方式(輸出到控制台、文件、數據庫等) Layout:控制日志信息的輸出格式 Ⅰ:Logger ①:日志記錄器,負責收集處理日志記錄,Logger的實例命名通常是類的全限定類名。 ②:Logger的名字大小寫敏感,其命名有繼承機制。 例如:name為org.apache.commons的logger會繼承name為org.apache的logger。 注:自log4j 1.2版以來, Logger類已經取代了Category類。對於熟悉早期版本的log4j的人來說,Logger類可以被視為Category類的別名 Ⅱ:Appenders Appender用來指定日志輸出到哪個地方,可以同時指定日志的輸出目的地。 Log4j常用的輸出目的地有以下幾種: ConsoleAppender: 將日志輸出到控制台 FileAppender: 將日志輸出到文件中 DailyRollingFileAppender: 將日志輸出到一個日志文件,周期為天,即每天輸出 RollingFileAppender: 將日志信息輸出到一個日志文件,並且指定文件的大小,當超過指定大小,會自動將文件重命名,同時產生一個新的文件 JDBCAppender: 將日志信息保存到數據庫中 Ⅲ:Layouts 布局器Layouts用於控制日志輸出內容的格式,我們可以使用各種自定義格式輸出日志。 Log4j常用的Layouts有以下幾種: HTMLLayout: 格式化日志輸出為HTML表格形式 SimpleLayout: 簡單的日志輸出格式,打印的日志格式為info-message PatternLayout: 最強大的格式化方式,可以根據自定義格式輸出日志,如果沒有指定轉換格式,則使用默認的轉換格式 補充:PatternLayout中的格式化規則: log4j采用類似C語言的printf函數的打印格式格式化日志信息,具體的占位符及其含義如下: %m 輸出代碼中指定的日志信息 %p 輸出優先級,及DEBUG、INFO等 %n 換行符(Windows平台的換行符為"\n",Unix平台為"\n") %r 輸出自應用啟動到輸出該 log 信息耗費的毫秒數 %c 輸出打印語句所屬的類的全名 %t 輸出產生該日志的線程全名 %d 輸出服務器當前時間,默認為ISO8601,也可以指定格式,如:%d{yyyy年MM月dd日 HH:mm:ss} %l 輸出日志時間發生的位置,包括類名、線程、及在代碼中的行數。如:Test.main(Test.java:10) %F 輸出日志消息產生時所在的文件名稱 %L 輸出代碼中的行號 %% 輸出一個"%"字符 可以在%與字符之間加上修飾符來控制最小寬度、最大寬度和文本的對其方式。如: %5c 輸出category名稱,最小寬度是5,category<5,默認的情況下右對齊 %-5c 輸出category名稱,最小寬度是5,category<5,"-"號指定左對齊,會有空格 %.5c 輸出category名稱,最大寬度是5,category>5,就會將左邊多出的字符截掉,<5不會有空格 %20.30c category名稱<20補空格,並且右對齊,>30字符,就從左邊交遠銷出的字符截掉
4:自定義配置文件
使用Log4j不需要特意加載配置文件,對於Maven項目來說,程序會自動掃描resources目錄下的log4j.properties配置文件。在下面的案例中我將針對Appenders的五種輸出端進行介紹

ConsoleAppender 對應自定義名稱:console FileAppender 對應自定義名稱:myFile RollingFileAppender 對應自定義名稱:rollingFile DailyRollingFileAppender 對應自定義名稱:dailyFile JDBCAppender 對應自定義名稱:dbFile
### 注:log4j.properties配置文件要放在resources目錄下 # 指定日志的輸出級別與輸出端(輸出級別,自定義輸出端名稱,自定義輸出端名稱....) log4j.rootLogger = trace,console,myFile,rollingFile,dailyFile,dbFile ###### 控制台輸出配置 log4j.appender.console = org.apache.log4j.ConsoleAppender # 使用PatternLayout來聲明日志用自定義格式 log4j.appender.console.layout = org.apache.log4j.PatternLayout # 針對PatternLayout來用conversionPattern設置格式 log4j.appender.console.layout.conversionPattern = %d [%t] %-5p [%c] - %m%n ###### 文件輸出配置 log4j.appender.myFile = org.apache.log4j.FileAppender log4j.appender.myFile.layout = org.apache.log4j.PatternLayout log4j.appender.myFile.layout.conversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n # 是否以追加日志的形式添加(默認就是true) log4j.appender.myFile.append = true # 指定日志的輸出路徑 log4j.appender.myFile.file = ./fileLog.log # 指定日志的文件編碼 log4j.appender.myFile.encoding = utf-8 ###### 文件自動按大小拆分配置 log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout log4j.appender.rollingFile.layout.conversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n log4j.appender.rollingFile.append = true log4j.appender.rollingFile.file = ./fileRollingFileLog.log log4j.appender.rollingFile.encoding = UTF-8 # 文件內容超過2KB則進行拆分,拆分的最多文件由maxBackupIndex定義 log4j.appender.rollingFile.maxFileSize = 2KB # 文件拆分的數量(這里是3),當文件拆分的數量超限時則最新拆分出的文件覆蓋最老的日志文件 log4j.appender.rollingFile.maxBackupIndex = 3 ###### 文件自動按日期拆分配置 log4j.appender.dailyFile = org.apache.log4j.DailyRollingFileAppender log4j.appender.dailyFile.layout = org.apache.log4j.PatternLayout log4j.appender.dailyFile.layout.conversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n log4j.appender.dailyFile.append = true log4j.appender.dailyFile.file = ./dailyRollingFile.log log4j.appender.dailyFile.encoding = UTF-8 # 文件拆分的日期 正常按照 '.'yyyy-MM-dd 以天為拆分,這里我以每秒拆分一次 log4j.appender.dailyFile.datePattern = '.'yyyy-MM-dd HH-mm-ss # MySQL輸出配置 log4j.appender.dbFile = org.apache.log4j.jdbc.JDBCAppender log4j.appender.dbFile.layout = org.apache.log4j.PatternLayout log4j.appender.dbFile.layout.conversionPattern = %p %r %c %t %d{yyyy/MM/dd HH:mm:ss:SSS} %m %l %F %L %% %n log4j.appender.dbFile.URL = jdbc:mysql://localhost:3306/log4j?serverTimezone=GMT%2B8&useAffectedRows=true&useSSL=false log4j.appender.dbFile.User = root log4j.appender.dbFile.Password = 123 log4j.appender.dbFile.Sql=INSERT INTO log(project_name, create_date, level, category, file_name, thread_name, line, all_category, message) \ values('log4j_xiaofeng', '%d{yyyy-MM-dd HH:mm:ss}', '%p', '%c', '%F', '%t', '%L', '%l', '%m')
-- 輸出MySQL配置需要在數據庫創建表
CREATE TABLE `log` ( `log_id` INT ( 11 ) NOT NULL AUTO_INCREMENT, `project_name` VARCHAR ( 255 ) DEFAULT NULL COMMENT '項目名稱', `create_date` VARCHAR ( 255 ) DEFAULT NULL COMMENT '創建時間', `level` VARCHAR ( 255 ) DEFAULT NULL COMMENT '優先級', `category` VARCHAR ( 255 ) DEFAULT NULL COMMENT '所在類的全名', `file_name` VARCHAR ( 255 ) DEFAULT NULL COMMENT '輸出日志消息產生時所在的文件名稱 ', `thread_name` VARCHAR ( 255 ) DEFAULT NULL COMMENT '日志事件的線程名', `line` VARCHAR ( 255 ) DEFAULT NULL COMMENT '行號', `all_category` VARCHAR ( 255 ) DEFAULT NULL COMMENT '日志事件的發生位置', `message` VARCHAR ( 4000 ) DEFAULT NULL COMMENT '輸出代碼中指定的消息', PRIMARY KEY ( `log_id` ) );
5:自定義配置Logger
如下我創建了兩個日志記錄器對象 Logger.getLogger("cn.xw.mapper.StudentMapper"); Logger.getLogger("cn.xw.mapper.TeacherMapper"); 現在要讓特定名稱的logger使用特定的配置: 通過log4j.logger.{loggerName}的配置方式,讓指定名為loggerName的logger使用該配置,由於該logger仍然是隸屬
於rootLogger因此輸出是累加的形式。如果RootLogger使用了ConsoleAppender,同時自定義的Logger也使用了
ConsoleAppender,此時控制台將輸出兩次日志記錄,一次為自定義Logger繼承自RootLogger的輸出,另一次則為自定義
Logger自身的輸出。但日志等級level則取決於子日志Logger為准。 例:此時RootLogger和Logger同時使用了ConsoleAppender,但輸出等級分別為INFO(父)和WARN(子),此時控制台輸出WARN等級
要求"cn.xw.mapper.StudentMapper"日志輸出到控制台;"cn.xw.mapper.TeacherMapper"日志輸出到文件
# 指定rootLogger根日志的輸出級別與輸出端 log4j.rootLogger = trace,console #注:下面兩個自定義的Logger都會繼承rootLogger,所以StudentMapper會輸出控制台2次,級別的話會取子級別”debug“ # 自定義Logger用來匹配 Logger.getLogger("cn.xw.mapper.StudentMapper"); log4j.logger.cn.xw.mapper.StudentMapper = debug,console # 自定義Logger用來匹配 Logger.getLogger("cn.xw.mapper.TeacherMapper"); log4j.logger.cn.xw.mapper.TeacherMapper = info,myFile # 控制台輸出Appender log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.conversionPattern = %d [%t] %-5p [%c] - %m%n # 文件輸出Appender log4j.appender.myFile = org.apache.log4j.FileAppender log4j.appender.myFile.layout = org.apache.log4j.PatternLayout log4j.appender.myFile.layout.conversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n log4j.appender.myFile.append = true log4j.appender.myFile.file = ./fileLog.log log4j.appender.myFile.encoding = utf-8

這時就有人問了,自定義的LoggerName是"cn.xw.mapper.StudentMapper"使用ConsoleAppender輸出了兩次,一次是繼承父Logger,一次是 自己原本的;若想輸出一次則有兩種方式: ①:摒棄RootLogger的輸出,即斷開指定Logger與RootLogger的繼承關系 # 自定義Logger用來匹配 Logger.getLogger("cn.xw.mapper.StudentMapper"); log4j.logger.cn.xw.mapper.StudentMapper = debug,console # 名為“cn.xw.mapper.StudentMapper”的Logger不再繼承使用RootLogger中的Appender log4j.additivity.cn.xw.mapper.StudentMapper = false ②:摒棄Logger的輸出,即指定名稱的Logger直接使用RootLogger關聯的Appender,子Logger不再額外指定和RootLogger相同的Appender # 自定義Logger用來匹配 Logger.getLogger("cn.xw.mapper.StudentMapper"); log4j.logger.cn.xw.mapper.StudentMapper = debug
6:日志輸出詳細信息
@Test public void demoA() { //設置內部調試模式,開啟此功能會在控制台詳細記錄Log4j在記錄日志時打印的日志,記錄自己 LogLog.setInternalDebugging(true); //創建日志記錄器對象 Logger logger = Logger.getLogger("cn.xw.mapper.StudentMapper"); logger.fatal("嚴重錯誤,一般造成系統崩潰並終止運行"); }
四:JCL日志門面框架
JCL(Jakarta Commons Logging)是Apache提供的一個通用日志API(日志門面)。用戶可以自由選擇第三方的日志組件作為具體實現,像log4j或者JDK自帶日志框架(JUL),選擇Log4j或JUL的第三方日志框架后,common-logging會通過動態查找的機制,在程序運行時自動找出真正使用的日志庫。當然common-logging內部有一個Simple logger 的簡單實現,但是功能很弱。所以使用common-logging,通常都是配合着log4j以及其它日志框架來使用。使用它的好處就是我們代碼依賴common-logging的API而非log4j的API,避免了和具體的日志API直接耦合,在有必要時,可以更改日志實現的第三方庫。
JCL有兩個基本抽象類:Log(基本記錄器)和LogFactory(負責創建Log實例)
1:JCL入門及總結
<!--JCL門面坐標依賴--> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> // 入門代碼 public class JCLClient { @Test public void demoA() { //創建日志對象;LogFactory是一個抽象類,LogFactoryImpl才是具體實現 Log logger = LogFactory.getLog(JCLClient.class); //日志記錄輸出 logger.fatal("嚴重錯誤,一般造成系統崩潰並終止運行"); logger.error("錯誤信息,不會影響系統運行"); logger.warn("警告信息,可能會發生問題"); logger.info("運行信息,數據連接,網絡連接,IO操作等"); // 默認級別INFO(和JUL默認級別一樣) logger.debug("調試信息,一般在開發中使用,記錄程序變量傳遞信息等等"); logger.trace("追蹤信息,記錄程序所有的流程信息"); } }
二月 09, 2022 9:49:11 下午 cn.xw.JCLClient demoA
嚴重: 嚴重錯誤,一般造成系統崩潰並終止運行
二月 09, 2022 9:49:11 下午 cn.xw.JCLClient demoA
嚴重: 錯誤信息,不會影響系統運行
二月 09, 2022 9:49:11 下午 cn.xw.JCLClient demoA
警告: 警告信息,可能會發生問題
二月 09, 2022 9:49:11 下午 cn.xw.JCLClient demoA
信息: 運行信息,數據連接,網絡連接,IO操作等
通過上面的入門案例我們可以發現,打印出來的日志和JUL的日志樣式是一樣的,那么我猜測它底層的日志實現使用的是JUL,因為我並未導入Log4j包,那導入了Log4j坐標是什么樣的呢?測試一下看看
<!--Log4j日志框架坐標--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> //導入后我們編寫的代碼不用改變,直接可以運行,日志打印出log4j:WARN No appenders could be found for logger (cn.xw.JCLClient). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.這就證明了,導入Log4j日志框架坐標后JCL會動態查找到Log4j並切換Log4j作為底層日志打印的第三方框架 這里在resources目錄下編寫一個log4j.properties配置文件即可
總結:我們為什么要使用日志門面: ①:面向接口開發,不再依賴具體的實現類。減少代碼的耦合 ②:項目通過導入不同的日志實現類,可以靈活的切換日志框架 ③:統一API,方便開發者學習和使用 ④:統一配置便於項目日志的管理
2:源碼分析
查看Log接口並光標對着Log接口按下CTRL+H來查看具體的實現類
AvalonLogger、Jdk13LumberjackLogger、Jdk14Logger、Log4JLogger、LogKitLogger、NoOpLog、SimpleLog
進入JDK14Logger類:
//注意這里引入的類是JDK自帶的日志JUL的Logger類 import java.util.logging.Level; import java.util.logging.Logger; public class Jdk14Logger implements Log, Serializable { protected static final Level dummyLevel = Level.FINE; protected transient Logger logger = null;//默認為null public Jdk14Logger(String name) { this.name = name; //調用了自身的getLogger()方法 logger = getLogger(); } //1、獲取Logger實例方法 public Logger getLogger() { if (logger == null) { //重要:這里調用的是java.util.logging.Logger(即JUL)的獲取實例方法 logger = Logger.getLogger(name); } return logger; } //info日志等級調用 public void info(Object message) { //實際上就是調用的JUL的INFO日志等級的Log方法 log(Level.INFO, String.valueOf(message), null); } //fatal:我們之前在Log4j中看到是最高等級,這里也用來表示最高的意思 public void fatal(Object message, Throwable exception) { //實際上就是調用JUL的SERVER日志等級(最高)的Log方法 log(Level.SEVERE, String.valueOf(message), exception); } }
看到這里其實心里就清楚了,原來就是外面套了個方法呀,通過繼承Log接口來讓Log接口統一管理各個日志框架,從而達到切換日志框架不改變代碼的操作。那對於JCL中Log4j的方法我們也差不多知道了是如何實現了的。

那為什么添加一個Log4j坐標底層就切換到了Log4j的日志框架輸出打印,去除Log4j坐標就變成JUL日志框架打印了,別急我們這就要查看LogFactory.getLog(xxx.class)的getLog方法了,通過它獲取不同的Log對象
// LogFactory抽象對象 public abstract class LogFactory { ①:獲取Log對象實例 public static Log getLog(Class clazz) throws LogConfigurationException { //獲取具體實例 return getFactory().getInstance(clazz); } } // LogFactoryImpl是LogFactory具體實現類 public class LogFactoryImpl extends LogFactory { ... //該數組中包含了對應的全限定類名 private static final String[] classesToDiscover = new String[] {"org.apache.commons.logging.impl.Log4JLogger", "org.apache.commons.logging.impl.Jdk14Logger", "org.apache.commons.logging.impl.Jdk13LumberjackLogger", "org.apache.commons.logging.impl.SimpleLog"}; ... ②:獲取Log實例 public Log getInstance(Class clazz) throws LogConfigurationException { //這里傳入類的名稱 return this.getInstance(clazz.getName()); } ... ③:其中獲取到Log實例 public Log getInstance(String name) throws LogConfigurationException { Log instance = (Log)this.instances.get(name); if (instance == null) { //通過類名獲取到Log實例 instance = this.newInstance(name); this.instances.put(name, instance); } return instance; } ... ④:獲取新的實例 protected Log newInstance(String name) throws LogConfigurationException { try { Log instance; Object[] params; //默認為null if (this.logConstructor == null) { //重要:查找Log實現類,及獲取Log的單個實現類 instance = this.discoverLogImplementation(name); } else { params = new Object[]{name}; instance = (Log)this.logConstructor.newInstance(params); } if (this.logMethod != null) { params = new Object[]{this}; this.logMethod.invoke(instance, params); } //返回獲取到的Log實例即可 return instance; } catch (LogConfigurationException var5) ... } //⑤:該方法通過數組的順序來進行實現類實例 private Log discoverLogImplementation(String logCategory) throws LogConfigurationException { ... //核心部分:從數組中進行遍歷,可見classesToDiscover數組內容(上面),順序依次為Log4JLogger、Jdk14Logger.. //其中的result == null則是判斷是否獲取到了實例,若獲取到退出for循環
//循環根據多種框架實現類的全限定類目去查找獲取的Log實例是否存在 for(int i = 0; i < classesToDiscover.length && result == null; ++i) { result = this.createLogFromClass(classesToDiscover[i], logCategory, true); } if (result == null) { throw new LogConfigurationException("No suitable Log implementation"); } else { //返回指定實例 return result; } } }
我們在調用LogFactory.getLog()方法時獲取了JCL中Log接口的實現類實例,在實現類中的構造器里獲取到了真正對應的日志框架Logger實例
總結: 1、JCL日志門面中Log是通用接口,LogFactory.getLog()用於獲取對應Log的實現類。 2、Log接口(包含了主要的日志等級方法)的實現類主要的是兩個,Jdk14Logger(即JUL,不導任何包默認)和Log4JLogger (即Log4j,導入Log4j則切換)。 3、對於獲取Log實例是在LogFactory.getLog()中執行獲取的,執行順序見如下 "org.apache.commons.logging.impl.Log4JLogger" "org.apache.commons.logging.impl.Jdk14Logger" "org.apache.commons.logging.impl.Jdk13LumberjackLogger" "org.apache.commons.logging.impl.SimpleLog" 4、在獲取到指定Log實例時,即使用構造器會進行各個實現類中的Logger實例的賦值獲取(通過對應的日志框架), 其中的日志等級方法調用的是對應框架的日志等級方法。
五:SLF4J日志門面框架(重點)
SLF4J即簡單日志門面(Simple Logging Facade for Java),它主要是為了給Java日志訪問提供一套標准、規范的API框架,其主要意義在於提供接口,具體的實現可以交由其它日志框架,例如log4j和logback等;當然slf4j自己也提供了功能較為簡單的實現,但是一般很少用到;對於一般的Java項目而言,日志框架會選擇slf4j-api作為門面,配上具體的實現框架(log4j、logback等),中間使用橋接器完成橋接。所以我們可以得出SLF4J最重要的兩個功能就是對於日志框架的綁定以及日志框架的橋接。SLF4J和JCL這兩個門面框架差不多。
核心功能:日志框架的綁定、日志框架的橋接
1:SLF4J入門案例及說明
<!--SLF4J基本的API坐標依賴;注單獨導入這個是沒有日志效果--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.35</version> </dependency> <!--基本的simple,就是剛才說的SLF4J提供的基本日志實現,--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.35</version> </dependency>
@Test public void demoA() { //通過Logger工廠對象動態獲取我們具體導入的日志實現框架(Log4j、Logback、JUL、slf4j-simple) Logger logger = LoggerFactory.getLogger(Slf4jDemo.class); //日志打印方式1 logger.error("[error ] 最高級別程序錯誤"); logger.warn("[warning] 程序警告不會出現錯誤"); logger.info("[info ] 程序info數據連接,網絡連接,IO操作等"); // 默認級別 logger.debug("[debug ] 一般在開發中使用,記錄程序變量傳遞信息等等"); logger.trace("[trace ] 記錄程序所有的流程信息"); //日志打印方式2 String name = "螞蟻小哥", age = "23"; logger.info("info日志記錄:我的姓名是:{} 年齡:{}", name, age); //日志打印方式3 try { int a = 1 / 0; } catch (Exception e) { logger.error("程序發生嚴重錯誤!", e); } } [main] ERROR cn.xw.Slf4jDemo - [error ] 最高級別程序錯誤 [main] WARN cn.xw.Slf4jDemo - [warning] 程序警告不會出現錯誤 [main] INFO cn.xw.Slf4jDemo - [info ] 程序info數據連接,網絡連接,IO操作等 [main] INFO cn.xw.Slf4jDemo - info日志記錄:我的姓名是:螞蟻小哥 年齡:23 [main] ERROR cn.xw.Slf4jDemo - 程序發生嚴重錯誤! java.lang.ArithmeticException: / by zero at cn.xw.Slf4jDemo.demoA(Slf4jDemo.java:30) ....
2:日志級別
這里我就簡單和大家闡述一下了,因為大同小異,和上面的日志框架的級別差不多
ERROR【錯誤信息】: 用來記錄運行時的錯誤信息,表示程序運行過程中出現了需要被解決的問題,往往是一些異常。使用error日志的時候 一般會將詳細的異常出現的原因記錄。如果不想輸出太多的日志,可以使用這個級別 WARN【警告信息】: 用來記錄一些警告信息。警告信息表示,程序進入了一個特殊的狀態,在該狀態下程序可以繼續運行,但是不建議讓程序進入 該狀態,因為該狀態可能導致結果出現問題;有些信息不是錯誤信息,但是也要給程序員的一些提示 INFO【運行信息】: 消息在粗粒度級別上突出強調應用程序的運行過程。打印一些你感興趣的或者重要的信息,這個可以用於生產環境中輸出程序 運行的一些重要信息,但是不能濫用,避免打印過多的日志。 DEBUG【調試信息】: 這類日志往往用在判斷是否有出現bug的場景,且往往記錄了代碼運行的詳細信息,比如方法調用傳入的參數信息,適用開發環境 TRACE【追蹤信息】: 最低優先級的日志,一般用來追蹤詳細的程序運行流,比如程序的運行過程中,運行到了哪一個方法,進入了哪一條分支。 通過trace程序的運行流程,可以判斷程序是否按照期望的邏輯在運行,一般不會使用
3:常見日志框架的綁定集成
通過前面介紹知道SLF4J它只是個門面框架,只提供API接口而不提供底層實現,所以我們就要綁定具體的日志實現框架;在入門案例中我們已經綁定了SLF4J官方的自己編寫的入門日志框架,這里就不在過多介紹,因為功能單調;
針對上面的編號進行介紹: ①:SLF4J 單獨導入slf4j-api是沒有日志打印的效果,只會打印幾句提示信息,提示未綁定日志實現,因為底層沒有綁定具體的日志框架 ②:SLF4J+logback 底層綁定logback日志實現框架 ③:SLF4J+reload4j(Log4j) 底層綁定Log4j日志實現框架(reload4j是Log4j的升級版,因為之前Log4j出現了重大漏洞) ④:SLF4J+JUL 底層綁定Java自帶的JUL日志實現框架 ⑤:SLF4J+Simple 底層綁定SLF4J官方推出的基本日志實現框架 ⑥:SLF4J+nop 關閉一切日志輸出信息 下面按照順序依次介紹及使用
①:SLF4J
<!--SLF4J基本的API坐標依賴;注單獨導入這個是沒有日志效果--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.35</version> </dependency> //測試代碼(這個測試代碼是通用的,因為使用的是門面API,后面的案例可以不要改變測試代碼) @Test public void demoA() { //通過Logger工廠對象動態獲取我們具體導入的日志實現框架(Log4j、Logback、JUL、slf4j-simple) Logger logger = LoggerFactory.getLogger(Slf4jDemo.class); //日志打印方式 logger.error("[error ] 最高級別程序錯誤"); logger.warn("[warning] 程序警告不會出現錯誤"); logger.info("[info ] 程序info數據連接,網絡連接,IO操作等"); logger.debug("[debug ] 一般在開發中使用,記錄程序變量傳遞信息等等"); logger.trace("[trace ] 記錄程序所有的流程信息"); } <!--具體打印--> <!--提示我們說沒有綁定具體的日志實現--> SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
②:SLF4J+LogBack
<!--集成logback 在maven中只要導入logback-classic即可,code會依賴傳遞--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.10</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.10</version> </dependency> <!--定義一個logback.xml文件放到resources目錄下,沒有當前文件則走默認配置,后面章節會詳細講解logback--> <?xml version="1.0" encoding="UTF-8"?> <configuration debug="false" scan="false" scanPeriod="30 seconds"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="DEBUG"> <appender-ref ref="STDOUT"/> </root> </configuration> <!--打印效果--> 17:35:04.120 [main] ERROR cn.xw.Slf4jDemo - [error ] 最高級別程序錯誤 17:35:04.124 [main] WARN cn.xw.Slf4jDemo - [warning] 程序警告不會出現錯誤 17:35:04.125 [main] INFO cn.xw.Slf4jDemo - [info ] 程序info數據連接,網絡連接,IO操作等 17:35:04.126 [main] DEBUG cn.xw.Slf4jDemo - [debug ] 一般在開發中使用,記錄程序變量傳遞信息等等
③:SLF4J+reload4j(Log4j)
<!--reload4j適配器 替代Log4j--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-reload4j</artifactId> <version>1.7.35</version> </dependency> <!--reload4j(原名Log4j)--> <dependency> <groupId>ch.qos.reload4j</groupId> <artifactId>reload4j</artifactId> <version>1.2.18.5</version> </dependency> <!--創建log4j.properties放在resources目錄下(必須有這個文件,否則無法使用)--> log4j.rootLogger=DEBUG,console #輸出到控制台 log4j.appender.console=org.apache.log4j.ConsoleAppender #設置輸出樣式 log4j.appender.console.layout=org.apache.log4j.PatternLayout #日志輸出信息格式為 log4j.appender.console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n <!--日志打印--> 2022-02-12 17:59:30,428 [main] ERROR [cn.xw.Slf4jDemo] - [error ] 最高級別程序錯誤 2022-02-12 17:59:30,429 [main] WARN [cn.xw.Slf4jDemo] - [warning] 程序警告不會出現錯誤 2022-02-12 17:59:30,429 [main] INFO [cn.xw.Slf4jDemo] - [info ] 程序info數據連接,網絡連接,IO操作等 2022-02-12 17:59:30,429 [main] DEBUG [cn.xw.Slf4jDemo] - [debug ] 一般在開發中使用,記錄程序變量傳遞信息等等
④:SLF4J+JUL
<!--JUL適配器--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>1.7.35</version> </dependency> // 打印日志 二月 12, 2022 6:01:45 下午 cn.xw.Slf4jDemo demoA 嚴重: [error ] 最高級別程序錯誤 二月 12, 2022 6:01:45 下午 cn.xw.Slf4jDemo demoA 警告: [warning] 程序警告不會出現錯誤 二月 12, 2022 6:01:45 下午 cn.xw.Slf4jDemo demoA 信息: [info ] 程序info數據連接,網絡連接,IO操作等
⑤:SLF4J+Simple
這個可以參考入門案例
⑥:SLF4J+nop
<!--slf4j-nop禁用日志,導入這個坐標即可--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-nop</artifactId> <version>1.7.35</version> </dependency>
⑦:注意事項
坐標導入不能出現沖突情況,比如既導入了Log4j作底層實現也導入了LogBack作底層實現,這樣就會產生沖突,會選擇其中一個, 選擇規律是先導入的被選擇 這時日志打印出多個實現框架,並在后面選擇出當前正在使用的日志打印框架是logback SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/I:/maven_repository/ch/qos/logback/logback-classic/1.2.10/logback-classic-1.2.10.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/I:/maven_repository/org/slf4j/slf4j-reload4j/1.7.35/slf4j-reload4j-1.7.35.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
4:SLF4J日志橋接技術
假設當初的項目使用的是非SLF4J的日志技術(這里我就以當初使用Log4j來說),但是在不久的將來感覺這些日志框架不是太好,這時我想切換SLF4J門面+LogBack日志框架來實現具體的日志記錄,那么當初在項目中引用的Log4j坐標將不能再繼續使用下去了,因為我想把整個項目中的日志記錄全部交由最終的LogBack輸出,如果Log4j引用的坐標不剔除,那么最終以前的日志打印還是走的Log4j輸出
<!--當初使用Log4j日志來打印日志--> <!--reload4j(原名Log4j)--> <dependency> <groupId>ch.qos.reload4j</groupId> <artifactId>reload4j</artifactId> <version>1.2.18.5</version> </dependency> <!--后來把整個項目改變為SLF4J門面+LogBack日志框架--> <!--SLF4J基本的API坐標依賴;注單獨導入這個是沒有日志效果--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.35</version> </dependency> <!--集成logback 在maven中只要導入logback-classic即可,code會依賴傳遞--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.10</version> </dependency> <!--reload4j的橋接器,橋接到當前SLF4J門面中,還需把原來的reload4j注釋掉--> <dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> <version>1.7.35</version> </dependency>
六:Logback日志框架
Logback 是由log4j創始人設計的另一個開源日志組件;可分為以下幾個模塊: logback-core: 其它兩個模塊的基礎模塊 logback-classic: 它是log4j的一個改良版本,同時它完整實現了slf4j API使你可以很方便地更換成其它日志系統如log4j或JDK14 Logging logback-access: 訪問模塊與Servlet容器集成提供通過Http來訪問日志的功能
詳細學習Logback請參考 logback系列教程 (51gjie.com)
1:Logback取代Log4j的理由
1:更快的實現:Logback的內核重寫了,在一些關鍵執行路徑上性能提升10倍以上。而且logback不僅性能提升了,初始化內存加載也更小了。 2:非常充分的測試:Logback經過了幾年,數不清小時的測試。Logback的測試完全不同級別的。 3:Logback-classic非常自然實現了SLF4j:Logback-classic實現了SLF4j。在使用SLF4j中,你都感覺不到logback-classic。 而且因為logback-classic非常自然地實現了slf4j,所以切換到log4j或者其他,非常容易,只需要提供成另一個jar包就OK, 根本不需要去動那些通過SLF4J API實現的代碼。 4:非常充分的文檔 官方網站有兩百多頁的文檔。 5:自動重新加載配置文件,當配置文件修改了,Logback-classic能自動重新加載配置文件。掃描過程快且安全,它並不需要另外創建一個
掃描線程。這個技術充分保證了應用程序能跑得很歡在JAVA EE環境里面。 6:謹慎的模式和非常友好的恢復,在謹慎模式下,多個FileAppender實例跑在多個JVM下,能夠安全地寫到同一個日志文件。
RollingFileAppender會有些限制。Logback的FileAppender和它的子類包括RollingFileAppender能夠非常友好地從I/O異常
中恢復。 7:配置文件可以處理不同的情況,開發人員經常需要判斷不同的Logback配置文件在不同的環境下(開發,測試,生產)。而這些配置文件僅僅
只有一些很小的不同,可以通過,和來實現,這樣一個配置文件就可以適應多個環境。 8:Filters(過濾器)有些時候,需要診斷一個問題,需要打出日志。在log4j,只有降低日志級別,不過這樣會打出大量的日志,會影響應
用性能。在Logback,你可以繼續 保持那個日志級別而除掉某種特殊情況,如alice這個用戶登錄,她的日志將打在DEBUG級別而其他 用戶可以繼續打在WARN級別。要實現這個功能只需加4行XML配置。可以參考MDCFIlter 。 9:SiftingAppender(一個非常多功能的Appender):它可以用來分割日志文件根據任何一個給定的運行參數。如,SiftingAppender能夠 區別日志事件跟進用戶的Session,然后每個用戶會有一個日志文件。 10:自動壓縮已經打出來的log:RollingFileAppender在產生新文件的時候,會自動壓縮已經打出來的日志文件。壓縮是個異步過程, 所以甚至對於大的日志文件,在壓縮過程中應用不會受任何影響。 11:堆棧樹帶有包版本:Logback在打出堆棧樹日志時,會帶上包的數據。 12:自動去除舊的日志文件:通過設置TimeBasedRollingPolicy或者SizeAndTimeBasedFNATP的maxHistory屬性,你可以控制已經產生日 志文件的最大數量。如果設置maxHistory 12,那那些log文件超過12個月的都會被自動移除。
2:Logback入門案例
<!--Logback基礎模塊--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.10</version> </dependency> <!--logback-classic是log4j改良版本,它完整實現SLF4J API,這樣只要導入此坐標就可以和SLF4J API契合--> <!--導入此坐標會自帶依賴導入一個core基本包--> <!--這個依賴直接包含了 logback-core 以及 slf4j-api的依賴--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.10</version> </dependency>
@Test public void demoA() { //通過Logger工廠對象動態獲取我們具體導入的日志實現框架Logback //因為導入logback-classic坐標后會自動依賴傳遞SLF4J-api門面 Logger logger = LoggerFactory.getLogger(LogbackDemo.class); //日志打印方式 logger.error("[error ] 最高級別程序錯誤"); logger.warn(" [warning] 程序警告不會出現錯誤"); logger.info(" [info ] 程序info數據連接,網絡連接,IO操作等"); logger.debug("[debug ] 一般在開發中使用,記錄程序變量傳遞信息等等"); // 默認級別 logger.trace("[trace ] 記錄程序所有的流程信息"); }
注:Logback因為和SLF4J API緊密結合,所以日志級別Level和SLF4J一樣;
不指定配置文件則有默認配置文件
日志級別:error > warning > info > debug > trace
3:Logback日志文件加載順序
logback在啟動的時候,會按照下面的順序加載配置文 ①:如果java程序啟動時指定了logback.configurationFile屬性,就用該屬性指定的配置文件。 如java -Dlogback.configurationFile=/path/to/mylogback.xml Test , 這樣執行Test類的時候就會加載/path/to/mylogback.xml配置 ②:在classpath中查找 logback.groovy 文件 ③:在classpath中查找 logback-test.xml 文件 ④:在classpath中查找 logback.xml 文件 ⑤:如果是jdk6+,那么會調用ServiceLoader 查找com.qos.logback.classic.spi.Configurator接口的第一個實現類 ⑥:自動使用ch.qos.logback.classic.BasicConfigurator,在控制台輸出日志 注:上面的順序表示優先級,使用java -D配置的優先級最高,只要獲取到配置后就不會再執行下面的流程。 相關代碼可以看ContextInitializer#autoConfig()方法。
4:Logback.xml控制台輸出配置
<?xml version="1.0" encoding="UTF-8" ?> <configuration> <!--定義日志格式參數,后面可以通過${myPattern}使用--> <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/> <!--定義ConsoleAppender 用於在屏幕上輸出日志--> <appender name="consoleAppend" class="ch.qos.logback.core.ConsoleAppender"> <!--顯示控制台日志顏色 System.err【紅色】 System.out【默認白色】--> <target>System.err</target> <!--配置日志輸出格式,並引用 myPattern 自定的日志格式--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${myPattern}</pattern> </encoder> </appender> <!--配置日志記錄器並設置日志記錄器的打印級別--> <root level="ALL"> <!--引入appender....--> <appender-ref ref="consoleAppend"/> </root> </configuration> //日志打印 2022-02-15 16:10:34.466 cn.xw.LogbackDemo [main] ERROR [error ] 最高級別程序錯誤 2022-02-15 16:10:34.470 cn.xw.LogbackDemo [main] WARN [warning] 程序警告不會出現錯誤 2022-02-15 16:10:34.471 cn.xw.LogbackDemo [main] INFO [info ] 程序info數據連接,網絡連接,IO操作等 2022-02-15 16:10:34.472 cn.xw.LogbackDemo [main] DEBUG [debug ] 一般在開發中使用,記錄程序變量傳遞信息等等 2022-02-15 16:10:34.472 cn.xw.LogbackDemo [main] TRACE [trace ] 記錄程序所有的流程信息
5:Logback.xml文件和HTML格式輸出
<?xml version="1.0" encoding="UTF-8" ?> <configuration> <!--定義日志格式參數,后面可以通過${myPattern}使用--> <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/> <!--定義日志路徑參數,后面可以通過${myPattern}使用--> <property name="file_dir" value="D:/logs"/> <!--定義FileAppender 用於在文件上輸出日志--> <appender name="fileAppend" class="ch.qos.logback.core.FileAppender"> <!--日志文件名稱--> <file>${file_dir}/fileLogback.log</file> <!--配置日志輸出格式,並引用 myPattern 自定的日志格式--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${myPattern}</pattern> </encoder> </appender> <!--定義FileAppender 用於在文件上輸出日志--> <appender name="fileHtmlAppend" class="ch.qos.logback.core.FileAppender"> <file>${file_dir}/fileLogback.html</file> <!--因為是要輸出HTML格式,所以需要布局包裝器一下--> <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"> <!--設置最終的輸出樣式--> <layout class="ch.qos.logback.classic.html.HTMLLayout"> <pattern>%level%d{yyyy-MM-dd HH:mm:ss}%c%M%L%thread%m</pattern> </layout> </encoder> </appender> <!--配置日志記錄器並設置日志記錄器的打印級別--> <root level="ALL"> <!--引入appender....--> <appender-ref ref="fileAppend"/> <appender-ref ref="fileHtmlAppend"/> </root> </configuration>
6:Logback.xml文件輸出之拆分歸檔
<?xml version="1.0" encoding="UTF-8" ?> <configuration> <!--定義日志格式參數,后面可以通過${myPattern}使用--> <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/> <!--定義日志路徑參數,后面可以通過${myPattern}使用--> <property name="file_dir" value="D:/logs"/> <!--定義RollingFileAppender 用於在文件上輸出日志並拆分歸檔--> <appender name="rollFileAppend" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!--定義日志輸出的路徑--> <!--這里的scheduler.manager.server.home 沒有在上面的配置中設定,所以會使用java啟動時配置的值--> <!--比如通過 java -Dscheduler.manager.server.home=/path/to XXXX 配置該屬性--> <file>${scheduler.manager.server.home}/fileRollLogback.log</file> <!--配置日志輸出格式,並引用 myPattern 自定的日志格式--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${myPattern}</pattern> </encoder> <!--基於大小和時間滾動策略--> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!--按照時間和壓縮格式聲明文件名 壓縮文件為.gz--> <fileNamePattern>${scheduler.manager.server.home}/roll.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern> <!--按照文件大小拆分 每個文件達到500MB會自動壓縮歸檔--> <maxFileSize>500MB</maxFileSize> </rollingPolicy> </appender> <!--配置日志記錄器並設置日志記錄器的打印級別--> <root level="ALL"> <!--引入appender....--> <appender-ref ref="rollFileAppend"/> </root> </configuration>
7:Logback過濾器及異步打印
<?xml version="1.0" encoding="UTF-8" ?> <configuration> <!--定義日志格式參數,后面可以通過${myPattern}使用--> <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/> <!--定義ConsoleAppender 用於在屏幕上輸出日志--> <appender name="consoleAppend" class="ch.qos.logback.core.ConsoleAppender"> <!--顯示控制台日志顏色 System.err【紅色】 System.out【默認白色】--> <target>System.err</target> <!--配置日志輸出格式,並引用 myPattern 自定的日志格式--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${myPattern}</pattern> </encoder> <!--LevelFilter: 級別過濾器,根據日志級別進行過濾。如果日志級別等於配置級別, 過濾器會根據onMath 和 onMismatch接收或拒絕日志。--> <!--例如:將過濾器的日志級別配置為INFO,所有INFO級別的日志交給appender處理,非INFO級別的日志,被過濾掉。--> <!-- <filter class="ch.qos.logback.classic.filter.LevelFilter">--> <!-- <level>INFO</level>--> <!-- <onMatch>ACCEPT</onMatch>--> <!-- <onMismatch>DENY</onMismatch>--> <!-- </filter>--> <!--ThresholdFilter: 臨界值過濾器,過濾掉低於指定臨界值的日志。當日志級別等於或高於臨界值時, 過濾器返回NEUTRAL;當日志級別低於臨界值時,日志會被拒絕。--> <!-- 過濾掉所有低於 DEBUG 級別的日志,留下DEBUG及以上級別的日志 --> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>DEBUG</level> </filter> </appender> <!--配置異步日志--> <appender name="asyncAppend" class="ch.qos.logback.classic.AsyncAppender"> <!--要使用到異步的Appender--> <appender-ref ref="consoleAppend"/> </appender> <!--配置日志記錄器並設置日志記錄器的打印級別--> <root level="ALL"> <!--引入appender....--> <appender-ref ref="asyncAppend"/> </root> </configuration>
8:自定義Logger
<?xml version="1.0" encoding="UTF-8" ?> <configuration> <!--定義日志格式參數,后面可以通過${myPattern}使用--> <property name="myPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/> <!--定義ConsoleAppender 用於在屏幕上輸出日志--> <appender name="consoleAppend" class="ch.qos.logback.core.ConsoleAppender"> <!--顯示控制台日志顏色 System.err【紅色】 System.out【默認白色】--> <target>System.err</target> <!--配置日志輸出格式,並引用 myPattern 自定的日志格式--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${myPattern}</pattern> </encoder> </appender> <!--additvity="false" 不繼承父元素--> <logger name="cn.xw" level="INFO" additvity="false"> <!--自定義logger中配置appender--> <appender-ref ref="consoleAppend"/> </logger> </configuration>