日誌概念 1. 日誌文件 日誌文件是用於記錄系統操作事件的文件集合 1.1 調試日誌 1.2 系統日誌 系統日誌是記錄系統中硬體、軟體和系統問題的信息,同時還可以監視系統中發生的事件。用戶可以通過它來檢查錯誤發生的原因,或者尋找受到攻擊時攻擊者留下的痕跡 日誌門面 當我們的系統變的更加複雜的時候,我 ...
日誌概念
1. 日誌文件
日誌文件是用於記錄系統操作事件的文件集合
1.1 調試日誌
1.2 系統日誌
系統日誌是記錄系統中硬體、軟體和系統問題的信息,同時還可以監視系統中發生的事件。用戶可以通過它來檢查錯誤發生的原因,或者尋找受到攻擊時攻擊者留下的痕跡
日誌門面
當我們的系統變的更加複雜的時候,我們的日誌就容易發生混亂。隨著系統開發的進行,可能會更新不同的日誌框架,造成當前系統中存在不同的日誌依賴,讓我們難以統一的管理和控制。所以我們需要借鑒 JDBC 的思想,為日誌系統也提供一套門面,那麼我們就可以面向這些介面規範來開發,避免了直接依賴具體的日誌框架。這樣我們的系統在日誌中,就存在了日誌的門面和日誌的實現
常見的日誌門面:JCL、Slf4j
常見的日誌實現:JUL、log4j、logback、log4j2
日誌門面和日誌實現的關係:
使用日誌門面的優勢:
- 面向介面開發,不再依賴具體的實現類,減少代碼的耦合
- 項目通過導入不同的日誌實現類,可以靈活的切換日誌框架
- 統一 API,方便開發者學習和使用
- 統一配置便於項目日誌的管理
JUL
JUL(Java util Logging)是 Java 原生的日誌實現框架,使用時不需要另外引用第三方類庫,相對其他日誌框架使用方便,學習簡單,能夠在小型應用中靈活使用
1. 架構
- Loggers:記錄器,應用程式通過獲取 Logger 對象,調用其 API 來發佈日誌信息
- 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. 入門案例
public class JULTest {
@Test
public void testQuick() throws Exception {
// 1.創建日誌記錄器對象
Logger logger = Logger.getLogger("com.itheima.log.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});
}
}
3. 日誌級別
JUL 中定義的日誌級別:SEVERE(最高值)、WARNING、INFO(預設級別)、CONFIG、FINE、FINER、FINEST(最低值)
還有兩個特殊的級別:OFF,可用來關閉日誌記錄、ALL,啟用所有消息的日誌記錄
@Test
public void testLogLevel() throws Exception {
// 1.獲取日誌對象
Logger logger = Logger.getLogger("com.itheima.log.QuickTest");
// 2.日誌記錄輸出
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("cofnig");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
雖然我們測試了 7 個日誌級別,但是預設只實現 info 以上的級別,info 以下不會輸出,我們可以自定義日誌級別配置
@Test
public void testLogConfig() throws Exception {
// 創建日誌記錄器對象
Logger logger = Logger.getLogger("com.itheima.log.JULTest");
// 一、自定義日誌級別
// a.關閉系統預設配置
logger.setUseParentHandlers(false);
// b.創建handler對象
ConsoleHandler consoleHandler = new ConsoleHandler();
// c.創建formatter對象
SimpleFormatter simpleFormatter = new SimpleFormatter();
// d.進行關聯
consoleHandler.setFormatter(simpleFormatter);
logger.addHandler(consoleHandler);
// e.設置日誌級別
logger.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);
// 二、輸出到日誌文件
FileHandler fileHandler = new FileHandler("d:/logs/jul.log");
fileHandler.setFormatter(simpleFormatter);
// 日誌記錄輸出
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
4. logger 之間的父子關係
JUL 中 Logger 之間存在父子關係,這種父子關係通過樹狀結構存儲,JUL 在初始化時會創建一個頂層 RootLogger 作為所有 Logger 父 Logger,存儲上作為樹狀結構的根節點,父子關係通過包路徑來關聯
@Test
public void testLogParent() throws Exception {
// 日誌記錄器對象父子關係
Logger logger1 = Logger.getLogger("com.itheima");
Logger logger2 = Logger.getLogger("com");
// true
System.out.println(logger1.getParent() == logger2);
// 所有日誌記錄器對象的頂級父元素 class 為 java.util.logging.LogManager$RootLogger
// logger2 parent:java.util.logging.LogManager$RootLogger,name:
System.out.println("logger2 parent:" + logger2.getParent() + ",name" +
logger2.getParent().getName());
// 一、自定義日誌級別
// a.關閉系統預設配置
logger2.setUseParentHandlers(false);
// b.創建handler對象
ConsoleHandler consoleHandler = new ConsoleHandler();
// c.創建formatter對象
SimpleFormatter simpleFormatter = new SimpleFormatter();
// d.進行關聯
consoleHandler.setFormatter(simpleFormatter);
logger2.addHandler(consoleHandler);
// e.設置日誌級別
logger2.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);
// 測試日誌記錄器對象父子關係,這裡會全部輸出,因為logger1繼承logger2的級別配置
logger1.severe("severe");
logger1.warning("warning");
logger1.info("info");
logger1.config("config");
logger1.fine("fine");
logger1.finer("finer");
logger1.finest("finest");
}
5. 日誌的配置文件
預設配置文件路徑 $JAVAHOME\jre\lib\logging.properties
@Test
public void testProperties() throws Exception {
// 讀取自定義配置文件
InputStream in = JULTest.class.getClassLoader().getResourceAsStream("logging.properties");
// 獲取日誌管理器對象
LogManager logManager = LogManager.getLogManager();
// 通過日誌管理器載入配置文件
logManager.readConfiguration(in);
Logger logger = Logger.getLogger("com.itheima.log.JULTest");
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
配置文件:
## RootLogger使用的處理器(獲取時設置)
handlers= java.util.logging.ConsoleHandler
# RootLogger日誌等級
.level= INFO
# 自定義Logger
com.itheima.handlers= java.util.logging.FileHandler
# 自定義Logger日誌等級
com.itheima.level= INFO
# 忽略父日誌設置
com.itheima.useParentHandlers=false
## 控制台處理器
# 輸出日誌級別
java.util.logging.ConsoleHandler.level = ALL
# 輸出日誌格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# 指定handler對象的字元集
java.util.logging.ConsoleHandler.encoding = UTF-8
# 指定日誌消息格式
java.util.logging.SimpleFormatter.format = %4$s: %5$s [%1$tc]%n
## 文件處理器
# 輸出日誌級別
java.util.logging.FileHandler.level=INFO
# 輸出日誌格式
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 輸出日誌文件路徑
# /logs/java0.log
java.util.logging.FileHandler.pattern = /logs/java%u.log
# 輸出日誌文件限制大小(50000位元組)
java.util.logging.FileHandler.limit = 50000
# 輸出日誌文件限制個數
java.util.logging.FileHandler.count = 10
# 輸出日誌文件是否是追加
java.util.logging.FileHandler.append=true
6. 日誌原理解析
- 初始化 LogManager
- LogManager 載入 logging.properties 配置
- 添加 Logger 到 LogManager
- 從單例 LogManager 獲取 Logger
- 設置級別 Level,並指定日誌記錄 LogRecord
- Filter 提供了日誌級別之外更細粒度的控制
- Handler 是用來處理日誌輸出位置
- Formatter 是用來格式化 LogRecord 的
Log4J
Log4j 是Apache 下的一款開源的日誌實現框架,通過在項目中使用 Log4J,我們可以控制日誌信息輸出到控制台、文件、甚至是資料庫中。我們可以控制每一條日誌的輸出格式,通過定義日誌的輸出級別,可以更靈活的控制日誌的輸出過程,方便項目調試
1. 入門案例
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
public class Log4jTest {
@Test
public void testQuick() throws Exception {
// 初始化系統配置,不需要配置文件
BasicConfigurator.configure();
// 創建日誌記錄器對象
Logger logger = Logger.getLogger(Log4jTest.class);
// 日誌記錄輸出
logger.info("hello log4j");
// 日誌級別
logger.fatal("fatal"); // 嚴重錯誤,一般會造成系統崩潰和終止運行
logger.error("error"); // 錯誤信息,但不會影響系統運行
logger.warn("warn"); // 警告信息,可能會發生問題
logger.info("info"); // 程式運行信息,資料庫的連接、網路、IO操作等
logger.debug("debug"); // 調試信息,一般在開發階段使用,記錄程式的變數、參數等
logger.trace("trace"); // 追蹤信息,記錄程式的所有流程信息
}
}
2. 日誌級別
每個 Logger 都被定義了一個日誌級別,用來控制日誌信息的輸出,日誌級別從高到低分為:
- fatal:指出每個嚴重的錯誤事件將會導致應用程式的退出
- error:指出雖然發生錯誤事件,但仍然不影響系統的繼續運行
- warn:表明會出現潛在的錯誤情形
- info:一般和在粗粒度級別上,強調應用程式的運行全程
- debug:一般用於細粒度級別上,對調試應用程式非常有幫助
- trace 是程式追蹤,可以用於輸出程式運行中的變數,顯示執行的流程
還有兩個特殊的級別:OFF 可用來關閉日誌記錄、ALL 啟用所有消息的日誌記錄
3. Log4j 組件
3.1 Loggers
日誌記錄器,負責收集處理日誌記錄,實例的命名就是類的全限定名
Logger 的名字大小寫敏感,其命名有繼承機制,例如:name 為 org.apache.commons 的 logger 會繼承 name 為 org.apache 的 logger
Log4J 中有一個特殊的 logger 叫做 root,他是所有 logger 的根,可以用 Logger.getRootLogger() 方法獲取,其他所有的 logger 都會直接或間接地繼承自 root
3.2 Appenders
Appender 用來指定日誌輸出到哪個地方,可以同時指定日誌的輸出目的地。Log4j 常用的輸出目的地有以下幾種:
- ConsoleAppender:將日誌輸出到控制台
- FileAppender:將日誌輸出到文件中
- DailyRollingFileAppender:將日誌輸出到一個日誌文件,並且每天輸出到一個新的文件
- RollingFileAppender:將日誌信息輸出到一個日誌文件,並且指定文件的尺寸,當文件大小達到指定尺寸時,會自動把文件改名,同時產生一個新的文件
- JDBCAppender:把日誌信息保存到資料庫中
3.3 Layouts
佈局器 Layouts 用於控制日誌輸出內容的格式,讓我們可以使用各種需要的格式輸出日誌。Log4j 常用的 Layouts:
- HTMLLayout:格式化日誌輸出為 HTML 表格形式
- SimpleLayout:簡單的日誌輸出格式化,列印的日誌格式為(info - message)
- PatternLayout:最強大的格式化期,可以根據自定義格式輸出日誌,如果沒有指定轉換格式,就使用預設的轉換格式
4. 配置文件詳解
#指定日誌的輸出級別與輸出端
log4j.rootLogger=INFO,Console
# 自定義Logger
# com.itheima包下的logger使用文件輸出配置
log4j.logger.com.itheima = info,file
# apache包下的logger輸出error級別日誌
log4j.logger.org.apache = error
# 控制台輸出配置
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.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字元,就從左邊交遠銷出的字元截掉
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
# 文件輸出配置
log4j.appender.A = org.apache.log4j.DailyRollingFileAppender
#指定日誌的輸出路徑
log4j.appender.A.File = D:/log.txt
log4j.appender.A.Append = true
#使用自定義日誌格式化器
log4j.appender.A.layout = org.apache.log4j.PatternLayout
#指定日誌的輸出格式
log4j.appender.A.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] -
[%p] %m%n
#指定日誌的文件編碼
log4j.appender.A.encoding=UTF-8
#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/test
log4j.appender.logDB.User=root
log4j.appender.logDB.Password=root
log4j.appender.logDB.Sql=INSERT INTO
log(project_name,create_date,level,category,file_name,thread_name,line,all_categ
ory,message) values('itcast','%d{yyyy-MM-dd
HH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')
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`)
);
JCL
全稱為 Jakarta Commons Logging,是 Apache 提供的一個日誌門面實現,為所有的 Java 日誌實現提供一個統一的介面,它自身也提供一個日誌的實現,但是功能較弱,它允許開發人員使用不同的具體日誌實現工具,如:Log4J,JUL
JCL 有兩個基本的抽象類:Log(基本記錄器)和 LogFactory(負責創建 Log 實例)
1. 入門案例
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
public class JULTest {
@Test
public void testQuick() throws Exception {
// 創建日誌對象
Log log = LogFactory.getLog(JULTest.class);
// 日誌記錄輸出
log.fatal("fatal");
log.error("error");
log.warn("warn");
log.info("info");
log.debug("debug");
}
}
2. JCL 原理
JCL 通過 LogFactory 動態載入 Log 實現類
. 獲取具體的日誌實現的源碼如下:
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"};
for(int i = 0; i < classesToDiscover.length && result == null; ++i) {
result = this.createLogFromClass(classesToDiscover[i], logCategory, true);
}
classesToDiscover 數組是 JCL 支持的日誌實現數組,對它進行遍歷,嘗試通過反射去實現
SLF4J
SLF4J(Simple Logging Facade For Java)主要是為了給 Java 日誌訪問提供一套標準、規範的 API 框架,其主要意義在於提供介面,具體的實現可以交由其他日誌框架,例如 log4j 和 logback 等
SLF4J 也提供了功能較為簡單的實現,但是一般很少用到。對於一般的 Java 項目而言,日誌框架會選擇 slf4j-api 作為門面,配上具體的實現框架(log4j、logback 等),中間使用橋接器完成橋接
SLF4J 的優勢:
- SLF4J 提供了對所有流行的日誌框架的綁定,可以在部署時切換到所需的日誌記錄框架
- 無論使用哪種綁定,SLF4J 都支持參數化日誌記錄消息
- SLF4J 提供了一個稱為橋接器的工具,可以將現有項目的使用日誌框架(JCL、JUL、Log4j)遷移到 SLF4J
1. 入門案例
<!--slf4j core 使用slf4j必須添加-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.27</version>
</dependency>
<!--slf4j 自帶的簡單日誌實現 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.27</version>
</dependency>
public class Slf4jTest {
// 聲明日誌對象
public final static Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);
@Test
public void testQuick() throws Exception {
// 列印日誌信息
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("info");
LOGGER.debug("debug");
LOGGER.trace("trace");
// 使用占位符輸出日誌信息
String name = "jack";
Integer age = 18;
LOGGER.info("用戶:{},{}", name, age);
// 將系統異常信息寫入日誌
try {
int i = 1 / 0;
} catch (Exception e) {
// e.printStackTrace();
LOGGER.info("出現異常:", e);
}
}
}
2. 綁定日誌的實現
SLF4J 提供了用於綁定日誌實現框架的 Jar 文件,每個綁定對應一個受支持的框架
使用 SLF4J 的日誌綁定流程如下:
- 添加 slf4j-api 的依賴
- 使用 slf4j 的 API 在項目中進行統一的日誌記錄
- 綁定具體的日誌實現框架
- 綁定已經實現了 slf4j 的日誌框架,可直接添加對應依賴
- 綁定沒有實現 slf4j 的日誌框架,先添加日誌的適配器,再添加實現類的依賴
- slf4j 有且僅有一個日誌實現框架的綁定(如果出現多個預設使用第一個依賴日誌實現)
通過 maven 引入常見的日誌實現框架:
<!--slf4j core 使用slf4j必須添加-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.27</version>
</dependency>
<!-- log4j 適配器 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.27</version>
</dependency>
<!-- log4j 依賴 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- jul 適配器 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.27</version>
</dependency>
<!-- jul 是 jdk 自帶的日誌實現,不需要引入依賴 -->
<!-- logback 依賴 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
要切換日誌框架,只需替換類路徑上的 slf4j 綁定,例如,要從 java.util.logging 切換到 log4j,只需將 java.util.logging 的依賴替換為 log4j 的依賴即可
如果出現多個日誌實現。預設載入第一個實現,也就是會使用 log4j
3. 橋接舊的日誌框架
項目初期使用了一個簡單的日誌實現框架,如 Log4J,後續需要升級成 Slf4J + Logback,但又不想修改原有 Log4J 的代碼,因此可以使用橋接器,將原本對 Log4J 的 API 調用重定向到 Slf4J 的實現
例如,最初的項目使用了 Log4J
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
public class Log4jTest {
// 聲明日誌對象
public final static Logger LOGGER = Logger.getLogger(Log4jTest.class);
@Test
public void test01() throws Exception {
// 列印日誌信息
LOGGER.info("hello log4j");
}
}
如果我們要使用 SLF4J 的橋接器,替換原有的日誌框架,那麼我們需要做的第一件事情,就是刪除掉原有項目中的日誌框架的依賴,然後替換成 SLF4J 提供的橋接器
<!-- <dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency> -->
<!-- log4j 橋接器 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.27</version>
</dependency>
註意的問題:
- log4j-over-slf4j.jar 和 slf4j-log4j12.jar 不能同時出現,前一個 jar 文件將導致 log4j 將日誌系統的選擇委托給 slf4j,後一個 jar 文件將導致 slf4j 將日誌系統的選擇委托給 log4j,從而導致無限迴圈,其他日誌實現框架同理
- 所有的橋接都只對 Logger 日誌記錄器對象有效,如果程式中調用了內部的配置類或者是 Appender、Filter 等對象,將無法產生效果
4. SLF4J 原理解析
- SLF4J 通過 LoggerFactory 載入日誌具體的實現對象
- LoggerFactory 在初始化的過程中,會通過 performInitialization() 方法綁定具體的日誌實現
- 在綁定具體實現的時候,通過類載入器,載入 org/slf4j/impl/StaticLoggerBinder.class
- 所以,只要是一個日誌實現框架,在 org.slf4j.impl 包中提供一個自己的StaticLoggerBinder類,在其中提供具體日誌實現的 LoggerFactory 就可以被 SLF4J 所載入
Logback
Logback 是由 log4j 創始人設計的另一個開源日誌組件,性能優於 log4j
Logback 主要分為三個模塊:
- logback-core:其它兩個模塊的基礎模塊
- logback-classic:它是 log4j 的一個改良版本,同時它完整實現了 slf4j API
- logback-access:訪問模塊與 Servlet 容器集成提供通過 Http 來訪問日誌的功能
1. 入門案例
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
public class LogBackTest {
//定義日誌對象
public final static Logger LOGGER = LoggerFactory.getLogger(LogBackTest.class);
@Test
public void testSlf4j(){
//列印日誌信息
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("info");
LOGGER.debug("debug");
LOGGER.trace("trace");
}
}
2. Logback 配置
Logback 會依次讀取以下類型配置文件,如果均不存在會採用預設配置:
- logback.groovy
- logback-test.xml
- logback.xml
基本配置信息如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
格式化輸出:
%d:日期
%thread:線程名
%-5level:級別從左顯示5個字元寬度
%n:換行符
%d{yyyy-MM-dd HH:mm:ss.SSS}:日期
%c:類的完整名稱
%M:方法名
%L:行號
%thread:線程名稱
%m或%msg:日誌信息
-->
<property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c %M %L [%thread] %m %n"/>
<!--
Appender:設置日誌信息的去向,常用的有以下幾個:
ch.qos.logback.core.ConsoleAppender (控制台)
ch.qos.logback.core.rolling.RollingFileAppender (文件大小到達指定尺
寸的時候產生一個新文件)
ch.qos.logback.core.FileAppender (文件)
-->
<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>
<!-- 日誌文件存放目錄 -->
<property name="log_dir" value="d:/logs"></property>
<!-- 日誌文件輸出 appender 對象 -->
<appender name="file" class="ch.qos.logback.core.FileAppender">
<!-- 日誌格式配置 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
<!-- 日誌輸出路徑 -->
<file>${log_dir}/logback.log</file>
</appender>
<!-- 生成 html 格式 appender 對象 -->
<appender name="htmlFile" class="ch.qos.logback.core.FileAppender">
<!-- 日誌格式配置 -->
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>${pattern}</pattern>
</layout>
</encoder>
<!--日誌輸出路徑-->
<file>${log_dir}/logback.html</file>
</appender>
<!-- 日誌文件拆分和歸檔的appender對象-->
<appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日誌格式配置-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
<!--日誌輸出路徑-->
<file>${log_dir}/roll_logback.log</file>
<!--指定日誌文件拆分和壓縮規則-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--通過指定壓縮文件名稱,來確定分割文件方式-->
<fileNamePattern>${log_dir}/rolling.%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
<!--文件拆分大小-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
<!-- 日誌級別過濾 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 日誌級別 -->
<level>error</level>
<!-- 對error級別及以上的日誌進行記錄 -->
<onMatch>ACCEPT</onMatch>
<!-- 攔截error級別以下的日誌 -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 開啟非同步日誌記錄,提升性能 -->
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="rollFile"/>
</appender>
<!-- RootLogger 對象 -->
<!-- level:用來設置列印級別,大小寫無關:TRACE,DEBUG,INFO,WARN,ERROR,ALL,OFF,預設debug -->
<!-- <root>可以包含零個或多個<appender-ref>元素,標識這個appender將會添加到這個logger-->
<root level="all">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
<appender-ref ref="htmlFile"/>
</root>
<!--自定義 logger -->
<!-- name:指定受此logger約束的某一個包或者具體的某一個類 -->
<!-- level:用來設置列印級別,大小寫無關:TRACE,DEBUG,INFO,WARN,ERROR,ALL,OFF,預設繼承父級的級別 -->
<!-- additivity:是否從 rootLogger 繼承配置 -->
<logger name="com.itheima" level="debug" additivity="false">
<appender-ref ref="async"/>
</logger>
</configuration>
Log4j2
Log4j2 也是日誌門面,因為它的日誌實現功能非常強大,性能優越,所以一般還是將 Log4j2 看作是日誌的實現
1. 入門案例
<!-- Log4j2 門面API-->
<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>
public class Log4j2Test {
// 定義日誌記錄器對象
public static final Logger LOGGER = LogManager.getLogger(Log4j2Test.class);
@Test
public void testQuick() throws Exception {
LOGGER.fatal("fatal");
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("info");
LOGGER.debug("debug");
LOGGER.trace("trace");
}
}
也可以使用 slf4j 作為日誌的門面,log4j2 作為日誌的實現
<!-- 使用slf4j作為日誌的門面,使用log4j2來記錄日誌 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- log4j2的適配器 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.10.0</version>
</dependency>
<!-- Log4j2 門面API-->
<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>
2. Log4j2 配置
log4j2 預設載入 classpath 下的 log4j2.xml 文件中的配置
<?xml version="1.0" encoding="UTF-8"?>
<!--
status:日誌本身的輸出日誌級別
monitorInterval:檢查配置文件是否更新的間隔時間
-->
<Configuration status="warn" monitorInterval="5">
<!-- 配置屬性管理,使用時通過 ${name} 來引用 -->
<properties>
<property name="LOG_HOME">D:/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="D:/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" />
<!-- 按照時間節點拆分,規則由filePattern定義 -->
<TimeBasedTriggeringPolicy />
</Policies>
<!-- 同一目錄下,文件個數限定為30個,超過將進行覆蓋 -->
<DefaultRolloverStrategy max="30" />
</RollingFile>
<!-- logger 定義 -->
<Loggers>
<!-- rootLogger 配置 -->
<Root level="trace">
<AppenderRef ref="Console" />
<AppenderRef ref="file" />
<AppenderRef ref="accessFile" />
<AppenderRef ref="rollingFile" />
</Root>
</Loggers>
</Appenders>
3. Log4j2 非同步日誌
Log4j2 提供了兩種實現日誌的方式,一個是通過 AsyncAppender,一個是通過 AsyncLogger,分別對應前面的 Appender 組件和 Logger 組件
配置非同步日誌需要添加依賴
<!-- 非同步日誌依賴 -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.4</version>
</dependency>
使用 AsyncAppender 的方式
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
<properties>
<property name="LOG_HOME">D:/logs</property>
</properties>
<Appenders>
<File name="file" fileName="${LOG_HOME}/myfile.log">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
</File>
<Async name="Async">
<AppenderRef ref="file"/>
</Async>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="Async"/>
</Root>
</Loggers>
</Configuration
AsyncLogger 是官方推薦的非同步方式,它可以使得調用 Logger.log 返回得更快,並且有兩種選擇:全局非同步和混合非同步
-
全局非同步:所有的日誌都非同步記錄,配置文件上不用做任何改動,只需要在類路徑添加一個名為
log4j2.component.properties
配置文件,並包含如下內容:Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
-
混合非同步:可以在應用中同時使用同步日誌和非同步日誌,這使得日誌的配置方式更加靈活,如下配置: com.itheima 日誌是非同步的,root 日誌是同步的
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <properties> <property name="LOG_HOME">D:/logs</property> </properties> <Appenders> <File name="file" fileName="${LOG_HOME}/myfile.log"> <PatternLayout> <Pattern>%d %p %c{1.} [%t] %m%n</Pattern> </PatternLayout> </File> </Appenders> <Loggers> <!-- 自定義非同步 logger 對象 --> <!-- includeLocation="false":關閉日誌記錄的行號信息,列印位置信息會急劇降低非同步日誌的性能 --> <AsyncLogger name="com.itheima" level="trace" includeLocation="false" additivity="false"> <AppenderRef ref="file"/> </AsyncLogger> <Root level="info" includeLocation="true"> <AppenderRef ref="file"/> </Root> </Loggers> </Configuration>
4. 無垃圾模式
垃圾收集暫停是延遲峰值的常見原因,並且對於許多系統而言,花費大量精力來控制這些暫停
許多日誌庫(包括以前版本的 Log4j)在穩態日誌記錄期間分配臨時對象,如日誌事件對象、字元串、字元數組、位元組數組等。這會對垃圾收集器造成壓力並增加 GC 暫停發生的頻率
從 2.6 版本開始,預設情況下 Log4j 以無垃圾模式運行,重用對象和緩衝區,儘可能不分配臨時對象
還有一個低垃圾模式,它不是完全無垃圾,但不使用 ThreadLocal 欄位,Log4j 2.6 中的無垃圾日誌記錄部分通過重用 ThreadLocal 欄位中的對象來實現,部分通過在將文本轉換為位元組時重用緩衝區來實現
有兩個單獨的系統屬性可用於手動控制 Log4j 用於避免創建臨時對象的機制:
log4j2.enableThreadlocals
:如果為 true(預設),對象存儲在 ThreadLocal 欄位中並重新使用,否則將為每個日誌事件創建新對象log4j2.enableDirectEncoders
:如果為 true(預設),日誌事件轉換為文本,則將此文本轉換為位元組而不創建臨時對象。註意:由於共用緩衝區上的同步,在此模式下多線程應用程式的同步日誌記錄性能可能更差,如果您的應用程式是多線程的並且日誌記錄性能很重要,請考慮使用非同步記錄器
SpringBoot 日誌使用
Springboot 預設使用 SLF4J 作為日誌門面,logback 作為日誌實現來記錄日誌
1. SpringBoot 的日誌設計
<dependency>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
依賴關係圖:
- Springboot 底層預設使用 logback 作為日誌實現,並提供了其他日誌實現框架的橋接器進行轉換
- Springboot 使用了 SLF4J 作為日誌門面,也可以使用如 Log4j2 等日誌門面,但最終還是通過 slf4j 進行調用 logback
2. SpringBoot 日誌使用
在 springboot 中測試列印日誌
@SpringBootTest
class SpringbootLogApplicationTests {
//記錄器
public static final Logger LOGGER = LoggerFactory.getLogger(SpringbootLogApplicationTests.class);
@Test
public void contextLoads() {
// 列印日誌信息
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("info"); // 預設日誌級別
LOGGER.debug("debug");
LOGGER.trace("trace");
}
}
修改預設日誌配置
logging.level.com.itheima=trace
# 在控制台輸出的日誌的格式 同logback
logging.pattern.console=%d{yyyy-MM-dd} [%thread] [%-5level] %logger{50} - %msg%n
# 指定文件中日誌輸出的格式
logging.file=D:/logs/springboot.log
logging.pattern.file=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n
給類路徑下放上每個日誌框架自己的配置文件,SpringBoot 就不使用預設配置的了
- Logback :logback-spring.xml,logback.xml
- Log4j2:log4j2-spring.xml ,log4j2.xml
- JUL:logging.properties
使用 SpringBoot 可以切換不同環境的日誌配置,如在 logback-spring.xml 添加如下內容:
...
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<springProfile name="dev">
<pattern>${pattern}</pattern>
</springProfile>
<springProfile name="pro">
<pattern>%d{yyyyMMdd:HH:mm:ss.SSS} [%thread] %-5level %msg%n</pattern>
</springProfile>
</encoder>
...
application.properties 配置如下:
spring.profiles.active=dev
如果希望將日誌切換為 log4j2
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- 排除 logback -->
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>