目錄
一、日志概述
1、為什么要學習日志?
2、日志的用途
(1)系統監控?
(2)數據采集
(3)日志審計
二、日志使用
1、打印日志
(1)在程序中得到日志對象
(2)使用日志對象打印日志
2、日志框架介紹
(1)門面模式(外觀模式)
門面模式定義
(2)門面模式的實現
燈接口代碼:
臥室燈代碼:
走廊燈接口:
燈門面的代碼:
main方法的代碼:
(3)門面模式的優點
(4)SLF4J 框架介紹
不引入日志門面:
引入日志門面:
3、日志格式的說明
4、日志級別
(1)日志級別的分類
(2)日志級別的使用
5、日志配置
(1)配置日志級別
(2)日志持久化
配置日志文件的路徑和文件名:
配置日志文件的保存路徑:
(3)配置日志文件分割
(4)配置日志格式
配置項說明:
三、更簡單的日志輸出
1、添加lombok依賴
2、輸出日志
四、總結
一、日志概述
1、為什么要學習日志?
? ? ? ? 之前在學習JavaSe部分,使用System.out.println打印日志,觀察、發現問題所在,追蹤問題源頭,在學習Spring的階段,也經常需要根據控制臺的日志來分析和定位問題。
? ? ? ? 隨著項目的復雜度提升,我們對日志的打印也有了更高的需求,不僅僅是定位排查問題。
? ? ? ? 比如:需要記錄一些用戶操作記錄(一些審計公司會要求),也可能需要使用日志來記錄用戶的一些喜好,把日志持久化,后續進行數據分析等。但是System.out.print不能很好的滿足我們的需求,我們就需要使用一些專門的日志框架(專業的事情交給專業的人去做)。
2、日志的用途
? ? ? ? 通過前面的學習,我們知道日志主要是為了發現問題、分析問題、定位問題的,但除此之外,日志還有很多用途。
(1)系統監控?
? ? ? ? 監控現在幾乎是一個成熟系統的標配,我們可以通過日志記錄這個系統的運行狀態,每一個方法的響應時間、響應狀態等,對數據進行分析,設置不同的規則,超過閾值時進行報警。比如統計日志中關鍵字的數量,并在關鍵字數量達到一定條件時報警,這也是日志的常見需求之一。
(2)數據采集
? ? ? ? 數據采集是一個比較大的范圍,采集的數據可以作用在很多方面,比如數據統計,推薦排序等。
數據統計:統計頁面的瀏覽量(PV),訪客量(UV),點擊量等等,根據這些數據進行數據分析,優化公司運營策略。
推薦排序:目前推薦排序應用在各個領域,我們經常接觸的各行各業,很多也都涉及推薦排序,比如購物、廣告、新聞等領域。數據采集是推薦排序工作中必須做的一環,系統通過日志記錄用戶的瀏覽歷史,停留時長等,算法人員通過分析這些數據,訓練模型,給用戶做推薦。
? ? ? ? 下圖的數據源,其中一部分就來自于日志記錄的數據。
(3)日志審計
? ? ? ? 隨著互聯網的發展,眾多企業的關鍵業務越來越多的運行于網絡之上。網絡安全越來越受到大家的關注,系統安全也成為項目中的一個重要環節,安全審計也是系統中非常重要的部分。國家的政策法規、行業標準等都明確對日志審計提出了要求。通過系統日志分析,可以判斷一些非法攻擊,非法調用,以及系統處理過程中的安全隱患。
比如,大家平時都在做運營系統,其中運營人員在通過界面處理一些數據的時候,如果沒有清楚的日志操作記錄,一條數據被刪除或者修改,你是無法找到是誰操作的,但是如果做了相應的記錄,該數據被誰刪除或者修改,就會一目了然。
還有一些內部的違規和信息泄露(比如客戶信息被賣掉)現象出現后,如果未記錄留存日志,為事后調查提供依據,則事后很難追查(一些公司查看客戶信息都會被記錄日志,如果頻繁的查詢也會報警)。
二、日志使用
? ? ? ? Spring Boot項目在啟動時,默認就有日志輸出,如下圖:
? ? ? ? 和我們使用System.out.println打印的日志不同,它打印的是如下圖:
? ? ? ? 可以看到,我們通過System.out.println打印的日志,相比于Spring Boot打印的日志,少了很多信息。
? ? ? ? 而Spring Boot內置了日志框架:Slf4j ,我們可以直接在程序中調用 Slf4j 來輸出日志。
1、打印日志
? ? ? ? 打印日志的步驟:
1、在程序中得到日志對象。
2、使用日志對象輸出要打印的內容。
(1)在程序中得到日志對象
? ? ? ? 在程序中獲取日志對象需要使用日志工程 LoggerFactory,如下代碼所示:
private static Logger logger = LoggerFactory.getLogger(LoggerFactory.class);
????????LoggerFactory.getLogger需要傳遞一個參數,標識這個日志的名稱。這樣就可以更清晰的知道是哪個類輸出的日志。當有問題時,可以更方便直觀的定位到問題類。
注意:Logger對象是屬于 org.slf4j 包下的,不要導入錯包。
(2)使用日志對象打印日志
? ? ? ? 日志對象的打印方法有很多種,我們可以先使用 info() 方法來輸出日志,代碼如下:
@RequestMapping("/logger")
@RestController
public class LoggerController {private static Logger logger = LoggerFactory.getLogger(LoggerFactory.class);@RequestMapping("/print")public String print() {logger.info("使用日志對象打印日志");System.out.println("使用sout打印日志");return "success";}
}
? ? ? ? 瀏覽器訪問:127.0.0.1/logger/print ,頁面如下:
? ? ? ? 控制臺輸出的內容如下:
2、日志框架介紹
? ? ? ? SLF4J 不同于其他日志框架,它不是一個真正的日志實現,而是一個抽象層,對日志框架制定的一種規范、標準、接口。所以SLF4J并不能獨立使用,需要和具體的日志框架配合使用。
(1)門面模式(外觀模式)
? ? ? ? SLF4J是門面模式的典型應用(但不僅僅使用了門面模式)。
門面模式定義
? ? ? ? 門面模式(Facade Pattern)又稱為外觀模式,提供了一個統一的接口,用來訪問子系統中的一群接口。其主要特征是定義了一個高層接口,讓子系統更容易使用。
原文:Provide a unified interface to a set ofinterfaces in a subsystem.Facade defines a higher-level interface that makes the subsystem easier to use.
解釋:求一個子系統的外部與其內部的通信必須通過一個統一的對象進行。門面模式提供一個高層次的接口,使得子系統更易于使用。
? ? ? ? 門面模式主要包含2種角色:
外觀角色(Facade):也稱門面角色,系統對外的統一接口。
子系統角色(SubSystem):可以同時有一個或多個 SubSystem。每個 SubSystem 都不是一個單獨的類,而是一個類的集合。SubSystem并不知道Facade的存在,對于SubSystem而言,Facade只是另一個客戶端而已(即Facade對SubSystem透明)
(2)門面模式的實現
? ? ? ? 場景:回家,我們會開各個屋的燈。離開家時,會關閉各個屋的燈。如果家里設置一個總開關,來控制整個屋的燈就會很方便。
? ? ? ? 我們使用門面模式的實現:
燈接口代碼:
public interface Light {void on();void off();
}
臥室燈代碼:
public class BedRoomLight implements Light{@Overridepublic void on() {System.out.println("打開臥室燈");}@Overridepublic void off() {System.out.println("關閉臥室燈");}
}
走廊燈接口:
public class HallLight implements Light{@Overridepublic void on() {System.out.println("打開走廊燈");}@Overridepublic void off() {System.out.println("關閉走廊燈");}
}
燈門面的代碼:
public class LightFacade {private Light bedRoomLight = new BedRoomLight();private Light hallLight = new HallLight();public void lightOn() {bedRoomLight.on();hallLight.on();}public void lightOff() {bedRoomLight.off();hallLight.off();}
}
main方法的代碼:
public class Main {public static void main(String[] args) {LightFacade lightFacade = new LightFacade();lightFacade.lightOn();lightFacade.lightOff();}
}
????????執行結果如下:
(3)門面模式的優點
? ? ? ? 1、減少了系統的相互依賴。降低了客戶端與子系統的耦合,這使得子系統的變化不會影響到調用它的客戶端。
例如,我修改了臥室燈的方法名,客戶端這邊不需要修改代碼,只需要修改燈門面的代碼即可,客戶端和子系統實現了解耦的效果。如圖:
燈門面修改這里即可,main方法的代碼不需要改變。如圖:
(這里報紅了是因為實現了接口,所以方法名是不能變的,這里只是舉個例子)
? ? ? ? 2、提高了靈活性,簡化了客戶端對子系統的使用難度,客戶端無需關心子系統的具體實現方式,而只需要和門面對象交互即可。
? ? ? ? 3、提高了安全性。可以靈活設定訪問權限,不在門面對象中開通方法,就無法訪問。
(4)SLF4J 框架介紹
? ? ? ? SLF4J 就是其他日志框架的門面。SLF4J可以理解為是提供日志服務的統一API接口,并不涉及到具體的日志邏輯實現。
不引入日志門面:
? ? ? ? 常見的日志框架有log4J、logback等。如有一個項目已經使用了log4J,而你依賴的另一個類庫,假設是Apache Active MQ,它依賴于另外一個日志框架logback,那么你就需要把logback也加載進去。如圖:
存在的問題:
1、不同日志框架的API接口陪配置文件不同,如果多個日志框架共存,那么就不得不維護多套配置文件了(這里的配置文件是指用戶自定義的配置文件)。
2、如果更換日志框架,應用程序將不得不修改代碼,并且修改過程中可能會存在一些代碼沖突。
3、如果引入的第三方框架,需要使用了多套配置文件,那就不得不維護多套配置文件了。
引入日志門面:
? ? ? ? 引入門面日志框架之后,應用程序和日志框架(框架的具體實現)之間就有了統一的API接口(門面日志框架實現),此時應用程序只需要維護一套日志文件配置,且當底層實現框架改變時,也不需要更改應用程序的代碼。如圖:
? ? ? ? SLF4J就是這個日志門面。總的來說,SLF4J使你的代碼獨立于任意一個特定的日志API,這是一個對開發API的開發者很好的思想。
3、日志格式的說明
? ? ? ? 打印的日志分別代碼什么呢?如圖:
4、日志級別
? ? ? ? 日志級別代表日志信息對應問題的嚴重性,為了更快的篩選符合目標的日志信息。
? ? ? ? 試想一下,一個有2w員工的公司,如果每一件瑣事、問題都要反映給老板,老板肯定是處理不過來的,所以就有了組織架構,而組織架構會進行分級,會有很多級別的設置,如圖:
? ? ? ? 有了組織架構后,就可以逐級別匯報消息了,例如:組員匯報給組長,組長匯報給研發一組,研發一組匯報給Java研發,等等依次進行匯報。
? ? ? ? 日志級別大概是同樣的道理,有了日志級別之后,就可以過濾想看到的信息了,比如只關注ERROR級別的,就可以根據級別過濾出來ERROR級別的日志信息,節約開發者的信息篩選時間。
(1)日志級別的分類
? ? ? ? 日志的級別從高到低依次分為:FATAL、ERROE、WARN、INFO、DEBUG、TRACE。
FATAL:致命信息,表示需要立即被處理的系統級錯誤。
ERROR:錯誤信息,級別較高的錯誤日志信息,但仍然不影響系統的繼續運行。
WRAN:警告信息,不影響使用,但需要注意的問題。
INFO:普通信息,用于記錄應用程序正常運行時的一些信息,例如系統啟動完成、請求處理完成等。
DEBUG:調試信息,需要調試時候的管家信息打印。
TRACE:追蹤信息,比DEBUG更細粒度的信息事件(除非有特殊用意,否則請使用DEBUG級別替代)。
日志級別的順序:
? ? ? ? 級別越高,收到的消息越少。
(2)日志級別的使用
? ? ? ? 日志級別是開發人員自己設置的。開發人員根據自己的理解來判斷該信息的重要程度。(類似公司管理,通常由領導來判斷什么樣的事情需要匯報,什么樣的事情不需要匯報)
????????Spring Boot默認的日志框架是Logback,Logback沒有FATAL級別的日志,它被映射到ERROR。我們也可以想想,如果出現fatal日志,表示服務已經出現了某種程度的不可用,需要系統管理員緊急介入處理。通常情況下,一個進程生命周期中應該最多只有一次FATAL記錄。
? ? ? ? 針對這些級別,Logger 對象分別提供了對應的方法,來輸出日志。代碼如下:
@RequestMapping("/logger")
@RestController
public class LoggerController {@RequestMapping("/level")public String levelPrint() {logger.trace("=======trace級別的日志==========");logger.debug("=======debug級別的日志==========");logger.info("=======info級別的日志===========");logger.warn("=======warn級別的日志==========");logger.error("=======error級別的日志==========");return "success";}
}
? ? ? ? 瀏覽器輸入:127.0.0.1:8080/logger/level ,頁面如下:
? ? ? ? 控制臺輸出內容如下:
? ? ? ? 觀察打印日志的結果,只打印了info、warn、error,并沒有輸出trace、debug。這與日志級別的配置有關,日志的輸出級別默認最低級是到info級別,所以只會打印大于等于次級別的日志,也就只會打印info、warn、error。
? ? ? ? 上面是日志的使用,日志框架還支持我們更靈活的輸出日志,包括內容、格式等。
5、日志配置
(1)配置日志級別
? ? ? ? 日志級別配置只需要在配置文件中設置 “logging.level” 配置項即可,如下所示:
properties配置:
logging.level.root=debug
yml配置:
logging:level:root: debug
? ? ? ? 重新運行上面代碼,控制臺打印內容:
? ? ? ? 日志多了debug級別的。
(2)日志持久化
? ? ? ? 以上的日志都是輸出在控制臺上的,然而在線上環境中,我們需要把日志保存下來,以便出現問題之后追溯問題。而把日志保存下來,就叫做 持久化。
? ? ? ? 日志持久化有兩種方式:1、配置日志文件名(logging.file.name); 2、配置日志的存儲目錄(logging.file.path)。
配置日志文件的路徑和文件名:
? ? ? ? properties配置:
logging.file.name=logger/springboot.log
? ? ? ? yml配置:
logging:file:name: logger/springboot.log
? ? ? ? name后面可以跟絕對路徑或者相對路徑。運行項目后,日志內容保存在了對應的目錄下,如圖:
????????
配置日志文件的保存路徑:
? ? ? ? properties配置:
logging.file.path=D:/temp
? ? ? ? yml配置:
logging:file:path: D:/temp
? ? ? ? 運行項目,該路徑下會多出一個日志文件,如圖:
其中,logging.file.nam和logging.file.path同時存在的話只會,只會生效一個,一logging.file.name為準。
(3)配置日志文件分割
? ? ? ? 如果我們的日志都放在一個文件中,隨著項目的運行,日志文件會越來越大,需要對日志文件進行分割。日志框架也考慮到了這一點,所以如果不進行配置,就走自動配置,默認日志文件超過10M就進行分割。
? ? ? ? properties配置:
logging.logback.rollingpolicy.file-name-pattern=${LOG_FILE}.%d{yyyy-MM-dd}.%i
logging.logback.rollingpolicy.max-file-size=1KB
? ? ? ? yml配置:
logging:logback:rollingpolicy:max-file-size: 1KBfile-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i
(4)配置日志格式
? ? ? ? 打印日志的格式,也是支持配置的。支持控制臺和日志文件分別設置。
配置項說明:
1、%clr(表達式){顏色}設置輸入日志的顏色,支持也是有:blue、cyan、faint、green、magenta、red、yellow。
2、%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}} 日期和時間--精確到毫秒。
%d{}?期
${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX} 非空表達式,獲取
系統屬性 LOG_DATEFORMAT_PATTERN ,若屬性?LOG_DATEFORMAT_PATTERN 不存在,則使用?-yyyy-MM-dd HH:mm:ss.SSSXXX 格式,系統屬性可以從System.getProperty("LOG_DATEFORMAT_PATTERN") 獲取
3、%5p 顯示日志級別ERROR、WARN、INFO、DEBUG、TRACE。
4、%t 線程名。%c 類的全限定名。%M method。?%L 為行號。%thread線程名稱。%m或者%msg顯示輸出消息。%n換行符。
5、%5若字符長度小于5,則右邊用空格填充。%-5若字符長度小于5,則左邊空格填充。%.15若字符長度超過15,截去多余字符。%15.15若字符長度小于15,則右邊用空格填充。若字符長度超過15,截去多余字符。
更多說明,參考:Chapter 6: Layouts (qos.ch)
Properties配置:
logging.pattern.console='%d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
yml配置:
logging:pattern:console: '%d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'file: '%d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
? ? ? ? 啟動項目,打印的日志格式是這樣的:
????????通常情況下,使用默認的日志打印即可。
????????這里如果要修改顏色,還要去setting->Runner->VM Options添加:Dspring.output.ansi.enabled=ALWAYS,如圖:
? ? ? ? 因為我的是專業版,控制臺輸出的日志默認就有顏色了,這里我就沒有添加這個內容。
三、更簡單的日志輸出
? ? ? ? 每次都使用LoggerFactory.getLogger(xxx.class)很繁瑣,且每個類都要添加一遍,lombok給我們提供了一種更簡單的方式。
第一步:添加lombok框架支持。
第二步:使用@slf4j注解輸出日志。
1、添加lombok依賴
2、輸出日志
? ? ? ? 代碼如下:
@Slf4j
@RequestMapping("/logger2")
@RestController
public class LoggerController2 {@RequestMapping("/log")public String log() {log.info("打印日志");return "success";}
}
? ? ? ? 瀏覽器輸入:127.0.0.1:8080/logger2/log ,瀏覽器頁面如下:
????????
? ? ? ? 控制臺輸入內容如下:
? ? ? ? lombok提供的@Slf4j會幫我們提供一個日志對象log,我們直接使用就可以。如圖:
四、總結
1、日志是程序中的最重要組成部分,使用日志可以快速的發現和定位問題,Spring Boot內容包含了日志框架,默認情況下使用的是info日志級別將日志輸出到控制臺,我們可以通過lombok提供的@Slf4j注解和log對象快速的打印自定義日志。
2、日志包含6個級別,日志級別越高,收到的日志信息就越少,我們可以通過配置日志的保存名稱或者保存目錄來將日志持久化。