日志概念
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="https://www.cnblogs.com/Yee-Q/archive/2022/11/07/[%-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" >
<!-- 輸出流物件默認 System.out 改為 System.err-->
<target>System.err</target>
<!--日志格式配置-->
<encoder >
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!-- 日志檔案存放目錄 -->
<property name="log_dir" value="d:/logs"></property>
<!-- 日志檔案輸出 appender 物件 -->
<appender name="file" >
<!-- 日志格式配置 -->
<encoder >
<pattern>${pattern}</pattern>
</encoder>
<!-- 日志輸出路徑 -->
<file>${log_dir}/logback.log</file>
</appender>
<!-- 生成 html 格式 appender 物件 -->
<appender name="htmlFile" >
<!-- 日志格式配置 -->
<encoder >
<layout >
<pattern>${pattern}</pattern>
</layout>
</encoder>
<!--日志輸出路徑-->
<file>${log_dir}/logback.html</file>
</appender>
<!-- 日志檔案拆分和歸檔的appender物件-->
<appender name="rollFile" >
<!--日志格式配置-->
<encoder >
<pattern>${pattern}</pattern>
</encoder>
<!--日志輸出路徑-->
<file>${log_dir}/roll_logback.log</file>
<!--指定日志檔案拆分和壓縮規則-->
<rollingPolicy >
<!--通過指定壓縮檔案名稱,來確定分割檔案方式-->
<fileNamePattern>${log_dir}/rolling.%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
<!--檔案拆分大小-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
<!-- 日志級別過濾 -->
<filter >
<!-- 日志級別 -->
<level>error</level>
<!-- 對error級別及以上的日志進行記錄 -->
<onMatch>ACCEPT</onMatch>
<!-- 攔截error級別以下的日志 -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 開啟異步日志記錄,提升性能 -->
<appender name="async" >
<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 >
<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>
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/528064.html
標籤:其他
上一篇:【HDLBits刷題筆記】13 Finite State Machines
下一篇:淺談PHP設計模式的橋接模式
