多种java 日志框架【超详细图文】
一、目標
二、日志的概念
2.1 日志文件
日志文件是用于記錄系統操作事件的文件集合,可分為事件日志和消息日志。具有處理歷史數據、診斷問題的追蹤以及理解系統的活動等重要作用。
在計算機中,日志文件是記錄在操作系統或其他軟件運行中發生的事件或在通信軟件的不同用戶之間的消息的文件。記錄是保持日志的行為,在最簡單的情況下,消息被寫入單個日志文件。
許多操作系統,軟件框架和程序包括日志系統。廣泛使用的日志記錄標準是在因特網工程任務組(IETE) RFC5424中定義的syslog。syslog標準使專用的標準化子系統能夠生成,過濾,記錄和分析日志消息。
2.1.1 調試日志
軟件開發中,我們經常需要去調試程序,做一些信息,狀態的輸出便于我們查詢程序的運行狀況。為了讓我們能夠更加靈活和方便的控制這些調試的信息,所以我們需要專業的日志技術。java 中尋找bug 會需要重現。調試也就是debug 可以在程序運行中暫停程序運行,可以查看程序在運行中的情況。日志主要是為了更加方便的去重現問題。
2.1.2 系統日志
系統日志是記錄系統中硬件、軟件和系統問題的信息,同時還可以監視系統中發生的事件。用戶可以通過它來檢查錯誤發生的原因,或者尋找受到攻擊者留下的痕跡。系統日志包括系統日志、應用程序日志和安全日志。
系統日志的價值
系統日志策略可以在故障剛剛發生時就向你發送警告信息,系統日志幫助你在最短的時間內發現問題。
三、JAVA 日志框架
問題:
3.1 為什么要用日志框架
因為軟件系統發展到今天已經很復雜了,特別是服務器端軟件,涉及到的知識、內容、問題太多。在某些方面使用別人成熟的框架,就相當于讓別人幫你完成一些基礎工作,你只需要集中精力完成系統的業務邏輯設計。而且框架一般是成熟、穩鍵的,它可以處理系統很多細節問題,比如,事務處理、安全性,數據流控制等問題。還有框架一般都經過很多人使用,所以結構很好,所以擴展性也很好,而且它是不斷升級的,你可以直接享受別人升級代碼帶來的好處。
3.2 現有日志框架
- JUL(java util logging)、logback、log4j、log4j2
- JCL(Jakarta Commons Logging)、slf4j(Simple Logging Facade for Java)
日志門面
JCL、slf4j
日志實現
JUL、logback、log4j、log4j2【目前性能最好的日志處理技術】
四、JUL 深入理解
JUL 全稱Java util Logging 是java 原生的日志框架,使用時不需要另外引用第三方類庫,相對其他日志框架使用方便、學習簡單、能夠在小型應用中靈活使用。
4.1 JUC 入門
4.1.1 架構介紹
- Loggers:被稱為記錄器,應用程序通過獲取Logger 對象,調用其API 來發布日志信息。Logger 通常時應用程序訪問日志系統的入口程序。
- Appenders:也被稱為Handlers,每個Logger 都會關聯一組Handlers,Logger會將日志交給關聯Handlers處理,由Handlers 負責將日志做記錄。Handlers 在此是一個抽象,其具體的實現決定了日志記錄的位置可以是控制臺、文件、網絡上的其他日志服務或操作系統日志等。
- Layouts:也被稱為Formatters,它負責對日志事件中的數據進行轉換和格式化。Layouts決定了數據在一條日志記錄中的最終形式。
- Level:每條日志消息都有一個關聯的日志級別。該級別精略指導了日志消息的重要性和緊迫,我可以將Level 和 Loggers,Appenders做關聯以便于我們過濾消息。
- Filters:過濾器,根據需要定制哪些信息會被記錄,哪些信息會被放過。
代碼示例:
package com.log;import org.junit.Test;import java.util.logging.Level; import java.util.logging.Logger;public class T01_JULTest {// 快速入門@Testpublic void testQuick() {// 1.創建日志記錄器對象Logger logger = Logger.getLogger("com.log.test.JULTest");// 2.日志記錄輸出logger.info("hello jul");// 通用方法進行日志記錄logger.log(Level.INFO, "info msg");// 通過點位符方式進行日志記錄String name = "jack";Integer age = 18;logger.log(Level.INFO, "用戶信息:{0},{1}", new Object[]{name, age});} }4.2 日志的級別
// JUL日志級別測試@Testpublic void testLogLevel() throws Exception {// 1.獲取日志記錄器對象Logger logger = Logger.getLogger("com.log.T01_JULTest");// 2.日志記錄輸出logger.severe("server");logger.warning("warning");logger.info("info"); // jul 默認的日志級別infologger.config("config");logger.fine("fine");logger.finer("finer");logger.finest("finest");}4.3 Logger 之間的父子關系
// Logger 對象父子關系@Testpublic void testLogParent() throws Exception {// todo: 日志父子關系是根據包名關系繼承的Logger logger1 = Logger.getLogger("com.log");Logger logger2 = Logger.getLogger("com"); // com包沒有父包的話,默認繼承自RootLogger// 測試日志對象是否存在父子關系:答案是存在的System.out.println(logger1.getParent() == logger2);// 所有日志記錄器的頂級父無素 LogManager$RootLogger, name ""System.out.println("logger2 Parent:" + logger2.getParent() + ", name:" + logger2.getParent().getName());/*todo: 1因為logger2是父日志級別,通過修改它的日志輸出級別,就可以控制logger1的日志輸出級別todo: 2如果關閉logger2 父日志級別則logger1不再繼承自logger2*/// 關閉默認配置logger2.setUseParentHandlers(false);// 自定義配置日志級別// 1.將日志通過 創建ConsoleHandler 控制臺輸出ConsoleHandler consoleHandler = new ConsoleHandler();// 創建簡單格式轉換對象SimpleFormatter simpleFormatter = new SimpleFormatter();// 進行關聯consoleHandler.setFormatter(simpleFormatter);logger2.addHandler(consoleHandler);logger1.severe("server");logger1.warning("warning");logger1.info("info"); // jul 默認的日志級別infologger1.config("config");logger1.fine("fine");logger1.finer("finer");logger1.finest("finest");}4.4 日志的配置文件
?
// 加載自定義配置文件:從指定配置文件中加載日志輸出級別@Testpublic void testLogProperties() throws Exception {// 讀取配置文件,通過類加載器InputStream in = T01_JULTest.class.getClassLoader().getResourceAsStream("logging.properties");// 創建LogManagerLogManager logManager = LogManager.getLogManager();// 通過LogManager 加載配置文件;相當于將JAVA_HOME/lib/logging.properties 進行了替換logManager.readConfiguration(in);// TODO: 因為加載的是自定義日志配置文件,日志輸出級別為ALL;可以運行起來驗證// 創建日志記錄器Logger logger = Logger.getLogger("com.log");logger.severe("server");logger.warning("warning");logger.info("info"); // jul 默認的日志級別infologger.config("config");logger.fine("fine");logger.finer("finer");logger.finest("finest");// 自定義日志記錄器Logger logger2 = Logger.getLogger("test");logger2.severe("server test");logger2.warning("warning test");logger2.info("info test"); // jul 默認的日志級別infologger2.config("config test");logger2.fine("fine test");logger2.finer("finer test");logger2.finest("finest test");}4.5 日志原理解析
五、LOG4J 進階
Log4j 是Apache 下的一款開源的日志框架,通過在項目中使用Log4J,我們可以控制日志信息輸出到控制臺、文件、甚至是數據庫中。我們可以控制每一條日志的輸出格式,通過定義日志的輸出級別,可以更靈活的控制日志的輸出過程。方便項目的調試。
官方網站:http://logging.apache.org/log4j/1.2/
5.1 Log4j 入門
需要導入maven 依賴:
<dependencies><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency></dependencies><build><plugins><plugin><!-- 設置編譯版本為1.8 --><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin></plugins></build>示例代碼:
public class Log4jTest {// Log4j 基礎@Testpublic void testQuick(){// 初始化配置信息,在入門案例中暫不使用配置文件BasicConfigurator.configure();// 獲取日志記錄器對象Logger logger = Logger.getLogger(Log4jTest.class);// 日志記錄輸出logger.info("hello log4j");// 日志級別有6種,默認的日志級別是DEBUGlogger.fatal("fatal"); // 嚴重錯誤,一般會造成系統崩潰并終止運行logger.error("error"); // 錯誤信息,不會影響系統運行logger.warn("warn"); // 警告信息,可能會發生問題logger.info("info"); // 運行信息,數據連接、網絡連接、IO 操作等等logger.debug("debug"); // 調試信息,一般在開發中使用,記錄程序變量參數傳遞信息等等logger.trace("trace"); // 追蹤信息,記錄程序所有的流程信息} }運行示例截圖:
?
5.2 Log4j 組件
Log4J 主要由 Loggers (日志記錄器)、Appenders(輸出端)和 Layout (日志格式化器)組成。其中 Loggers 控制日志的輸出級別與日志是否輸出:Appenders 指定日志的輸出方式(輸出到控制臺、文件等);Layout 控制日志信息的輸出格式。
5.2.1 Loggers
日志記錄器,負責收集處理日志記錄,實例的命名就是類 "XX" 的 full quailied name (類的全限定名),Logger 的名字大小寫敏感,其命名有繼承機制:例如: name 為 org.apache.commons 的logger 會繼承 name 為 org.apache 的 logger。
Log4J 中有一個特殊的logger 叫做 "root" ,他是所有 logger 的根,也就意味著他所有的 logger 都會直接或間接地繼承自 root。root logger 可以用 Logger.getRootLogger() 方法獲取。
但是,自Log4J 1.2版本以來,Logger 類已經取代了 Category 類。對于熟悉早期版本的 log4j 的人來說,Logger 類可以被視為 Category 類的別名。
5.2.2 Appenders
Appender 用來指定日志輸出到哪個地方,可以同時指定日志的輸出目的地。Log4j 常用的輸出目的有以下幾種:
| ConsoleAppender | 將日志輸出到控制臺 |
| FileAppender | 將日志輸出到文件中 |
| DailyRollingFileAppender | 將日志輸出一個日志文件中,并且每天輸出到一個新的文件 |
| RollingFileAppender | 將日志信息輸出到一個日志文件,并且指定文件的尺寸,當文件大小達到指定 尺寸時,會自動把文件改名,同時產生一個新的文件 |
| JDBCAppender | 把日志信息保存到數據庫中 |
5.2.3 Layouts
布局器 Layouts 用于控制日志輸出內容的格式,讓我們可以使用各種需要的格式輸出日志。Log4j 常用的 Layouts:
| HTMLLayout | 格式化日志輸出為HTML 表格形式 |
| SimpleLayout | 簡單的日志輸出格式化,打印的日志格式為(info - message) |
| PatternLayout | 最強大的格式化器,可以根據自定義 |
代碼示例:
public class Log4jTest {// Log4j 基礎@Testpublic void testQuick(){// 初始化配置信息,在入門案例中暫不使用配置文件// BasicConfigurator.configure();// 獲取日志記錄器對象Logger logger = Logger.getLogger(Log4jTest.class);// 日志記錄輸出logger.info("hello log4j");// 日志級別有6種,默認的日志級別是DEBUGlogger.fatal("fatal"); // 嚴重錯誤,一般會造成系統崩潰并終止運行logger.error("error"); // 錯誤信息,不會影響系統運行logger.warn("warn"); // 警告信息,可能會發生問題logger.info("info"); // 運行信息,數據連接、網絡連接、IO 操作等等logger.debug("debug"); // 調試信息,一般在開發中使用,記錄程序變量參數傳遞信息等等logger.trace("trace"); // 追蹤信息,記錄程序所有的流程信息} }log4j.properties 文件內容:
log4j.rootLogger = trace, console log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.layout = org.apache.log4j.SimpleLayout5.3 Log4j 的內置日志記錄
5.4 Layout 的格式
在 log4j.properties 配置文件中,我們定義了日志輸出級別與輸出端,在輸出端中分別配置日志的輸出格式。
* 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 字符,就從左邊較遠處的字符解掉 |
log4j.properties 示例:
# 指定 RootLogger 頂級父元素默認配置信息 # 指定日志級別 = trace,使用的 appender 為 console,這個是由我們配置的 log4j.rootLogger = trace, console # 指定控制臺日志輸出的 appender log4j.appender.console = org.apache.log4j.ConsoleAppender # 指定消息格式 layout # log4j.appender.console.layout = org.apache.log4j.SimpleLayout # log4j.appender.console.layout = org.apache.log4j.HTMLLayout # log4j.appender.console.layout = org.apache.log4j.xml.XMLLayout # 自定義日志輸出格式使用是最多的,默認只會輸出日志內容,沒有產生的時間等信息 log4j.appender.console.layout = org.apache.log4j.PatternLayout # 指定消息格式的內容 # log4j.appender.console.layout.conversionPattern = %r [%t] %p %c %x - %m%n # log4j.appender.console.layout.conversionPattern = [%-10p]%r %c %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n log4j.appender.console.layout.conversionPattern = [%-10p]%r %c %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n# %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 輸出代碼中的行號 # %% 輸出一個"%" 字符5.5 Appender 的輸出
控制臺、文件、數據庫
# 指定日志的輸出級別與輸出端
- 控制臺
- 文件,將日志輸出到指定文件中
- 文件,將日志按文件大小拆分輸出到多個文件
- 文件,將日志按時間拆分輸出到多個文件中,生產環境一天一個文件
5.6 Log4j 的JDBCAppender 配置
先創建數據庫、表
-- 創建java_log數據庫。注意:庫名與應用名稱保持一致 CREATE DATABASE IF NOT EXISTS `java_log`;-- 使用java_log USE java_log;-- 創建log 表 CREATE TABLE IF NOT EXISTS `log` (`id` BIGINT UNSIGNED AUTO_INCREMENT COMMENT '主鍵',`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',`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 (`id`) ) ENGINE=INNODB DEFAULT CHARSET=utf8;-- INSERT INTO `log`(project_name, create_date, level, category, file_name, thread_name, line, all_category, message) values ('java_log', '%d{yyyy-MM-dd HH:mm:ss}', '%p', '%c', '%F', '%t', '%L', '%l', '%m')配置log4j.properties 文件
# 指定 RootLogger 頂級父元素默認配置信息 # 指定日志級別 = trace,使用的 appender 為 console,這個是由我們配置的 # log4j.rootLogger = trace, console, file, rollingFile, dailyFile log4j.rootLogger = trace, logDB# MySQL log4j.appender.logDB=org.apache.log4j.jdbc.JDBCAppender log4j.appender.logDB.layout=org.apache.log4j.PatternLayout log4j.appender.logDB.Driver=com.mysql.jdbc.Driver log4j.appender.logDB.URL=jdbc:mysql://localhost:3306/java_log log4j.appender.logDB.User=root log4j.appender.logDB.Password=1234 log4j.appender.logDB.Sql=INSERT INTO `log`(project_name, create_date, level, category, file_name, thread_name, line, all_category, message) values ('java_log', '%d{yyyy-MM-dd HH:mm:ss}', '%p', '%c', '%F', '%t', '%L', '%l', '%m')添加maven 依賴
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.44</version></dependency>?編寫示例代碼、并運行它:
public class Log4jTest {// Log4j 基礎@Testpublic void testQuick(){// 開啟 log4j 內置日志記錄,如果開啟內置日志記錄則日志輸出會更多LogLog.setInternalDebugging(true);// 初始化配置信息,在入門案例中暫不使用配置文件// BasicConfigurator.configure();// 獲取日志記錄器對象Logger logger = Logger.getLogger(Log4jTest.class);// 日志記錄輸出logger.info("hello log4j");for (int i = 0; i < 10000; i++) {// 日志級別有6種,默認的日志級別是DEBUGlogger.fatal("fatal"); // 嚴重錯誤,一般會造成系統崩潰并終止運行logger.error("error"); // 錯誤信息,不會影響系統運行logger.warn("warn"); // 警告信息,可能會發生問題logger.info("info"); // 運行信息,數據連接、網絡連接、IO 操作等等logger.debug("debug"); // 調試信息,一般在開發中使用,記錄程序變量參數傳遞信息等等logger.trace("trace"); // 追蹤信息,記錄程序所有的流程信息}} }運行結果:單元測試中的?testQuick() 方法會不斷向 java_log 數據庫中的 log 表添加數據。
5.7 Log4j 的自定義 logger
作用:好處就是開發人員可以根據不同的業務、模塊靈活地輸出日志;比如向文件輸出我們自己編寫的業務日志;向控制臺只輸出第三方插件的錯誤日志。
# 自定義 logger 對象設置 【PropertyConfigurator.class 源碼位置】;其中com.log 是來自于我們業務所在的包名 # 好處就是不同的業務、模塊靈活輸出日志 log4j.logger.com.log = info,file log4j.logger.org.apache = error運行結果截圖:?
六、JCL 入門
全稱為Jakarta Commons Logging,是Apache 提供的一個通用日志API。就是我們的日志門面技術。
它是為 "所有的Java 日志實現" 提供一個統一的接口,它自身也提供一個日志的實現,但是功能非常常弱(SimpleLog)。所以一般不會單獨使用它。他允許開發人員使用不同的具體日志實現工具:Log4j, Jdk 自帶的日志(JUL)。
JCL 有兩個基本的抽象類:Log(基本記錄器)和 LogFactory(負責創建Log 實例)。默認實現 jdk14,就是jdk 自帶的
6.1 JCL 入門
1. 建立maven 工程
2. 添加依賴
<dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency>3、編寫測試代碼
?
運行結果截圖:
如果我在maven 依賴中添加log4j 依賴項,代碼保持不變,繼續看日志輸出
<dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency><!-- 如果我們導入log4j的依賴,再來看看日志輸出,則不再走jdk14 --><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency>添加log4j 日志輸出截圖如下:?
小結:通過上述實驗,我們發現面向接口的日志實現業務代碼保持不變,只是maven 依賴稍變更而已。更加穩定、高效。
我們為什么要使用日志門面:
6.2 JCL 原理
1、通過LogFactory 動態加載 Log 實現類
2、日志門面支持的日志實現數組
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"};3、獲取具體的日志實現
for(int i = 0; i < classesToDiscover.length && result == null; ++i) {result = this.createLogFromClass(classesToDiscover[i], logCategory, true); }JCL?缺點:由于JCL 實現類已經寫死,如果需要擴展的話就得修改JCL 源碼,成本很大。這種技術被淘汰了,后來Apache 設計了更加高效的方式來實現日志門面技術
?
JUL 技術是JDK 自帶的,不需要導入第三方依賴,但功能點單一。
第二,Log4j 也是相對老牌的日志處理,功能相對上述的話強大很多;但如果前期項目使用JUL 技術來開發的話,后期通過Log4j 來做很多API 不兼容,需要修改大量的代碼。
然后,我們又繼續研究的統一API 的日志門面技術 JCL,只需要調用統一的API 即可實現日志。但它也是有缺陷的,它當時只考慮了主流的日志實現框架(JCL、Log4j),隨著技術的發展,后面出現了很多優秀的日志技術,但JCL 是不支持的,需要修改JCL 源代碼進行擴展,一般在企業級開發中,我們不會這么做。所以已經JCL 這種技術已經被淘汰了。
接下來,重點介紹主流的日志技術:
七、日志門面
當我們的系統變的更加復雜的時候,我們的日志就容易發生混亂。隨著系統開發的進行,可能會更新不同的日志框架,造成當前系統中存在不同的日志依賴,讓我們難以統一的管理和控制。就算我們強制要求所有的模塊使用相同的日志框架,系統中也難以避免使用其他類似 spring, mybatis 等其他的第三方框架,它們依賴于我們規定不同的日志框架,而且他們自身的日志系統就有著不一致性,依然會出來日志體系的混亂。
所以我們需要借鑒JDBC 的思想,為日志系統也提供一套門面,那么我們就可以面向這些接口規范來開,避免了直接依賴具體的日志框架。這樣我們的系統在日志中,就存在了日志的門面和日志的實現。
常見的日志門面:
- JCL(已經被淘汰了)、slf4j(目前主流)
常見的日志實現:
- JUL、log4j、logback、log4j2
日志門面和日志實現的關系:
日志框架出現的歷史順序:
目前主流使用日志是采用日志門面技術:slf4j + logback? 或? slf4j + log4j2
八、SLF4J 的使用
8.1 SFL4J 入門
簡單日志門面(Simple Logging Facade For Java) SLF4J 主要是為了給 Java 日志訪問提供一個標準、規范的API框架,其主要意義在于提供接口,具體的實現可以交由其他日志框架,例如log4j 和 logback 等。當然 slf4j 自己也提供了功能較為簡單的實現。但是一般很少用到。對于一般的 Java 項目而言,日志框架會選擇 slf4j-api 作為門面,配上具體的實現框架(log4j、logback等),中間使用橋接器完成橋接。
官方網站:https://www.slf4j.org/
?
SLF4J 是目前 市面上最流行的日志門面?,F在的項目中,基本上都是使用SLF4J 作為我們的日志系統。SLF4J 日志門面主要提供兩大功能:
日志框架的綁定
日志框架的橋接
8.2 SFL4J?綁定日志的實現(Binding)
如前所述,SLF4J 支持各種日志框架。SLF4J 發行版本附帶了幾件稱為"SLF4J綁定" 的jar 文件,每個綁定對應一個受支持的框架。
?
使用slf4j 的日志綁定流程:
?
通過maven 引入常見的日志實現框架:
首先,我們先通過 slf4j 官網查看日志框架組織形式,官網鏈接:http://www.slf4j.org/manual.html
要切換日志框架,只需替換類路徑上的slf4j 綁定。例如,要從java.util.logging 切換到log4j,只需將 slf4j-jdk14-1.7.27.jar 替換為 slf4j-log4j12-1.7.27.jar 即可。
SLF4J 不依賴于任何特殊的類狀載。實際上,每個SLF4J 綁定在編譯時都是硬連線的,以使用一個且只有一個特定的日志記錄框架。例如,slf4j-log4j12-1.7.27.jar 綁定在編譯時綁定以使用log4j。在您的代碼中,除了sfl4j-api-1.7.27.jar 之外,您只需將您選擇的一個且只有一個綁定放到相應的類路徑位置。不要在類路勁上放置多個綁定。以下是一般概念的圖解說明。
?
通過官網的slf4j 日志框架組織,可以看出有很多組織形式
A、slf4j日志門面接口?+ slf4j-simple具體實現類
1、添加依賴
<!-- slf4j 日志門面 它只是一個日志接口,還需要導入具體的日志實現 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.26</version></dependency><!-- slf4j 內置的簡單實現 --><!-- 如果只有門面,沒有實現的話是運行不起來的 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.21</version></dependency><!-- junit 單元測試 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency>2、編寫代碼,示例:
public class Slf4jTest {// 為了保證使用時,不需要每次都去創建logger 對象,我們聲明靜態常量public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);// 快速入門@Testpublic void testQuick(){// 日志輸出LOGGER.error("error");LOGGER.warn("warning");LOGGER.info("info"); // 默認的日志級別信息LOGGER.debug("debug");LOGGER.trace("trace"); // 追蹤信息// 使用占位符輸出日志信息String name = "java_log";Integer age = 18;LOGGER.info("用戶:{},{}", name, age);// 將系統的異常信息輸出try {int i = 1 / 0;} catch (Exception e){// e.printStackTrace();LOGGER.error("出現異常:", e);}} }3、運行結果截圖:
B、sfl4j日志門面接口 + logback-classic具體實現類:
1)修改maven 依賴
<!-- slf4j 日志門面 它只是一個日志接口,還需要導入具體的日志實現 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.26</version></dependency><!-- slf4j 內置的簡單實現 --><!-- 如果只有門面,沒有實現的話是運行不起來的<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.21</version></dependency> --><!-- 通過logback 日志實現 ; 如果slf4j-simple依賴也在的話,優先使用它,則需要將此依賴進行移除即可 --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency><!-- junit 單元測試 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency>2)代碼示例
public class Slf4jTest {// 為了保證使用時,不需要每次都去創建logger 對象,我們聲明靜態常量public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);// 快速入門@Testpublic void testQuick(){// 日志輸出LOGGER.error("error");LOGGER.warn("warning");LOGGER.info("info"); // 默認的日志級別信息LOGGER.debug("debug");LOGGER.trace("trace"); // 追蹤信息// 使用占位符輸出日志信息String name = "java_log";Integer age = 18;LOGGER.info("用戶:{},{}", name, age);// 將系統的異常信息輸出try {int i = 1 / 0;} catch (Exception e){// e.printStackTrace();LOGGER.error("出現異常:", e);}} }3)運行結果截圖:
C、sfl4j日志門面接口 + slf4j-nop?具體實現類:
1) 修改依賴
<!-- slf4j 日志門面 它只是一個日志接口,還需要導入具體的日志實現 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.26</version></dependency><!-- slf4j 內置的簡單實現 --><!-- 如果只有門面,沒有實現的話是運行不起來的<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.21</version></dependency> --><!-- 通過logback 日志實現 ; 如果slf4j-simple依賴也在的話,優先使用它,則需要將此依賴進行移除即可<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency> --><!-- nop 日志開關 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-nop</artifactId><version>1.7.25</version></dependency><!-- junit 單元測試 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency>2)編寫代碼
public class Slf4jTest {// 為了保證使用時,不需要每次都去創建logger 對象,我們聲明靜態常量public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);// 快速入門@Testpublic void testQuick(){// 日志輸出LOGGER.error("error");LOGGER.warn("warning");LOGGER.info("info"); // 默認的日志級別信息LOGGER.debug("debug");LOGGER.trace("trace"); // 追蹤信息// 使用占位符輸出日志信息String name = "java_log";Integer age = 18;LOGGER.info("用戶:{},{}", name, age);// 將系統的異常信息輸出try {int i = 1 / 0;} catch (Exception e){// e.printStackTrace();LOGGER.error("出現異常:", e);}} }3)運行結果
D、sfl4j日志門面接口 + slf4j-log4j12【log4j適配器】 + log4j【實現類】
1)修改maven 依賴
<!-- slf4j 日志門面 它只是一個日志接口,還需要導入具體的日志實現 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.26</version></dependency><!-- 綁定 log4j 日志實現,需要導入適配器 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.12</version></dependency><!-- 導入適配器后,再導入具體的實現類 --><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency><!-- junit 單元測試 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency>2)編寫log4j.properties 日志配置文件
# 指定 RootLogger 頂級父元素默認配置信息 # 指定日志級別 = trace,使用的 appender 為 console,這個是由我們配置的 # log4j.rootLogger = trace, console, file, rollingFile, dailyFile log4j.rootLogger = trace, console# 指定控制臺日志輸出的 appender log4j.appender.console = org.apache.log4j.ConsoleAppender # 指定消息格式 layout # log4j.appender.console.layout = org.apache.log4j.SimpleLayout # log4j.appender.console.layout = org.apache.log4j.HTMLLayout # log4j.appender.console.layout = org.apache.log4j.xml.XMLLayout # 自定義日志輸出格式使用是最多的,默認只會輸出日志內容,沒有產生的時間等信息 log4j.appender.console.layout = org.apache.log4j.PatternLayout # 指定消息格式的內容 # log4j.appender.console.layout.conversionPattern = %r [%t] %p %c %x - %m%n log4j.appender.console.layout.conversionPattern = [%-10p]%r %c %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n3)編寫代碼
public class Slf4jTest {// 為了保證使用時,不需要每次都去創建logger 對象,我們聲明靜態常量public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);// 快速入門@Testpublic void testQuick(){// 日志輸出LOGGER.error("error");LOGGER.warn("warning");LOGGER.info("info"); // 默認的日志級別信息LOGGER.debug("debug");LOGGER.trace("trace"); // 追蹤信息// 使用占位符輸出日志信息String name = "java_log";Integer age = 18;LOGGER.info("用戶:{},{}", name, age);// 將系統的異常信息輸出try {int i = 1 / 0;} catch (Exception e){// e.printStackTrace();LOGGER.error("出現異常:", e);}} }4)運行結果截圖
E、sfl4j日志門面接口 + slf4j-log4j12【jdk14適配器】 + jdk14【實現類】
1) 修改maven 依賴
<!-- slf4j 日志門面 它只是一個日志接口,還需要導入具體的日志實現 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.26</version></dependency><!-- 綁定 JUL 日志實現,需要導入適配器 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-jdk14</artifactId><version>1.7.25</version></dependency><!-- junit 單元測試 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency>2)編寫業務代碼
public class Slf4jTest {// 為了保證使用時,不需要每次都去創建logger 對象,我們聲明靜態常量public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);// 快速入門@Testpublic void testQuick(){// 日志輸出LOGGER.error("error");LOGGER.warn("warning");LOGGER.info("info"); // 默認的日志級別信息LOGGER.debug("debug");LOGGER.trace("trace"); // 追蹤信息// 使用占位符輸出日志信息String name = "java_log";Integer age = 18;LOGGER.info("用戶:{},{}", name, age);// 將系統的異常信息輸出try {int i = 1 / 0;} catch (Exception e){// e.printStackTrace();LOGGER.error("出現異常:", e);}} }3)運行結果截圖
8.3 橋接舊的日志框架(Bridging)
通常,您依賴的某些依賴于SLF4J 以外的日志記錄API。您也可以假設這些組件在不久的將來不會切換到SLF4J。為了解決這種情況,SLF4J附帶了幾個橋接模塊,這些模塊將對log4j,JCL 和 java.util.logging API 的調用重定向,就好像它們是對SLF4J API 一樣。
橋接解決的是項目中日志的遺留問題,當系統中存在之前的日志API,可以通過橋接轉換到slf4j 的實現
下面演示的是日志老系統
1)maven 依賴
<!-- 導入適配器后,再導入具體的實現類 --><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency><!-- junit 單元測試 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency>2)編寫代碼
public class Log4jTest {// 定義 log4j 日志記錄器public static final Logger LOGGER = Logger.getLogger(Log4jTest.class);// 測試橋接器@Testpublic void test01(){LOGGER.info("hello log4j");} }3)運行結果截圖
需求:由于老的日志系統無法滿足功能需求,需要從log4j 日志升級到 logback。則需要通過以下操作完成
1)修改maven 依賴
<!-- slf4j 日志門面 它只是一個日志接口,還需要導入具體的日志實現 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.26</version></dependency><!-- 通過logback 日志實現 ; 如果slf4j-simple依賴也在的話,優先使用它,則需要將此依賴進行移除即可 --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency><!-- 導入適配器后,再導入具體的實現類<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency> --><!-- junit 單元測試 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency>2)我們發現之前使用log4j 的代碼報錯了,如下圖所示:
3)修改源碼可以解決報錯問題,工作量太大;另外一個slf4j 可以導入橋接器依賴解決此問題。來看slf4j 官網:http://slf4j.org/images/legacy.png
通過上圖,我們可以添加log4j 日志框架適配器,以適應slf4j 日志系統
<!-- 配置log4j 的橋接器 --><dependency><groupId>org.slf4j</groupId><artifactId>log4j-over-slf4j</artifactId><version>1.7.25</version></dependency>添加log4j-over-slf4j 依賴后,我們發現代碼不報錯了。并運行發現日志輸出與log4j 不一樣了
注意問題:
8.4 SLF4J 原理解析
九、Logback 的使用
Logback 是由log4j 創始人設計的另一個開源日志組件,性能比log4j 要好。官方網站:https://logback.qos.ch/index.html
Logback 主要分為三個模塊:
- logback-core:其它兩個模塊的基礎模塊
- logback-classic:它是log4j 的一個改良版本,同時它完整實現了slf4j API
- logback-access:訪問模塊與Servlet 容器集成提供通過Http 來訪問日志的功能
后續的日志代碼都是通過SLF4J 日志門面搭建日志系統,所以在代碼是沒有區別,主要是通過修改配置文件和pom.xml 依賴。
9.1 logback 入門
1)添加依賴
<!-- slf4j 日志門面 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.26</version></dependency><!-- logback 日志實現 --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency><!-- junit 單元測試 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency>2)編寫代碼
public class LogbackTest {public static final Logger LOGGER = LoggerFactory.getLogger(LogbackTest.class);// 快速入門@Testpublic void testQuick() throws Exception {// 日志輸出LOGGER.error("error");LOGGER.warn("warning");LOGGER.info("info");LOGGER.debug("debug"); // 默認的日志級別信息LOGGER.trace("trace"); // 追蹤信息} }3)運行結果截圖
9.2 logback 配置
logback 會依次讀取以下類型配置文件:
- logback.groovy
- logback-test.xml? 測試環境下的配置文件
- logback.xml 如果均不存在會采用默認配置
1、logback 組件之間的關系
2、基本配置信息
A maven 依賴
<!-- slf4j 日志門面 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.26</version></dependency><!-- logback 日志實現 --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency><!-- junit 單元測試 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency>B logback.xml 配置文件
<?xml version="1.0" encoding="utf-8" ?> <configuration><!--配置集中管理屬性我們可以直接改屬性的 value 值格式:${name}--><property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"></property><!--日志輸出格式:%-5level%d{yyyy-MM-dd HH:mm:ss.SSS}日期%c類的完整信息%M為method%L為行號%thread線程名稱%m或者%msg 為信息%n換行--><!-- 控制臺日志輸出的 appender --><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><!-- 控制輸出流對象 默認System.out 改為 System.err --><target>System.err</target><!-- 日志消息格式配置 --><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>${pattern}</pattern></encoder></appender><!-- root logger 配置 --><root level="ALL"><appender-ref ref="console"></appender-ref></root> </configuration>C 編寫代碼
public class LogbackTest {public static final Logger LOGGER = LoggerFactory.getLogger(LogbackTest.class);// 快速入門@Testpublic void testQuick() throws Exception {// 日志輸出LOGGER.error("error");LOGGER.warn("warning");LOGGER.info("info");LOGGER.debug("debug"); // 默認的日志級別信息LOGGER.trace("trace"); // 追蹤信息} }D 運行結果截圖
上述是通過logback 將日志輸出到控制臺,實際生產環境,我們更希望將日志寫入到日志文件中去。接下來,我們一起操作一下。
1)編寫依賴
<!-- slf4j 日志門面 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.26</version></dependency><!-- logback 日志實現 --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency><!-- junit 單元測試 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency>2)logback.xml 配置可向控制臺、文件、html文件輸出
<?xml version="1.0" encoding="utf-8" ?> <configuration><!--配置集中管理屬性我們可以直接改屬性的 value 值格式:${name}--><property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"></property><!--日志輸出格式:%-5level%d{yyyy-MM-dd HH:mm:ss.SSS}日期%c類的完整信息%M為method%L為行號%thread線程名稱%m或者%msg 為信息%n換行--><!-- 定義日志文件保存路徑屬性 --><property name="log_dir" value="logs"></property><!-- 控制臺日志輸出的 appender --><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><!-- 控制輸出流對象 默認System.out 改為 System.err --><target>System.err</target><!-- 日志消息格式配置 --><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>${pattern}</pattern></encoder></appender><!-- 日志文件輸出的 appender --><appender name="file" class="ch.qos.logback.core.FileAppender"><!-- 日志文件保存路徑 --><file>${log_dir}/logback.log</file><!-- 日志消息格式配置,默認格式 --><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>${pattern}</pattern></encoder></appender><!-- html 格式日志文件輸出 appender 【查閱較為方便】 --><appender name="htmlFile" class="ch.qos.logback.core.FileAppender"><!-- 日志文件保存路徑 --><file>${log_dir}/logback.html</file><!-- html 消息格式配置 --><encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"><layout class="ch.qos.logback.classic.html.HTMLLayout"><!--<pattern>${pattern}</pattern>--><pattern>%-5level%d{yyyy-MM-dd HH:mm:ss.SSS}%c%M%L%thread%m</pattern></layout></encoder></appender><!-- root logger 配置 --><root level="ALL"><appender-ref ref="console"/> <!-- 控制臺輸出 --><appender-ref ref="file"/> <!-- 文件輸出 --><appender-ref ref="htmlFile"/> <!-- 文件輸出 --></root> </configuration>3)編寫代碼
public class LogbackTest {public static final Logger LOGGER = LoggerFactory.getLogger(LogbackTest.class);// 快速入門@Testpublic void testQuick() throws Exception {// 日志輸出LOGGER.error("error");LOGGER.warn("warning");LOGGER.info("info");LOGGER.debug("debug"); // 默認的日志級別信息LOGGER.trace("trace"); // 追蹤信息} }4)運行結果截圖
文件logback.log 日志數據
文件logback.html 日志數據,使用瀏覽器打開截圖
但我們在實際生產環境上,需要按時間、文件大小進行拆分。需要怎么做呢?請繼續往下看
1)編寫maven 依賴
<!-- slf4j 日志門面 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.26</version></dependency><!-- logback 日志實現 --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency><!-- junit 單元測試 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency>2)配置按文件大小、時間輸出的日志策略
<?xml version="1.0" encoding="utf-8" ?> <configuration><!--配置集中管理屬性我們可以直接改屬性的 value 值格式:${name}--><property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"></property><!--日志輸出格式:%-5level%d{yyyy-MM-dd HH:mm:ss.SSS}日期%c類的完整信息%M為method%L為行號%thread線程名稱%m或者%msg 為信息%n換行--><!-- 定義日志文件保存路徑屬性 --><property name="log_dir" value="logs"></property><!-- 控制臺日志輸出的 appender --><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><!-- 控制輸出流對象 默認System.out 改為 System.err --><target>System.err</target><!-- 日志消息格式配置 --><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>${pattern}</pattern></encoder></appender><!-- 日志文件輸出的 appender --><appender name="file" class="ch.qos.logback.core.FileAppender"><!-- 日志文件保存路徑 --><file>${log_dir}/logback.log</file><!-- 日志消息格式配置,默認格式 --><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>${pattern}</pattern></encoder></appender><!-- html 格式日志文件輸出 appender 【查閱較為方便】 --><appender name="htmlFile" class="ch.qos.logback.core.FileAppender"><!-- 日志文件保存路徑 --><file>${log_dir}/logback.html</file><!-- html 消息格式配置 --><encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"><layout class="ch.qos.logback.classic.html.HTMLLayout"><!--<pattern>${pattern}</pattern>--><pattern>%-5level%d{yyyy-MM-dd HH:mm:ss.SSS}%c%M%L%thread%m</pattern></layout></encoder></appender><!-- 日志拆分和歸檔壓縮的 appender 對象 --><appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 日志文件保存路徑 --><file>${log_dir}/roll_logback.log</file><!-- 日志消息格式配置 --><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>${pattern}</pattern></encoder><!-- 指定拆分規則 --><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!-- 按照時間和壓縮格式聲明拆分的文件名 --><fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd-HH-mm-ss}.log%i.log</fileNamePattern><!-- 按照文件大小拆分 --><maxFileSize>1MB</maxFileSize></rollingPolicy></appender><!-- root logger 配置 --><root level="ALL"><appender-ref ref="console"/> <!-- 控制臺輸出 --><appender-ref ref="file"/> <!-- 文件輸出 --><appender-ref ref="htmlFile"/> <!-- html文件輸出 --><appender-ref ref="rollFile"/> <!-- 拆分文件輸出 --></root> </configuration>3)編寫代碼
public class LogbackTest {public static final Logger LOGGER = LoggerFactory.getLogger(LogbackTest.class);// 快速入門@Testpublic void testQuick() throws Exception {for (int i = 0; i < 10000; i++) {// 日志輸出LOGGER.error("error");LOGGER.warn("warning");LOGGER.info("info");LOGGER.debug("debug"); // 默認的日志級別信息LOGGER.trace("trace"); // 追蹤信息}} }4)運行結果截圖
有些時候,我們需要設置一下日志的過濾級別。比如,只輸出ERROR 級別的日志
<!-- 日志拆分和歸檔壓縮的 appender 對象 --><appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 日志文件保存路徑 --><file>${log_dir}/roll_logback.log</file><!-- 日志消息格式配置 --><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>${pattern}</pattern></encoder><!-- 指定拆分規則 --><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!-- 按照時間和壓縮格式聲明拆分的文件名 --><fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd-HH-mm-ss}.log%i.log</fileNamePattern><!-- 按照文件大小拆分 --><maxFileSize>1MB</maxFileSize></rollingPolicy><!-- 日志級別過濾器 --><filter class="ch.qos.logback.classic.filter.LevelFilter"><!-- 日志過濾規則 --><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender>為了提升日志輸出性能,往往需要異步日志輸出
<!-- 異步日志 --><appender name="async" class="ch.qos.logback.classic.AsyncAppender"><!-- 指定某個具體的 appender --><appender-ref ref="rollFile"/></appender>全部配置
<?xml version="1.0" encoding="utf-8" ?> <configuration><!--配置集中管理屬性我們可以直接改屬性的 value 值格式:${name}--><property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"></property><!--日志輸出格式:%-5level%d{yyyy-MM-dd HH:mm:ss.SSS}日期%c類的完整信息%M為method%L為行號%thread線程名稱%m或者%msg 為信息%n換行--><!-- 定義日志文件保存路徑屬性 --><property name="log_dir" value="logs"></property><!-- 控制臺日志輸出的 appender --><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><!-- 控制輸出流對象 默認System.out 改為 System.err --><target>System.err</target><!-- 日志消息格式配置 --><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>${pattern}</pattern></encoder></appender><!-- 日志文件輸出的 appender --><appender name="file" class="ch.qos.logback.core.FileAppender"><!-- 日志文件保存路徑 --><file>${log_dir}/logback.log</file><!-- 日志消息格式配置,默認格式 --><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>${pattern}</pattern></encoder></appender><!-- html 格式日志文件輸出 appender 【查閱較為方便】 --><appender name="htmlFile" class="ch.qos.logback.core.FileAppender"><!-- 日志文件保存路徑 --><file>${log_dir}/logback.html</file><!-- html 消息格式配置 --><encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"><layout class="ch.qos.logback.classic.html.HTMLLayout"><!--<pattern>${pattern}</pattern>--><pattern>%-5level%d{yyyy-MM-dd HH:mm:ss.SSS}%c%M%L%thread%m</pattern></layout></encoder></appender><!-- 日志拆分和歸檔壓縮的 appender 對象 --><appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 日志文件保存路徑 --><file>${log_dir}/roll_logback.log</file><!-- 日志消息格式配置 --><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>${pattern}</pattern></encoder><!-- 指定拆分規則 --><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!-- 按照時間和壓縮格式聲明拆分的文件名 --><fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd-HH-mm-ss}.log%i.log</fileNamePattern><!-- 按照文件大小拆分 --><maxFileSize>1MB</maxFileSize></rollingPolicy><!-- 日志級別過濾器 --><filter class="ch.qos.logback.classic.filter.LevelFilter"><!-- 日志過濾規則 --><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><!-- 異步日志 --><appender name="async" class="ch.qos.logback.classic.AsyncAppender"><!-- 指定某個具體的 appender --><appender-ref ref="rollFile"/></appender><!-- root logger 配置 --><root level="ALL"><appender-ref ref="console"/> <!-- 控制臺輸出 --><appender-ref ref="file"/> <!-- 文件輸出 --><appender-ref ref="htmlFile"/> <!-- html文件輸出 --><!--<appender-ref ref="rollFile"/> --> <!-- 拆分文件輸出 --><appender-ref ref="async"/></root> </configuration>logback 可將log4j 配置轉成logback.xml,官網鏈接:http://logback.qos.ch/index.html
9.3 logback-access 的使用
logback-access 模塊與Servlet 容器(如Tomcat 和 Jetty)集成,以提供HTTP 訪問日志功能。我們可以使用logback-access 模塊來替換tomcat 的訪問日志。
1、將logback-access.jar 與 logback-core.jar 復制到 $TOMCAT_HOME/lib/目錄下;這兩個jar 可通過maven 依賴下載下來
可通過我上傳的jar 文件下載,免費的。下載鏈接:logback-core 和logback-access 1.2.3.zip
<!-- logback 日志實現 --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency><!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-access --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-access</artifactId><version>1.2.3</version></dependency>2、修改$TOMCAT_HOME/conf/server.xml 中的Host 元素中添加:
<Value className="ch.qos.logback.access.tomcat.LogbackValue" />3、logback 默認會在$TOMCAT_HOME/conf 下查找件 logback-access.xml 配置文件,我們在conf 目錄下創建logback-access.xml 文件,添加如下內容:
<?xml version="1.0" encoding="UTF-8"?> <configuration><!-- always a good activate OnConsoleStatusListener --><statusListtener class="ch.qos.logback.core.status.OnConsoleStatusListener"/><!-- 日志保存路徑 --><property name="LOG_DIR" value="${catalina.base}/logs"/><!-- 日志拆分和歸檔壓縮的 appender 對象 --><appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 日志文件保存路徑 --><file>${LOG_DIR}/access.log</file><!-- 指定拆分規則:以天為單位的拆分規則,將其進行壓縮 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>access.%d{yyyy-MM-dd}.log.zip</fileNamePattern></rollingPolicy><encoder><!-- 日志消息格式表達式 --><pattern>%h %l %u [%t] "%r" %s %b "%i{Referer}" "%i{User-Agent}"</pattern><!--<pattern>combined</pattern>--></encoder></appender><appender-ref ref="FILE"/> </configuration>4、官方配置:https://logback.qos.ch/access.html#configuration
修改好配置后,需要重新啟動tomcat;再次打開tomcat界面并點擊某些按鈕,查看在logs目錄下是否有:日志生成access.log 文件
log4j2 的使用
Apache Log4j2 是對Log4j 的升級版本,參考了logback 的一些優秀的設計,并且修復了一些問題,因此帶來了一些重大的提升,主要有:
- 異常處理,在logback中,Appender中的異常不會被應用感知到,但是在log4j2中,提供了一些異常處理機制。
- 性能提升,log4j2 相較于log4j 和 logback 都具有明顯的性能提升,有18倍性能提升,后面會有官方測試的數據。
- 自動重載配置,參考了logback的設計,當然會提供自動刷新參數配置,最實用的就是我們在生產上可以動態的修改日志的級別而不需要重啟應用。
- 無垃圾機制,log4j2 在大部分情況下,都可以使用其設計的一套無垃圾機制【對象重用、內存緩沖】,避免頻繁的日志收集導致的 jvm gc。
官網:https://logging.apache.org/log4j/2.x/
一. Log4j2 入門
目前市面上最主流的日志門面就是SLF4J,雖然Log4j2 也是日志門面,因為它的日志實現功能非常強大,性能優越。所以大家一般還是將 Log4j2 看作是日志的實現,Slf4j + Log4j2 應該是未來的大勢所趨。
1.添加依賴
<dependencies><!-- log4j2 日志門面 --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>2.11.1</version></dependency><!-- log4j2 日志實面 --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.11.1</version></dependency><!-- junit 單元測試 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency></dependencies>2.編寫代碼
package com.log;import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.Test;public class Log4j2Test {// 定義日志記錄器對象public static final Logger LOGGER = LogManager.getLogger(Log4j2Test.class);// 快速入門@Testpublic void testQuick() throws Exception {// 日志消息輸出LOGGER.fatal("fatal");LOGGER.error("error");LOGGER.warn("warn");LOGGER.info("info");LOGGER.debug("debug");LOGGER.trace("trace");} }3.運行結果截圖
上述報錯是因為沒有找到日志配置文件,需要添加 log4j2.xml 配置文件到 resource 目錄下【classpath】下。log4j2 默認加載classpath 下的 log4j2.xml 文件中的配置
<?xml version="1.0" encoding="UTF-8" ?> <configuration status="warn" monitorInterval="5"><properties><property name="LOG_HOME">E:/logs</property></properties><Appenders><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n" /></Console><File name="file" fileName="${LOG_HOME}/myfile.log"><PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" /></File><RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log"><PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" /></RandomAccessFile><RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log"filePattern="E:/logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyy-MM-dd-HH-mm}-%i.log"><ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" /><PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n" /><Policies><OnStartupTriggeringPolicy /><SizeBasedTriggeringPolicy size="10 MB" /><TimeBasedTriggeringPolicy /></Policies><DefaultRolloverStrategy max="30" /></RollingFile></Appenders><Loggers><Root level="trace"><AppenderRef ref="Console" /></Root></Loggers> </configuration>實際生產中,我們往往需要 slf4j + log4j2 進行日志管理;就需要導入slf4j 日志門面、log4j2 適配器;然后使用 slf4j 方法接口名稱來輸出日志
<dependencies><!-- 使用slf4j 作為日志門面 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.26</version></dependency><!-- 使用 log4j2 的適配器進行綁定 --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j-impl</artifactId><version>2.9.1</version></dependency><!-- log4j2 日志門面 --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>2.11.1</version></dependency><!-- log4j2 日志實面 --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.11.1</version></dependency><!-- junit 單元測試 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency></dependencies>?編寫 slf4j + log4j2 代碼
package com.log;import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory;public class Slf4jTest {// 為了保證使用時,不需要每次都去創建logger 對象,我們聲明靜態常量public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);// 快速入門@Testpublic void testQuick(){// 日志輸出LOGGER.error("error");LOGGER.warn("warning");LOGGER.info("info"); // 默認的日志級別信息LOGGER.debug("debug");LOGGER.trace("trace"); // 追蹤信息// 使用占位符輸出日志信息String name = "java_log";Integer age = 18;LOGGER.info("用戶:{},{}", name, age);// 將系統的異常信息輸出try {int i = 1 / 0;} catch (Exception e){// e.printStackTrace();LOGGER.error("出現異常:", e);}} }log4j2? ? vs? ?slf4j + log4j2 日志輸出對比:
二、Log4j2 配置
log4j2 默認加載classpath 下的 log4j2.xml 文件中的配置。下面通過log4j2.xml 配置文件進行測試
<?xml version="1.0" encoding="UTF-8" ?> <!--status="warn" 日志框架本身的輸出日志級別,可以修改為debugmonitorInterval="5" 自動加載配置文件的間隔時間,不低于 5秒;生產環境中修改配置文件,是熱更新,無需重啟應用--> <configuration status="warn" monitorInterval="5"><!--集中配置屬性進行管理使用時通過:${name}--><properties><property name="LOG_HOME">D:/logs</property></properties><!-- 日志處理 --><Appenders><!-- 控制臺輸出 appender,SYSTEM_OUT輸出黑色,SYSTEM_ERR輸出紅色 --><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n" /></Console><!-- 日志文件輸出 appender --><File name="file" fileName="${LOG_HOME}/myfile.log"><PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" /></File><!-- 使用隨機讀寫流的日志文件輸出 appender,性能提高 --><RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log"><PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" /></RandomAccessFile><!-- 按照一定規則拆分的日志文件的appender --> <!-- 拆分后的文件 --><!-- filePattern="${LOG_HOME}/$${date:yyyy-MM-dd}/myrollog-%d{yyyy-MM-dd-HH-mm}-%i.log"> --><RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log"filePattern="${LOG_HOME}/$${date:yyyy-MM-dd}/myrollog-%d{yyyy-MM-dd}-%i.log"><!-- 日志級別過濾器 --><ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" /><!-- 日志消息格式 --><PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n" /><Policies><!-- 在系統啟動時,出發拆分規則,生產一個新的日志文件 --><OnStartupTriggeringPolicy /><!-- 按照文件大小拆分,10MB --><SizeBasedTriggeringPolicy size="2MB" /><!-- 按照時間節點拆分,規則根據filePattern定義的 --><TimeBasedTriggeringPolicy /></Policies><!-- 在同一個目錄下,文件的個限定為 30個,超過進行覆蓋 --><DefaultRolloverStrategy max="10" /></RollingFile></Appenders><!-- logger 定義 --><Loggers><!-- 使用 rootLogger 配置 日志級別 level="trace" --><Root level="trace"><!-- 指定日志使用的處理器 --><!-- <AppenderRef ref="Console" />--><AppenderRef ref="file" /><AppenderRef ref="rollingFile" /><AppenderRef ref="accessFile" /></Root></Loggers> </configuration>編寫代碼:
package com.log;import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory;public class Slf4jTest {// 為了保證使用時,不需要每次都去創建logger 對象,我們聲明靜態常量public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);// 快速入門@Testpublic void testQuick(){// 日志輸出for (int i = 0; i < 1000000; i++) {LOGGER.error("error");LOGGER.warn("warning");LOGGER.info("info"); // 默認的日志級別信息LOGGER.debug("debug");LOGGER.trace("trace"); // 追蹤信息}// 使用占位符輸出日志信息/*String name = "java_log";Integer age = 18;LOGGER.info("用戶:{},{}", name, age);// 將系統的異常信息輸出try {int i = 1 / 0;} catch (Exception e){// e.printStackTrace();LOGGER.error("出現異常:", e);}*/} }運行結果截圖:
?
三、Log4j2 異步日志
異步日志
log4j2 最大的特點就是異步日志,其性能的提升主要也是從異步日志中受益,我們來看看如何使用log4j2 的異步日志。
- 同步日志
- 異步日志
Log4j2 提供了兩種實現日志的方式,一個是通過AsyncAppender【幾乎沒人用】,一個是通過AsyncLogger【主要是這個】,分別對應前面我們說的Appender 組件和Logger 組件。
官網詳細介紹:http://logging.apache.org/log4j/2.x/performance.html
注意:配置異步日志需要添加依賴
<!--異步日志依賴 --><dependency><groupId>com.lmax</groupId><artifactId>disruptor</artifactId><version>3.3.4</version></dependency>1、AsyncAppender 方式【生產上幾乎不使用,因為性能低下】
<?xml version="1.0" encoding="UTF-8" ?> <!--status="warn" 日志框架本身的輸出日志級別,可以修改為debugmonitorInterval="5" 自動加載配置文件的間隔時間,不低于 5秒;生產環境中修改配置文件,是熱更新,無需重啟應用--> <configuration status="warn" monitorInterval="5"><!--集中配置屬性進行管理使用時通過:${name}--><properties><property name="LOG_HOME">D:/logs</property></properties><!-- 日志處理 --><Appenders><!-- 控制臺輸出 appender,SYSTEM_OUT輸出黑色,SYSTEM_ERR輸出紅色 --><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n" /></Console><!-- 日志文件輸出 appender --><File name="file" fileName="${LOG_HOME}/myfile.log"><!--<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />--><PatternLayout pattern="%d %p %c{1.} [%t] %m%n" /></File><Async name="Async"><AppenderRef ref="file" /></Async></Appenders><!-- logger 定義 --><Loggers><!-- 使用 rootLogger 配置 日志級別 level="trace" --><Root level="trace"><!-- 指定日志使用的處理器 --><AppenderRef ref="Console" /><!-- 使用異步 appender --><AppenderRef ref="Async" /></Root></Loggers> </configuration>2、編寫代碼
package com.log;import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory;public class Slf4jTest {// 為了保證使用時,不需要每次都去創建logger 對象,我們聲明靜態常量public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);// 快速入門@Testpublic void testQuick(){// 日志輸出LOGGER.error("error");LOGGER.warn("warning");LOGGER.info("info"); // 默認的日志級別信息LOGGER.debug("debug");LOGGER.trace("trace"); // 追蹤信息} }3、運行結果截圖,下面就是異步日志輸出
2、AsyncLogger 方式【生產上用得多,因為性能高】
AsyncLogger 才是log4j2 的重頭戲,也是官方推薦的異步方式。它可以調用Logger.log 返回的更快。你可以有兩種選擇:全局異步和混合異步。
- 全局異步就是,所有日志都異步的記錄,在配置文件上不用做任何改動,只需要添加一個 log4j2.component.properties 配置到 resources;
則此時,向控制臺、文件都是異步方式日志輸出
- 混合異步就是,你可以在應用中同時使用同步日志和異步日志,這使得日志的配置方式更加靈活,按如下配置主要是添加截圖部分即可:
同時,AsyncLogger 混合異動日志輸出需要將log4j2.component.properties 內容進行注釋【因為這個是配置AsyncLogger全局異步日志輸出】
如下配置:com.log 日志是異步的, root 日志是同步的。
使用異步日志需要注意的問題:
四、Log4j2 的性能
Log4j2 最牛的地方在于異步輸出日志時的性能表現,Log4j2 在多線程的環境下吞吐量與 Log4j 和 Logback 的比較如下圖。下圖比較中 Log4j2 有三種模式:
全局使用異步模式;
部分Logger采用異步模式;
異步Appenderf。
可以看出在前兩種模式下,Log4j2 的性能較之 Log4j 和Logback有很大的優勢。
無垃圾記錄
垃圾收集暫停是延遲峰值的常見原因,并且對于許多系統而言,花費大量精力來控制這些暫停。
許多日志庫(包括以前版本的Log4j)在穩態日志記錄期間分配臨時對象,如日志事件對象,字符串,字符數組,字節數組等。這會對垃圾收集器造成壓力并增加 GC 暫停發生的概率。
從版本2.6 開始,默認情況下 Log4j 以“無垃圾” 模式運行,其中重用對象和緩沖區,并且盡可能不分配臨時對象。還有一個“低垃圾”模式,它不是完全無垃圾,但不使用ThreadLocal 字段。
Log4j 2.6 中的無垃圾日志記錄部分通過重用ThreadLocal 字段中的對象來實現,部分通過在將文件轉換為字節時重用緩沖區來實現。
使用Log4j 2.5:內存分配速度809 MB / 秒,141個無效集合。
Log4j 2.6沒有分配臨時對象:0(零)垃圾回收。
有兩個單獨的系統屬性可用于手動控制Log4j 用于避免創建臨時對象的機制:
- log4j2.enableThreadlocals? -如果"true"(非Web應用程序的默認值)對象存儲在ThreadLocal 字段中并重新使用,否則將為每個日志事件創建新對象。
- log4j2.enableDirectEncoders -如果將"true" (默認)日志事件轉為文本,則將文本轉換為字節而不創建臨時對象。注意:由于共享緩沖區上的同步,在此模式下多線程應用程序的同步日志記錄性能可能更差。
真誠的建議:如果您的應用程序是多線程的并且日志記錄性能很重要,請考慮使用異步記錄器。
文章最后,給大家推薦一些受歡迎的技術博客鏈接:
歡迎掃描下方的二維碼或 搜索 公眾號“大數據高級架構師”,我們會有更多、且及時的資料推送給您,歡迎多多交流!
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
?? ? ??
?
總結
以上是生活随笔為你收集整理的多种java 日志框架【超详细图文】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 1、节气:开篇 - 中国的节气
- 下一篇: excel如何当计算机使用方法,如何让你