如何在專案中記錄日誌資訊?

Java3y發表於2021-12-09

大家好,我是3y。在正文之前,先給各位股東彙報下austin專案進度:

img

總的來說,我感覺這次的反響是不錯的,雖然閱讀量不高。但留言的人多了很多,也有很多人都擔心我會不會鴿掉(更新一半中途就斷了)

我只能說:別慌,絕對不鴿,你只管追更就好

img

我已經決定每個週末都扛著電腦回家,有空就往附近的圖書館裡跑(圖書館是學習的YYDS,在家的效率就是要比圖書館要低不少)

不多BB了,今天繼續聊個話題:日誌

01、什麼是日誌

所謂日誌,在我理解下就是:記錄程式執行時的資訊

img

在Java最初期又或是我們初學階段,列印日誌全憑System.out.println();

這好用嗎?有待商榷。

對於大部分初學者來說,好用!我想看的資訊,直接在console就能看到了,這是多麼地方便阿。學習Java的第一個執行結果都是由System.out.println();出來的,不需要有任何的學習成本。

對於大部分工作者來說,本地除錯可以,但如果程式部署到伺服器以後,那就算了。

生產環境跟本地環境是有區別的:

  • 生產環境需要記錄的日誌會更多(畢竟是作為一個系統/專案線上上執行,不可能只列印一點點內容)
  • 生產環境的日誌內容需要保留至檔案(作為留存,線上不會說第一時間發現問題,很多需要查詢歷史日誌資料)
  • 生產環境的日誌內容需要有一定的規範格式(至少日誌記錄的時間需要有吧)

上面這些要求,System.out.println();都是不具備的。

所以,我們可以看到在公司裡寫的專案,是沒有用System.out.println();記錄日誌的

img

02、Java日誌體系

工作了以後,你會發現每次引入一個框架,這個框架下幾乎都有對應的日誌包。

我之前在公司裡曾經整合過幾個專案(將原有的幾個工程合併到一個專案內)。

系統分久必合合久必分,當時是認為以前的同事把專案拆得過於細,造成一定的資源浪費(畢竟每個工程跑線上上至少都會部署兩臺線上機器),所以有段時間公司就希望我們把一些細小的專案進行合併。

至於這做得對與錯,這塊我就不談了。

在合併的過程中,最最最麻煩的就是解決依賴衝突的問題(都是Maven專案,會有Maven仲裁的問題),而這裡邊,最明顯的就是Java日誌包的問題。

如果你有那麼一丟丟了解Java日誌,你就應該多多少少聽說過以下的名字:Log4j(log for java)、JUL(Java Util Logging)、JCL(Jakarta Commons Logging)、Slf4j(Simple Logging Facade for Java)、LogbackLog4j2

img

如果你比較細心,你會發現,不同的技術框架所採用的Java日誌實現都很有可能不一樣的。

既然實現不一樣,那對應的API呼叫是不是就不一樣?(畢竟它還不像是JDBC,定義了一套介面規範,各個資料庫廠商去實現JDBC規範,程式設計師面向JDBC介面程式設計就完事了)

那這這這不是亂套呢?想到這裡,血壓就逐漸就上來了?這別慌,上面提到的Java日誌Slf4j(Simple Logging Facade for Java)乾的就類似JDBC做的事情。

它定義了日誌的介面(門面模式),當專案使用別的日誌框架時,那就適配它!(注意:JDBC是定義介面,資料庫廠商實現。Slf4j也定義了介面,但是它適配其他的Java日誌實現,騷不騷?)

我們看Slf4j官網的一張圖,應該就挺好理解了:

img

扯了這麼久,我想表達的是:我們在專案中,最好是使用Slf4j提供的API,至於真實的LOG實現,都可以用Slf4j進行橋接(這樣一來,或許將來有一天說要從log4j改為logback,那程式程式碼也不用改動)

03、日誌有什麼用?

還沒有過生產環境的開發的同學可能認為記錄日誌就是用來定位問題的,其實並不完全是。

日誌一方面我們用它來定位問題,一方面我們很多的資料也是來源於日誌

不要覺得存在資料庫裡的資料才是重要的,我們程式執行時記錄下的日誌資料也同樣重要

在大資料領域裡,資料來源有很多:關係型資料庫、爬蟲、日誌等等

img

舉個例子,我以前的公司就有處理日誌的一套框架:

  1. 我們正常把日誌資訊輸出到檔案下
  2. 框架提供後臺給予我們配置(檔案的路徑以及Kafka Topic Name)

該框架做的事情說白了就是:把我們的日誌檔案內容轉成Kafka訊息(如果使用方需要將哪個日誌檔案的內容轉為MQ訊息,那在平臺上配置下就完事了)

有了Kafka訊息,那配合流式處理平臺(Storm/Spark/Flink)再對日誌進行清洗,是不是就能產生有價值的資料

img

04、Austin 日誌

扯了這麼久的日誌基礎,只是想讓還不瞭解日誌的同學有個認知。

不扯別的了,還是回到我們還在「新建資料夾」階段的austin專案吧。

austin專案的搭建技術框架使用的是SpringBoot,SpringBoot預設的日誌組合是:Slf4j + logback

我在公司接觸到的專案幾乎都是這個組合,所以我就不打算動了,就直接用logback作為austin的日誌實現框架了(要是真有那麼一天要改成別的日誌實現,理論上只要引入對應的橋接包就完事了)。

05、logback日誌初體驗

在無任何配置的前提下,只要我們引入了SpringBoot的包,就能直接使用日誌的功能了。具體效果入下圖

img

SpringBoot是約定大於配置的一個框架

SpringBoot會預設去載入resources下名為**logback.xml **或者 logback-spring.xml的配置檔案( xml 格式也可以改為 groovy 格式)

如果都不存在,那麼 logback 預設地會呼叫BasicConfigurator ,建立一個最小化配置。

最小化配置由一個關聯到根 logger 的ConsoleAppender 組成。輸出用模式為%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 的 PatternLayoutEncoder 進行格式化

img

06、logback配置

從上面可以發現的是,預設的logback配置是不符合我們的要求的(它是列印在console的),我們是希望把日誌記錄在檔案下的。

所以,我們會在resources下新建一個logback配置。常見的配置內容如下:


<?xml version="1.0" encoding="UTF-8"?>

<configuration scan="true" scanPeriod="10 seconds">

    <contextName>austin</contextName>

    <!-- 設定日誌輸出路徑  可以使“${}”來使用變數。TODO 這裡後面是需要讀配置的 -->
    <property name="log.path" value="logs"/>


    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化輸出:%d表示日期,%thread表示執行緒名,%-5level:級別從左顯示5個字元寬度%msg:日誌訊息,%n是換行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <!-- 設定字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>


    <!-- 時間滾動輸出 level為 INFO 日誌 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在記錄的日誌檔案的路徑及檔名 -->
        <file>${log.path}/austin-info.log</file>
        <!--日誌檔案輸出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日誌記錄器的滾動策略,按日期,按大小記錄 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日誌歸檔路徑以及格式 -->
            <fileNamePattern>${log.path}/logs/austin-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>1000MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日誌檔案保留天數-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日誌檔案只記錄info級別的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 時間滾動輸出 level為 ERROR 日誌 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在記錄的日誌檔案的路徑及檔名 -->
        <file>${log.path}/austin-error.log</file>
        <!--日誌檔案輸出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此處設定字符集 -->
        </encoder>
        <!-- 日誌記錄器的滾動策略,按日期,按大小記錄 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/austin-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>1000MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日誌檔案保留天數-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日誌檔案只記錄ERROR級別的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <root level="info">
        <!-- TODO console列印後面可以只針對dev環境的 -->
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="INFO_FILE"/>
        <appender-ref ref="ERROR_FILE"/>
    </root>

</configuration>

img

日誌的配置不會是一成不變的,現在專案剛搭建出來,就怎麼簡單怎麼來。

07、彩蛋

這篇文章釋出的前一個晚上,ZhenDong突然問我現在用的什麼MQ比較多,我隨口一答:Kafka吧,我接觸MQ基本都是Kafka

他說在寫一個好東西,到時候發出來。我這一聽,就肯定感興趣了啊。

img

ZhenDong發的文章連結:https://mp.weixin.qq.com/s/JC51S_bI02npm4CE5NEEow

文章大概就是美團大佬們他們用AOP+動態模板封裝了一套SDK,進而優雅地記錄操作日誌(說人話就是:大佬不想日誌寫在業務程式碼上,難以管理。將寫日誌這個動作抽象出來,用註解來統一記錄日誌)

文章還是很精彩的,我推薦閱讀一遍。

ZhenDong大佬看完文章後,自己實現了一套,已經差不多快要完成了。順便我跟他討論了下使用場景,感覺我的專案也可以用那一套東西(有優雅的打日誌方式,誰不愛呢

我已經預定了,到時候他給我發原始碼,我就學習下實現思路(後面專案也用他提供的SDK來打日誌,有問題就開噴?[狗頭.jpg])。等他忙完寫好文章,我也轉載下跟大家一起學習下。

img

像這種輪子或者說是經驗思路,自己學會了以後,就可以在面試的時候吹了。就說自己對專案系統改造了一把,從原來的破鬼樣(介紹背景) *,變成現在如此優雅(**得到的結果**),並這個過程中穿插自己的實現思路以及遇到的坑(**艱辛的過程**),這種亮點*哪個面試官又不愛呢

08、總結

日誌在一個專案裡,我認為是在一個比較重要的位置上的。我們的資料和定位問題都離不開日誌,有的專案的日誌相當混亂,那維護起來就特別特別麻煩。

其實我完全可以自己寫個logback配置就把這塊給忽略了,但我還是堅持梳理出來,這篇文章按照「專案」的維度從頭梳理了一遍日誌的知識,希望對大家有幫助吧。

專案原始碼Gitee連結:https://gitee.com/austin

專案原始碼GitHub連結:https://github.com/austin

img

關注我的微信公眾號【Java3y】來聊點不一樣的!
如何在專案中記錄日誌資訊?
【對線面試官+從零編寫Java專案】 持續高強度更新中!求star

原創不易!!求三連!!

相關文章