寫在前面
Hello大家好,今日是2024年的第一天,祝大家元旦快樂?? 2024第一篇文章從SpringBoot日志開始
一、前言
在我們?nèi)粘5墓鹃_發(fā)中,難免都會存在著大大小小的BUG,不可能會有公司說我們的項(xiàng)目做出來是沒有BUG的,那既然或多或少會BUG的話,要如何去發(fā)現(xiàn)BUG呢?
- 那對于我們程序員來說,首先會想到的就是DeBug調(diào)試,有關(guān)IDEA的一些調(diào)試技巧,讀者可以看看和這個視頻 鏈接
- 但是除了調(diào)試之外,其實(shí)我們還有其他的方法,既然我們在學(xué)習(xí)SpringBoot的話,那就要知道它里面其實(shí)有個東西叫做 日志文件,對于【日志】來說相信對大部分的開發(fā)者來說也是不陌生的,因?yàn)槲覀內(nèi)粘5拈_發(fā)中如果遇到一些問題的話,其實(shí)第一時間就會去查找日志,因?yàn)橛行╁e誤僅僅通過調(diào)試是排查不出來的,甚至是專業(yè)的排查工具也做不到,但是【日志】卻記錄了很多程序運(yùn)行過程中的種種細(xì)節(jié)
?? 接下去就讓我們來看看日志到底是個什么東西?
二、日志有什么用?
-
記錄錯誤日志和警告日志(發(fā)現(xiàn)和定位問題):
- 在交了錢之后卻沒有這位同學(xué)的記錄了,后期只能靠錯誤日志去排查
-
記錄用戶的登錄日志:
- 通過記錄同學(xué)們的登錄時間,教務(wù)系統(tǒng)更新的時間
- 如果發(fā)現(xiàn)有惡意的用戶,就將其IP加入到黑名單中
-
記錄系統(tǒng)的操作日志:
- 比方說在教務(wù)系統(tǒng)不小心將一個同學(xué)的班級轉(zhuǎn)錯了,那就需要操作日志來進(jìn)行數(shù)據(jù)恢復(fù)
- 還可以通過這個操作日志才定位到操作人,是誰誤操作了,防止推卸責(zé)任
-
程序執(zhí)行日志:
三、日志怎么用?
Spring Boot 項(xiàng)?在啟動的時候默認(rèn)就有?志輸出,如下圖所示:
?? 那有同學(xué)就會說,既然都有日志了,為什么還要再去學(xué)習(xí)呢?
此時我便提出了以下幾個問題
- 既然SpringBoot已經(jīng)內(nèi)置了日志,那就要去明白它內(nèi)置的日志究竟是什么?
- 默認(rèn)情況下,輸出的?志并?是開發(fā)者定義和打印的,那開發(fā)者怎么在程序中?定義打印?志呢?
- ?志默認(rèn)是打印在控制臺上的,?控制臺的?志是不能被保存的,那么怎么把?志永久的保存下來呢?
- 既然它已經(jīng)內(nèi)置了日志框架了,那這個框架應(yīng)該怎么用?
下?我們?起來找尋這些問題的答案??
四、自定義日志打印
有的同學(xué)認(rèn)為我們?nèi)粘T趯懘a時使用
System.out.println()
輸出的內(nèi)容就是日志,那我們可以用這個方法來打印看看
@RequestMapping("/sayhi")
public String sayHi(){
System.out.println("打印日志");
return "hello world ->" + myconfig;
}
- 可以看到,只是在這個方法調(diào)用之后打印了對應(yīng)的語句,但是這卻不是我們想要看到的相關(guān)程序信息
?? 那么接下來我來介紹如何通過得到日志對象來打印日志
?? 常見日志框架說明
在這之前呢,我們要先來聊聊前面所提到的【日志框架】,首先你要知道的一點(diǎn)是:SpringBoot是內(nèi)置了日志框架的
- 我們可以來講講用戶獲取日志的調(diào)用流程:
- 用戶在去寫日志的時候不是直接具體去操作某個框架的,而是會先來到一個 日志門面 叫做
Slf4j
- 然后
Slf4j
再根據(jù)系統(tǒng)的配置決定要調(diào)用的具體框架是什么
- 用戶在去寫日志的時候不是直接具體去操作某個框架的,而是會先來到一個 日志門面 叫做
??這個Slf4j
呢就類似于 房產(chǎn)中介??,我們在買房的時候不可能一家家地去找,而是通過房產(chǎn)中介來進(jìn)行對接以此可以看到不同的戶型
- 我們在最早的時候使用這個
log4j 1
是比較多的,后面就慢慢升級成log4j 2
了
? 注意:在去年log4j
被報(bào)出了漏洞,而且漏洞很嚴(yán)重,它可以通過這個漏洞直接把你的應(yīng)用服務(wù)給關(guān)掉。那這個其實(shí)對許多公司來說是很大的一個問題,這里的話就不細(xì)講了,有興趣的同學(xué)可以去了解一下 鏈接
-
所以到后面我們都換成了
logback
這個日志框架了,它就有人維護(hù)比較穩(wěn)定一些,不過它的寫法就不太一樣了,類名、方法名都不一樣了,所以我們直接去對接這個框架的話成本是非常高的,但如果此時我們有了Slf4j
這個門面就不一樣了?? -
就和我們之前在講解 JDBC連接數(shù)據(jù)庫 的時候一樣,為什么要有JDBC呢?就是因?yàn)椴煌臄?shù)據(jù)庫有不同的驅(qū)動,如果當(dāng)一個項(xiàng)目突然要換數(shù)據(jù)庫的話(MySQL —> Oracle),就會很麻煩了。所以我們才有了JDBC這個東西,程序員直接去操作JDBC就可以了,它就是一套規(guī)范,就是一個【門面】,程序員們通過JDBC去適配不同的數(shù)據(jù)庫,就可以做到很好地
解耦
了
?? 我們也可以去驗(yàn)證一下SpringBoot是否真的內(nèi)置了這兩個框架,那可以觀察到確實(shí)是有的
4.1 在程序中得到?志對象【Logger】
在大致了解了SpringBoot中內(nèi)置的框架后,我們就可以去試試看要如何獲取到當(dāng)前程序的日志
?不過對于個日志對象的定義可以非常有講究的
- 首先要明白我們這個日志對象是:
- 方法級別(只能在某一個方法中去使用)
- 類級別(可以在整個類的任何地方都能使用)
?? 那可以很清楚地知道,我們和這個日志對象一定是類級別的,因?yàn)橐谡麄€類的任何方法都能獲取到
- 那還要看的是這個日志對象是
共有的
還是私有的
,那可以很明確,每個類都有自己的日志對象,所以它肯定是私有的,所以要用private
來進(jìn)行修飾 - 并且這個還得是 靜態(tài)的,讓其可以在方法中直接調(diào)用,那么就要用
static
來進(jìn)行修飾 - 還有一點(diǎn):你期望這個日志對象在某個方法中被修改嗎?當(dāng)然不想!所以我們還要再加上
final
關(guān)鍵字作為修飾
那么再加上一步步的修飾后,我們就要通過
Slf4j
所提供的接口去新建出一個日志對象了,注意在這里我們要選擇org.slf4j
這個包下的,不要導(dǎo)?錯包!
- 那我們知道,對于一個接口來說是我們在獲取?志對象還需要使??志?? LoggerFactory,然后使用到里面的
getLogger
這個方法,這里一共有兩種重寫方法,我們先選擇Class<?> clazz
以下是它的源碼,我們后面通過查看打印出來日志的形式來進(jìn)行觀察:
public static Logger getLogger(Class<?> clazz) {
Logger logger = getLogger(clazz.getName());
if (DETECT_LOGGER_NAME_MISMATCH) {
Class<?> autoComputedCallingClass = Util.getCallingClass();
if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(), autoComputedCallingClass.getName()));
Util.report("See https://www.slf4j.org/codes.html#loggerNameMismatch for an explanation");
}
}
return logger;
}
4.2 使??志對象打印?志
- 接下去我們就將這個日志對象給打印出來吧
// 打印日志
logger.trace("test2 trace");
logger.debug("test2 debug");
logger.info("test2 info");
logger.warn("test2 warn");
logger.error("test2 error");
- 通過運(yùn)行并訪問路由,我們可以發(fā)現(xiàn)當(dāng)前程序的日志信息被打印出來了,但是呢卻不是從
trace
開始打印的,而是從info
開始打印的,這個的話就要追溯到 日志等級的優(yōu)先級 和 默認(rèn)級別 了,這個我們在下一模塊會展開詳細(xì)講解
4.3 ?志格式解讀
看到了打印出來的日志信息后,相信有很多讀者并不是很了解每一條日志信息到底想告訴我們什么,現(xiàn)在就讓我們來分析一下這些日志信息吧
- 這里我們主要來關(guān)注兩塊地方:
- 第一個是這邊的包名,你是否覺得
c.e.controller
這個名稱有點(diǎn)熟悉呢?沒錯,它就是我們在這個包下創(chuàng)建類的時候自動導(dǎo)入的包名,前面兩個c
和e
即為縮寫
package com.example.demo.controller;
- 第二塊的話就是后面的類名
Test2Controller
了,這個的話就要去回憶我們上面為 工廠類LoggerFactory 中的getLogger
這個方法所傳入的當(dāng)前類類名,所以SpringBoot靠著這些很好地識別到并打印出了相應(yīng)的日志信息
- 第一個是這邊的包名,你是否覺得
還記得我們在上面所談到的
getLogger
這個方法的第二種形式嗎,我們一起再來看看
- 此時我們再創(chuàng)建一個類,然后采取不同的
getLogger
方法
- 以下是具體的測試代碼
@RestController // = @Controller + @ResponseBody
public class StudentController {
private static final Logger logger = LoggerFactory.getLogger("StudentController");
@RequestMapping("/stu/sayhi")
public String sayhi(){
logger.info("student info");
logger.error("student error");
return "student sayhi";
}
}
- 然后我們來觀察一下結(jié)果并與之前的方法進(jìn)行一個對比就可以發(fā)現(xiàn)所打印出來的日志信息有著很大的不同,對于傳入
name
來說所并不會展現(xiàn)出完整的包名路徑,而是只有一個類名,所以想看到怎么樣的日志信息讀者可以自己來進(jìn)行控制
以下是它的源碼,供閱讀參考
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
五、日志級別
整體地介紹完日志消息后,我們再來詳細(xì)地介紹一下其中的一個東西叫做【日志級別】
5.1 日志級別的作用
- ?志級別可以幫你篩選出重要的信息,?如設(shè)置?志級別為
error
,那么就可以只看程序的報(bào)錯?志了,對于普通的調(diào)試?志和業(yè)務(wù)?志就可以忽略了,從?節(jié)省開發(fā)者信息篩選的時間。 - ?志級別可以控制不同環(huán)境下,?個程序是否需要打印?志,如【開發(fā)環(huán)境】我們需要很詳細(xì)的信息,?【?產(chǎn)環(huán)境】為了保證性能和安全性就會輸?盡量少的?志,?通過?志的級別就可以實(shí)現(xiàn)此需求。
5.3 日志級別分類和使用
-
trace
:微量,少許的意思,級別最低; -
debug
:需要調(diào)試時候的關(guān)鍵信息打??; -
info
:普通的打印信息(默認(rèn)?志級別); -
warn
:警告,不影響使?,但需要注意的問題; -
error
:錯誤信息,級別較?的錯誤?志信息; -
fatal
:致命的,因?yàn)榇a異常導(dǎo)致程序退出執(zhí)?的事件。
?志級別的順序:
?? 級別越高所能接收到的消息就越少了,比如說error
只能接收到error
和fatal
級別的日志
清楚各個日志級別后,我們就來配置文件中實(shí)際地來操作一下吧??
- ?志級別配置只需要在配置?件中設(shè)置
“l(fā)ogging.level”
配置項(xiàng)即可,如下所示:
# 日志級別設(shè)置
logging:
level:
root: error
- 然后我們通過去訪問不同的路由地址就可以看到,即使我們選擇打印不同的日志級別,但是打印出來的內(nèi)容只有
error
- 我們還可以做進(jìn)一步更加嚴(yán)格的設(shè)置,只打印我們當(dāng)前項(xiàng)目中
controller
層中的所觸發(fā)的日志信息
# 日志級別設(shè)置
logging:
level:
root: error
com:
example:
demo:
controller: trace
先把項(xiàng)目重啟一下我們可以看到,任何多余的日志都沒有看到,就等待我們?nèi)ピL問
接著去訪問一下就可以看到打印出來了相關(guān)的日志信息,而且很干凈,并沒有任何雜志
?? 所以在學(xué)習(xí)了日志級別后我們就可以清除掉配置?件中的原先?志設(shè)置,從而隨心地去控制日志的打印內(nèi)容
六、日志持久化【將日志保存下來】
以上的?志都是直接輸出在控制臺上的,然?在?產(chǎn)環(huán)境上咱們需要將?志保存下來,以便出現(xiàn)問題之后追溯問題,把?志保存下來的過程就叫做【持久化】
想要將?志進(jìn)?持久化,只需要在配置?件中 指定?志的存儲?錄 或者是 指定?志保存?件名 之后,Spring Boot 就會將控制臺的?志寫到相應(yīng)的?錄或?件下了
6.1 設(shè)置日志的保存路徑
- 首先我們來看看如何設(shè)置日志的保存路徑,這里在對于路徑的設(shè)置要注意以下兩點(diǎn):
- 盡量不要將保存的路徑寫在系統(tǒng)盤
- 路徑中不要出現(xiàn)中文和空格
- 比方說我現(xiàn)在將日志文件的輸出路徑放到了D盤下的
home
文件夾下
# 日志保存路徑
logging:
file:
path: D:\\home
- 然后我們將項(xiàng)目重啟并訪問相關(guān)路由就可以看到,名為
spring.log
的文件就出現(xiàn)了,點(diǎn)進(jìn)去一看確實(shí)出現(xiàn)了我們上面在控制臺中所出現(xiàn)的日志信息
我們來看看幾位同學(xué)的問題(???(???(???*)
?? 那有同學(xué)就說,那在項(xiàng)目重啟之后會不會丟失呢?
- 答案是:不會的。日志文件一旦產(chǎn)生,那么日志文件及其內(nèi)容就會永久得保存,不會出現(xiàn)文件或內(nèi)容的丟失,無論任何操作都會保持以上的特性
?? 那在重啟項(xiàng)目后再去運(yùn)行,存儲的日志會不會將之前的日志覆蓋掉呢?
-
答案是:不會的。對于日志的記錄是一個
append
追加的過程,而不會產(chǎn)生一個覆蓋的現(xiàn)象
我們可以將項(xiàng)目重啟后再來訪問一下看看,便可以觀察到上一次的日志信息確實(shí)還存留,并且追加上了這一次的日志信息??
?? 如果一直像上面那樣追加,如果文件變得越來越大怎么辦呢?
- 這個不需要擔(dān)心,有最大文件大小限制,如果超過了的話就會重新去創(chuàng)建一個,可以看 Spring的官方文檔
6.2 配置?志?件的?件名
那么除了可以設(shè)置日志的保存路徑外呢,我們還可以去配置?志?件的?件名,可以不用系統(tǒng)默認(rèn)的,自己也可以起名哦~
- 比方說我在這里將名字取為
springboot.log
,不過要記得取名字的同時也要帶上路徑哦,否則就就無法輸出到指定的路徑了,就會直接保存在當(dāng)前項(xiàng)目中
# 日志保存名稱
logging:
file:
name: D:\\home\\springboot.log
來運(yùn)行一下看看,確實(shí)可以看到出現(xiàn)了一個名為springboot.log
的文件
?? 那這個時候又有同學(xué)問了:什么東西都存在文件里,萬一系統(tǒng)被入侵了文件不是很容易丟失嗎?
- 這個問題問得很好?? 在公司中做大項(xiàng)目的時候,我們并不是所有的日志信息都放在文件中,對于有些文件我們則是選擇存放在【數(shù)據(jù)庫】 中
對于生產(chǎn)級別日志分類 —— 根據(jù)業(yè)務(wù)場景來定
- 程序運(yùn)行日志(存放在文件中)
- 屬于邊緣性的東西,簡單看一下
- 業(yè)務(wù)日志(存放到數(shù)據(jù)庫中)
- 記錄系統(tǒng)關(guān)鍵操作的關(guān)鍵人,以及其修改前后的操作
?? 【綜合練習(xí)】:將 controller 包下 error 級別以上的?志保存到 log_all.log 下,將 service 下warn 級別以上的?志保存到 log_all.log 下
七、更簡單的?志輸出 — lombok
每次都使? LoggerFactory.getLogger(xxx.class) 很繁瑣,且每個類都添加?遍,也很麻煩,這?講?種更好?的?志輸出?式,使? lombok 來更簡單的輸出。
7.1 添加 lombok 依賴
- 首先我們要在IDEA中去安裝一個插件,叫做
EditStarters
- 接下去就要在
pom.xml
依賴文件中通過這個插件去生成相關(guān)的lombok依賴
然后在文件中就會多出來以下依賴了
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
7.2 使用 @slf4j 輸出日志
- 有了依賴的支持,接下去我們就可以通過
@slf4j
這個注解去代替前面繁瑣的日志對象創(chuàng)建過程了,這個注解會給當(dāng)前的類提供一個log
對象,使用這個對象就可以去調(diào)用對應(yīng)日志級別了
@RequestMapping("/log/sayhi")
public String sayhi(){
log.info("info log");
log.warn("warn log");
log.error("error log");
return "log sayhi";
}
然后我們來運(yùn)行一下試試,便可以觀察到與前面獲取到的日志信息是一樣的~
7.3 lombok 原理解釋
但是光這樣子是不夠的,我們要做到的是 知其然,知其所以然。對于
lombok
來說,它呢是編譯期間的一個框架
- 通過我們之前的學(xué)習(xí)可以知道,對于一個普通的Java程序來說,當(dāng)用戶寫好代碼之后進(jìn)行編譯,然后生成一個
.class
為后綴的字節(jié)碼文件,將其放到JVM虛擬機(jī)上去運(yùn)行就可以讓程序跑起來了
- 但是對于
lombok
框架支持后,它就會與普通的java
程序一同進(jìn)行編譯,然后生成.class
為后綴的字節(jié)碼文件
那怎么去證實(shí)呢?我們一起來看看
- 首先我們在啟動項(xiàng)目后到
target
文件中去進(jìn)行查看,然后打開內(nèi)部的controller
文件
- 在其內(nèi)部我們看到了編譯好的
.class
為后綴的字節(jié)碼文件,將其拖入到IDEA中觀察一下??
- 將編譯前后的文件進(jìn)行對比我們可以發(fā)現(xiàn),因?yàn)?code>@Slf4j注解的緣故,出現(xiàn)了我們在前面所學(xué)習(xí)的
Logger
接口所新建出來的對象,不過這里叫做log
- 那其實(shí)我們就看得很明確了,其實(shí) lombok 的這個注解所做的工作其實(shí)就是在做生成日志對象的轉(zhuǎn)換工作,這就是為何說 Spring面向注解開發(fā) 的原因了
7.4 lombok 更多注解說明
當(dāng)然,lombok 這個框架可不知那么一個注解,它的功能還是很強(qiáng)大的,其中的很多注解都可以幫助我們快速地去進(jìn)行開發(fā)
- 基本注解:
注解 | 作用 |
---|---|
@Getter | ?動添加 getter ?法 |
@Setter | ?動添加 setter ?法 |
@ToString | ?動添加 toString?法 |
@EqualsAndHashCode | ?動添加 equals 和 hashCode ?法 |
@NoArgsConstructor | ?動添加?參構(gòu)造?法 |
@AllArgsConstructor | ?動添加全屬性構(gòu)造?法,順序按照屬性的定義順序 |
@NonNull | 屬性不能為 null |
@RequiredArgsConstructor | ?動添加必需屬性的構(gòu)造?法,final + @NonNull 的屬性為必需 |
- 組合注解:
注解 | 作用 |
---|---|
@Data | @Getter + @Setter + @ToString +@EqualsAndHashCode +@RequiredArgsConstructor +@NoArgsConstructor |
- ?志注解
注解 | 作用 |
---|---|
@Slf4j | 添加?個名為 log 的?志,使? slf4j |
八、總結(jié)與提煉
接下去來總結(jié)一下本文所學(xué)習(xí)的內(nèi)容??
-
首先我們簡單地來聊了聊日志是什么:?志是程序中的重要組成部分,使??志可以快速的發(fā)現(xiàn)和定位問題,SpringBoot 內(nèi)容了?志框架:SLF4J 與 logback 是我們最常用的兩個,其二者通過 SLF4J 這個日志門面可以實(shí)現(xiàn)很好的解耦,方便后期的替換維護(hù),與我們之前所講的
JDBC
有著異曲同工之妙 -
然后我們又講到了如何去獲取日志對象:在這一塊首先我們講到了使用一個 工廠類LoggerFactory 去實(shí)現(xiàn)
Logger接口
,然后通過調(diào)用其中的方法并傳入對應(yīng)的參數(shù)來獲取到對應(yīng)的日志對象,接著再通過這個對象去調(diào)用【日志級別】來進(jìn)行輸出 -
然后我們便詳細(xì)地說到了【日志級別】:其包含 6 個級別:
-
trace
:微量,少許的意思,級別最低; -
debug
:需要調(diào)試時候的關(guān)鍵信息打??; -
info
:普通的打印信息(?默認(rèn)?志級別?); -
warn
:警告,不影響使?,但需要注意的問題; -
error
:錯誤信息,級別較?的錯誤?志信息; -
fatal
:致命的,因?yàn)榇a異常導(dǎo)致程序退出執(zhí)?的事件。
-
- 然后呢我們又學(xué)了什么是日志持久化: 通過去設(shè)置 日志的保存路徑 以及 ?志?件的?件名 便可以控制我們所觀察到的日志信息
-
最后呢我們又學(xué)了一個很棒的框架: 它叫做 lombok,通過 lombok 提供的
@Slf4j
注解和log
對象我們可以實(shí)現(xiàn)快速的打印?定義?志,當(dāng)然我們還去好好地探究了一番這個注解究竟怎么一回事,做到了 “知其然,知其所以然”
以上就是本文要介紹的所有內(nèi)容,誠摯感謝您對本文的觀看??????文章來源:http://www.zghlxwxcb.cn/news/detail-773599.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-773599.html
到了這里,關(guān)于Spring Boot日志:從Logger到@Slf4j的探秘的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!