不正經(jīng)人愛寫日志
第零篇簡(jiǎn)單介紹了austin項(xiàng)目做什么用的,這兩個(gè)月我重點(diǎn)會(huì)實(shí)現(xiàn)哪一塊內(nèi)容。
第一篇用Maven+SpringBoot搭好了項(xiàng)目的架子,以及聊了下我對(duì)SpringBoot和Maven的看法。
總的來說,我感覺這次的反響是不錯(cuò)的,雖然閱讀量不高。但留言的人多了很多,也有很多人都擔(dān)心我會(huì)不會(huì)鴿掉(更新一半中途就斷了)
我只能說:別慌,絕對(duì)不鴿,你只管追更就好。

我已經(jīng)決定每個(gè)周末都扛著電腦回家,有空就往附近的圖書館里跑(圖書館是學(xué)習(xí)的YYDS,在家的效率就是要比圖書館要低不少)
不多BB了,今天繼續(xù)聊個(gè)話題:日志
01、什么是日志
所謂日志,在我理解下就是:記錄程序運(yùn)行時(shí)的信息

在Java最初期又或是我們初學(xué)階段,打印日志全憑System.out.println();
這好用嗎?有待商榷。
對(duì)于大部分初學(xué)者來說,好用!我想看的信息,直接在console就能看到了,這是多么地方便阿。學(xué)習(xí)Java的第一個(gè)運(yùn)行結(jié)果都是由System.out.println();出來的,不需要有任何的學(xué)習(xí)成本。
對(duì)于大部分工作者來說,本地調(diào)試可以,但如果程序部署到服務(wù)器以后,那就算了。
生產(chǎn)環(huán)境跟本地環(huán)境是有區(qū)別的:
生產(chǎn)環(huán)境需要記錄的日志會(huì)更多(畢竟是作為一個(gè)系統(tǒng)/項(xiàng)目在線上運(yùn)行,不可能只打印一點(diǎn)點(diǎn)內(nèi)容) 生產(chǎn)環(huán)境的日志內(nèi)容需要保留至文件(作為留存,線上不會(huì)說第一時(shí)間發(fā)現(xiàn)問題,很多需要查找歷史日志數(shù)據(jù)) 生產(chǎn)環(huán)境的日志內(nèi)容需要有一定的規(guī)范格式(至少日志記錄的時(shí)間需要有吧) ...
上面這些要求,System.out.println();都是不具備的。
所以,我們可以看到在公司里寫的項(xiàng)目,是沒有用System.out.println();記錄日志的

02、Java日志體系
工作了以后,你會(huì)發(fā)現(xiàn)每次引入一個(gè)框架,這個(gè)框架下幾乎都有對(duì)應(yīng)的日志包。
我之前在公司里曾經(jīng)整合過幾個(gè)項(xiàng)目(將原有的幾個(gè)工程合并到一個(gè)項(xiàng)目?jī)?nèi))。
系統(tǒng)分久必合合久必分,當(dāng)時(shí)是認(rèn)為以前的同事把項(xiàng)目拆得過于細(xì),造成一定的資源浪費(fèi)(畢竟每個(gè)工程跑在線上至少都會(huì)部署兩臺(tái)線上機(jī)器),所以有段時(shí)間公司就希望我們把一些細(xì)小的項(xiàng)目進(jìn)行合并。
至于這做得對(duì)與錯(cuò),這塊我就不談了。
在合并的過程中,最最最麻煩的就是解決依賴沖突的問題(都是Maven項(xiàng)目,會(huì)有Maven仲裁的問題),而這里邊,最明顯的就是Java日志包的問題。
如果你有那么一丟丟了解Java日志,你就應(yīng)該多多少少聽說過以下的名字:Log4j(log for java)、JUL(Java Util Logging)、JCL(Jakarta Commons Logging)、Slf4j(Simple Logging Facade for Java)、Logback、Log4j2

如果你比較細(xì)心,你會(huì)發(fā)現(xiàn),不同的技術(shù)框架所采用的Java日志實(shí)現(xiàn)都很有可能不一樣的。
既然實(shí)現(xiàn)不一樣,那對(duì)應(yīng)的API調(diào)用是不是就不一樣?(畢竟它還不像是JDBC,定義了一套接口規(guī)范,各個(gè)數(shù)據(jù)庫廠商去實(shí)現(xiàn)JDBC規(guī)范,程序員面向JDBC接口編程就完事了)
那這這這不是亂套呢?想到這里,血壓就逐漸就上來了?這別慌,上面提到的Java日志Slf4j(Simple Logging Facade for Java)干的就類似JDBC做的事情。
它定義了日志的接口(門面模式),當(dāng)項(xiàng)目使用別的日志框架時(shí),那就適配它?。ㄗ⒁猓篔DBC是定義接口,數(shù)據(jù)庫廠商實(shí)現(xiàn)。Slf4j也定義了接口,但是它適配其他的Java日志實(shí)現(xiàn),騷不騷?)
我們看Slf4j官網(wǎng)的一張圖,應(yīng)該就挺好理解了:

扯了這么久,我想表達(dá)的是:我們?cè)陧?xiàng)目中,最好是使用Slf4j提供的API,至于真實(shí)的LOG實(shí)現(xiàn),都可以用Slf4j進(jìn)行橋接(這樣一來,或許將來有一天說要從log4j改為logback,那程序代碼也不用改動(dòng))
03、日志有什么用?
還沒有過生產(chǎn)環(huán)境的開發(fā)的同學(xué)可能認(rèn)為記錄日志就是用來定位問題的,其實(shí)并不完全是。
日志一方面我們用它來定位問題,一方面我們很多的數(shù)據(jù)也是來源于日志
不要覺得存在數(shù)據(jù)庫里的數(shù)據(jù)才是重要的,我們程序運(yùn)行時(shí)記錄下的日志數(shù)據(jù)也同樣重要。
在大數(shù)據(jù)領(lǐng)域里,數(shù)據(jù)來源有很多:關(guān)系型數(shù)據(jù)庫、爬蟲、日志等等

舉個(gè)例子,我以前的公司就有處理日志的一套框架:
我們正常把日志信息輸出到文件下 框架提供后臺(tái)給予我們配置(文件的路徑以及Kafka Topic Name)
該框架做的事情說白了就是:把我們的日志文件內(nèi)容轉(zhuǎn)成Kafka消息(如果使用方需要將哪個(gè)日志文件的內(nèi)容轉(zhuǎn)為MQ消息,那在平臺(tái)上配置下就完事了)
有了Kafka消息,那配合流式處理平臺(tái)(Storm/Spark/Flink)再對(duì)日志進(jìn)行清洗,是不是就能產(chǎn)生有價(jià)值的數(shù)據(jù)

04、Austin 日志
扯了這么久的日志基礎(chǔ),只是想讓還不了解日志的同學(xué)有個(gè)認(rèn)知。
不扯別的了,還是回到我們還在「新建文件夾」階段的austin項(xiàng)目吧。
austin項(xiàng)目的搭建技術(shù)框架使用的是SpringBoot,SpringBoot默認(rèn)的日志組合是:Slf4j + logback
我在公司接觸到的項(xiàng)目幾乎都是這個(gè)組合,所以我就不打算動(dòng)了,就直接用logback作為austin的日志實(shí)現(xiàn)框架了(要是真有那么一天要改成別的日志實(shí)現(xiàn),理論上只要引入對(duì)應(yīng)的橋接包就完事了)。
05、logback日志初體驗(yàn)
在無任何配置的前提下,只要我們引入了SpringBoot的包,就能直接使用日志的功能了。具體效果入下圖

SpringBoot是約定大于配置的一個(gè)框架
SpringBoot會(huì)默認(rèn)去加載resources下名為logback.xml 或者 logback-spring.xml的配置文件( xml 格式也可以改為 groovy 格式)
如果都不存在,那么 logback 默認(rèn)地會(huì)調(diào)用BasicConfigurator ,創(chuàng)建一個(gè)最小化配置。
最小化配置由一個(gè)關(guān)聯(lián)到根 logger 的ConsoleAppender 組成。輸出用模式為%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n?的 PatternLayoutEncoder 進(jìn)行格式化

06、logback配置
從上面可以發(fā)現(xiàn)的是,默認(rèn)的logback配置是不符合我們的要求的(它是打印在console的),我們是希望把日志記錄在文件下的。
所以,我們會(huì)在resources下新建一個(gè)logback配置。常見的配置內(nèi)容如下:
<configuration?scan="true"?scanPeriod="10?seconds">
????<contextName>austincontextName>
????
????<property?name="log.path"?value="logs"/>
????<appender?name="CONSOLE"?class="ch.qos.logback.core.ConsoleAppender">
????????<encoder?class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
????????????
????????????<pattern>%d{yyyy-MM-dd?HH:mm:ss.SSS}?[%thread]?%-5level?%logger{50}?-?%msg%npattern>
????????????
????????????<charset>UTF-8charset>
????????encoder>
????appender>
????
????<appender?name="INFO_FILE"?class="ch.qos.logback.core.rolling.RollingFileAppender">
????????
????????<file>${log.path}/austin-info.logfile>
????????
????????<encoder>
????????????<pattern>%d{yyyy-MM-dd?HH:mm:ss.SSS}?[%thread]?%-5level?%logger{50}?-?%msg%npattern>
????????????<charset>UTF-8charset>
????????encoder>
????????
????????<rollingPolicy?class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
????????????
????????????<fileNamePattern>${log.path}/logs/austin-info-%d{yyyy-MM-dd}.%i.logfileNamePattern>
????????????<timeBasedFileNamingAndTriggeringPolicy?class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
????????????????<maxFileSize>1000MBmaxFileSize>
????????????timeBasedFileNamingAndTriggeringPolicy>
????????????
????????????<maxHistory>15maxHistory>
????????rollingPolicy>
????????
????????<filter?class="ch.qos.logback.classic.filter.LevelFilter">
????????????<level>infolevel>
????????????<onMatch>ACCEPTonMatch>
????????????<onMismatch>DENYonMismatch>
????????filter>
????appender>
????
????<appender?name="ERROR_FILE"?class="ch.qos.logback.core.rolling.RollingFileAppender">
????????
????????<file>${log.path}/austin-error.logfile>
????????
????????<encoder>
????????????<pattern>%d{yyyy-MM-dd?HH:mm:ss.SSS}?[%thread]?%-5level?%logger{50}?-?%msg%npattern>
????????????<charset>UTF-8charset>?
????????encoder>
????????
????????<rollingPolicy?class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
????????????<fileNamePattern>${log.path}/austin-error-%d{yyyy-MM-dd}.%i.logfileNamePattern>
????????????<timeBasedFileNamingAndTriggeringPolicy?class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
????????????????<maxFileSize>1000MBmaxFileSize>
????????????timeBasedFileNamingAndTriggeringPolicy>
????????????
????????????<maxHistory>15maxHistory>
????????rollingPolicy>
????????
????????<filter?class="ch.qos.logback.classic.filter.LevelFilter">
????????????<level>ERRORlevel>
????????????<onMatch>ACCEPTonMatch>
????????????<onMismatch>DENYonMismatch>
????????filter>
????appender>
????<root?level="info">
????????
????????<appender-ref?ref="CONSOLE"/>
????????<appender-ref?ref="INFO_FILE"/>
????????<appender-ref?ref="ERROR_FILE"/>
????root>
configuration>

日志的配置不會(huì)是一成不變的,現(xiàn)在項(xiàng)目剛搭建出來,就怎么簡(jiǎn)單怎么來。
07、彩蛋
這篇文章發(fā)布的前一個(gè)晚上,ZhenDong突然問我現(xiàn)在用的什么MQ比較多,我隨口一答:Kafka吧,我接觸MQ基本都是Kafka
他說在寫一個(gè)好東西,到時(shí)候發(fā)出來。我這一聽,就肯定感興趣了啊。

ZhenDong發(fā)的文章鏈接:https://mp.weixin.qq.com/s/JC51S_bI02npm4CE5NEEow
文章大概就是美團(tuán)大佬們他們用AOP+動(dòng)態(tài)模板封裝了一套SDK,進(jìn)而優(yōu)雅地記錄操作日志(說人話就是:大佬不想日志寫在業(yè)務(wù)代碼上,難以管理。將寫日志這個(gè)動(dòng)作抽象出來,用注解來統(tǒng)一記錄日志)
文章還是很精彩的,我推薦閱讀一遍。
ZhenDong大佬看完文章后,自己實(shí)現(xiàn)了一套,已經(jīng)差不多快要完成了。順便我跟他討論了下使用場(chǎng)景,感覺我的項(xiàng)目也可以用那一套東西(有優(yōu)雅的打日志方式,誰不愛呢)
我已經(jīng)預(yù)定了,到時(shí)候他給我發(fā)源碼,我就學(xué)習(xí)下實(shí)現(xiàn)思路(后面項(xiàng)目也用他提供的SDK來打日志,有問題就開噴??[狗頭.jpg])。等他忙完寫好文章,我也轉(zhuǎn)載下跟大家一起學(xué)習(xí)下。

像這種輪子或者說是經(jīng)驗(yàn)思路,自己學(xué)會(huì)了以后,就可以在面試的時(shí)候吹了。就說自己對(duì)項(xiàng)目系統(tǒng)改造了一把,從原來的破鬼樣(介紹背景),變成現(xiàn)在如此優(yōu)雅(得到的結(jié)果),并這個(gè)過程中穿插自己的實(shí)現(xiàn)思路以及遇到的坑(艱辛的過程),這種亮點(diǎn)哪個(gè)面試官又不愛呢?
08、總結(jié)
日志在一個(gè)項(xiàng)目里,我認(rèn)為是在一個(gè)比較重要的位置上的。我們的數(shù)據(jù)和定位問題都離不開日志,有的項(xiàng)目的日志相當(dāng)混亂,那維護(hù)起來就特別特別麻煩。
其實(shí)我完全可以自己寫個(gè)logback配置就把這塊給忽略了,但我還是堅(jiān)持梳理出來,這篇文章按照「項(xiàng)目」的維度從頭梳理了一遍日志的知識(shí),希望對(duì)大家有幫助吧。
另外,《對(duì)線面試官》公眾號(hào)還在持續(xù)分享面試題,沒關(guān)注的同學(xué)可以關(guān)注一波(近期我想把文字版上傳上去了)

