你真的会正确使用日志吗?
點(diǎn)擊上方?好好學(xué)java?,選擇?星標(biāo)?公眾號(hào)
重磅資訊、干貨,第一時(shí)間送達(dá)
今日推薦:今天給大家推薦 6 個(gè) Spring Boot 項(xiàng)目,拿來就可以賺錢!
個(gè)人原創(chuàng)100W+訪問量博客:點(diǎn)擊前往,查看更多
來源:frankiegao123
日志在應(yīng)用程序中是非常非常重要的,好的日志信息能有助于我們?cè)诔绦虺霈F(xiàn) BUG 時(shí)能快速進(jìn)行定位,并能找出其中的原因。
但是,很多介紹 AOP 的地方都采用日志來作為介紹,實(shí)際上日志要采用切面的話是極其不科學(xué)的!對(duì)于日志來說,只是在方法開始、結(jié)束、異常時(shí)輸出一些什么,那是絕對(duì)不夠的,這樣的日志對(duì)于日志分析沒有任何意義。如果在方法的開始和結(jié)束整個(gè)日志,那方法中呢?如果方法中沒有日志的話,那就完全失去了日志的意義!如果應(yīng)用出現(xiàn)問題要查找由什么原因造成的,也沒有什么作用。這樣的日志還不如不用!
希望藉以本文能讓應(yīng)用程序的開發(fā)人員能更加重視日志,能在應(yīng)用中輸出有意義的日志。
日志基本格式
日志輸出主要在文件中,應(yīng)包括以下內(nèi)容:
時(shí)間
日志級(jí)別主要使用
調(diào)用鏈標(biāo)識(shí)(可選)
線程名稱
日志記錄器名稱
日志內(nèi)容
異常堆棧(不一定有)
日志時(shí)間
作為日志產(chǎn)生的日期和時(shí)間,這個(gè)數(shù)據(jù)非常重要,一般精確到毫秒。由于一般按天滾動(dòng)日志文件,日期不需要放在這個(gè)時(shí)間中,使用 HH:mm:ss.SSS 格式即可。
日志級(jí)別
日志級(jí)別主要使用 DEBUG、INFO、WARN、ERROR。
###DEBUG
DEUBG 級(jí)別的主要輸出調(diào)試性質(zhì)的內(nèi)容,該級(jí)別日志主要用于在開發(fā)、測試階段輸出。該級(jí)別的日志應(yīng)盡可能地詳盡,便于在開發(fā)、測試階段出現(xiàn)問題或者異常時(shí),對(duì)其進(jìn)行分析。
INFO
INFO 級(jí)別的主要輸出提示性質(zhì)的內(nèi)容,該級(jí)別日志主要用于生產(chǎn)環(huán)境的日志輸出。該級(jí)別或更高級(jí)別的日志不要出現(xiàn)在循環(huán)中,可以在循環(huán)開始或者結(jié)束后輸出循環(huán)的次數(shù),以及一些其他重要的數(shù)據(jù)。
應(yīng)用啟動(dòng)時(shí)所加載的配置參數(shù)值(比如:連接參數(shù)、線程池參數(shù)、超時(shí)時(shí)間等,以及一些與環(huán)境相關(guān)的配置,或者是整個(gè)配置參數(shù))
一些重要的依賴注入對(duì)象的類名
方法(服務(wù)方法)的輸入?yún)?shù)值、返回值,由于一些方法入?yún)⒌闹捣浅6?#xff0c;只在入口處輸出一次就可以了,在服務(wù)方法內(nèi)部或者調(diào)用非服務(wù)方法時(shí)就不需要再輸出了
方法中重要的部分,比如:從數(shù)據(jù)庫中所獲取較為重要的數(shù)據(jù),以及調(diào)用第三方接口的輸入?yún)?shù)值和接口返回值
INFO 級(jí)別日志原則是在生產(chǎn)環(huán)境中,通過 INFO 和更高級(jí)別的日志,可以了解系統(tǒng)的運(yùn)行狀況,以及出現(xiàn)問題或者異常時(shí),能快速地對(duì)問題進(jìn)行定位,還原當(dāng)時(shí)調(diào)用的上下文數(shù)據(jù),能重現(xiàn)問題。
建議在項(xiàng)目完成后,在測試環(huán)境將日志級(jí)別調(diào)成 INFO,然后通過 INFO 級(jí)別的信息看看是否能了解這個(gè)應(yīng)用的運(yùn)用情況,如果出現(xiàn)問題后是否這些日志能否提供有用的排查問題的信息。
WARN
WARN 級(jí)別的主要輸出警告性質(zhì)的內(nèi)容,這些內(nèi)容是可以預(yù)知且是有規(guī)劃的,比如,某個(gè)方法入?yún)榭栈蛘咴搮?shù)的值不滿足運(yùn)行該方法的條件時(shí)。在 WARN 級(jí)別的時(shí)應(yīng)輸出較為詳盡的信息,以便于事后對(duì)日志進(jìn)行分析,不要直接寫成:
不好的日志
log.warn(?``"name?is?null"`?`);除了輸出警告的原因之外,還需要將其他參數(shù)內(nèi)容都輸出,以便于有更多的信息供為日志分析的參考。
推薦的日志
log.warn(?``"[{}]?name?is?null,?ignore?the?method,?arg0:?{},?arg1:?{}"`?`,?methodName?,?arg0?,?arg1?);ERROR
ERROR 級(jí)別主要針對(duì)于一些不可預(yù)知的信息,諸如:錯(cuò)誤、異常等,比如,在 catch 塊中抓獲的網(wǎng)絡(luò)通信、數(shù)據(jù)庫連接等異常,若異常對(duì)系統(tǒng)的整個(gè)流程影響不大,可以使用 WARN 級(jí)別日志輸出。在輸出 ERROR 級(jí)別的日志時(shí),盡量多地輸出方法入?yún)?shù)、方法執(zhí)行過程中產(chǎn)生的對(duì)象等數(shù)據(jù),在帶有錯(cuò)誤、異常對(duì)象的數(shù)據(jù)時(shí),需要將該對(duì)象一并輸出:
推薦的日志
log.error(?"Invoking?com.service.UserService?cause?error,?username:?{}"?,?username?,?e?);不要寫成(下面這種會(huì)將 e 作為日志內(nèi)容參數(shù)中的一個(gè),效果與使用 e.toString() 一致,不會(huì)輸出異常堆棧):
不好的日志
log.error(?``"Invoking?com.service.UserService?cause?error,?username:?{},?e:?{}"`?`,?username?,?e?);不要在日志中輸出下面這樣的日志,在異常堆棧 e 中本身就會(huì)輸出 e.getMessage 的內(nèi)容,沒必要在日志行中輸出一遍,這樣的日志對(duì)于問題的追蹤毫無意義!
不好的日志
log.error(?e.getMessage()?,?e?);調(diào)用鏈標(biāo)識(shí)
在分布式應(yīng)用中,用戶的一個(gè)請(qǐng)求會(huì)調(diào)用若干個(gè)服務(wù)完成,這些服務(wù)可能還是嵌套調(diào)用的,因此完成一個(gè)請(qǐng)求的日志并不在一個(gè)應(yīng)用的日志文件,而是分散在不同服務(wù)器上不同應(yīng)用節(jié)點(diǎn)的日志文件中。該標(biāo)識(shí)是為了串聯(lián)一個(gè)請(qǐng)求在整個(gè)系統(tǒng)中的調(diào)用日志。
調(diào)用鏈標(biāo)識(shí)格式:
唯一字符串(trace ID)
調(diào)用層級(jí)(span ID)
調(diào)用鏈標(biāo)識(shí)作為可選項(xiàng),無該數(shù)據(jù)時(shí)只輸出 [] 即可。
線程名稱
輸出該日志的線程名稱,一般在一個(gè)應(yīng)用中一個(gè)同步請(qǐng)求由同一線程完成,輸出線程名稱可以在各個(gè)請(qǐng)求產(chǎn)生的日志中進(jìn)行分類,便于分清當(dāng)前請(qǐng)求上下文的日志。
日志記錄器名稱
日志記錄器名稱一般使用類名,日志文件中可以輸出簡單的類名即可,看實(shí)際情況是否需要使用包名。主要用于看到日志后到哪個(gè)類中去找這個(gè)日志輸出,便于定位問題所在。
日志內(nèi)容
注意事項(xiàng)
禁用 System.out.println
src/main 的代碼中嚴(yán)禁使用 System.out.println 進(jìn)行輸出,因?yàn)樯a(chǎn)環(huán)境一般不會(huì)將標(biāo)準(zhǔn)輸出和錯(cuò)誤輸出重定向到文件中去,如果代碼中使用該方式輸出日志,可能會(huì)導(dǎo)致該輸出丟失。
變參替換日志拼接
使用 slf4j 的 Logger 進(jìn)行處理,使用其變參功能進(jìn)行日志輸出,不要在日志中進(jìn)行字符串的拼接,比如:
推薦的日志
log.debug(?"Load?No.{}?object,?{}"?,?i?,?object?);不要寫成 log.debug ( "Load No." + i + " object, " + object ); 這是因?yàn)閷⑷罩炯?jí)別調(diào)至 INFO 或以上級(jí)別時(shí),這樣會(huì)增加無畏的字符串拼接。
實(shí)現(xiàn) toString()
需要輸出日志的對(duì)象,應(yīng)在其類中實(shí)現(xiàn)快速的 toString 方法,以便于在日志輸出時(shí)僅輸出這個(gè)對(duì)象類名和 hashCode。該 toString 方法應(yīng)該處理類中所有的字段。toString 方法可以通過 IDE 的自動(dòng)功能 toString 功能生成。toString 方法建議不要通過反射或者一些 toString 工具類生成,也不要直接使用 JSON 序列化工具轉(zhuǎn)為 JSON 字符串,這兩者均使用反射進(jìn)行處理的,僅為了輸出日志較為影響應(yīng)用的性能。
預(yù)防空指針
不要在日志中調(diào)用對(duì)象的方法獲取值,除非確保該對(duì)象肯定不為 null,否則很有可能會(huì)因?yàn)槿罩镜膯栴}而導(dǎo)致應(yīng)用產(chǎn)生空指針異常。
不好的日志
log.debug(?"Load?student(id={}),?name:?{}"?,?id?,?student.getName()?);可以改為(當(dāng) student 為 null 時(shí),這樣也不會(huì)產(chǎn)生空指針異常):
推薦的日志
log.debug(?"Load?student(id={}),?student:?{}"?,?id?,?student?);對(duì)于一些一定需要進(jìn)行拼接字符串,或者需要耗費(fèi)時(shí)間、浪費(fèi)內(nèi)存才能產(chǎn)生的日志內(nèi)容作為日志輸出時(shí),應(yīng)使用 log.isXxxxxEnable() 進(jìn)行判斷后再進(jìn)行拼接處理,比如:
推薦的代碼
if?(?log.isDebugEnable()?)?{``??StringBuilder?builder?=?new?StringBuilder();``??for?(?Student?student?:?students?)?{``????builder.append(?"student:?"?).append(?student?);``??}``??builder.append(?"value:?"?).append(?JSON.toJSONString(object)?);``??log.debug(?"debug?log?example,?detail:?{}"?,?builder?);``}信息安全
切記不要 log 密碼及個(gè)人信息相關(guān)的內(nèi)容!為了便于進(jìn)行問題定位,以下是涉及敏感信息日志輸出時(shí)最為寬松(明文顯示的數(shù)據(jù)只能更少,不能更多)的要求:
| 密碼 | 不輸出 | **** ** | 登錄密碼、支付密碼等各種類型的密碼 |
| 信用卡 CVV2 | 不輸出 | *** | |
| 信用卡有效期 | 不輸出 | **** | |
| 驗(yàn)證碼 | 不輸出 | **** ** | 圖形驗(yàn)證碼、短信驗(yàn)證碼、郵件驗(yàn)證碼等 |
| 密鑰、鹽 | 不輸出 | **** ** | 用于加解密算法的密鑰,消息摘要的鹽,以及數(shù)字簽名及簽名驗(yàn)證算法所使用的公私鑰對(duì)等 |
| 會(huì)話 ID設(shè)備指紋 (ID)指紋 token密文數(shù)據(jù) | 前 5 后 5 | 7SuA8***TtslB | 主要有以下類型:1. 應(yīng)用的會(huì)話標(biāo)識(shí),比如:Web、APP、H5 等用于識(shí)別會(huì)話狀態(tài)信息的標(biāo)識(shí)2. APP 標(biāo)識(shí)設(shè)備的設(shè)備指紋或者設(shè)備 ID3. APP 用于指紋驗(yàn)證的 token4. 密文數(shù)據(jù)指的是加密后的數(shù)據(jù)被掩碼的字符無論多少位都輸出 3 個(gè) * |
| 銀行卡卡號(hào) | 前 6 后 4 | 622666**** **0831 | 銀行卡卡號(hào)最多 19 位數(shù)字 |
| 手機(jī)號(hào) | 前 3 后 4 | 137**** 9574 | 定長 11 位數(shù)字 |
| 身份證號(hào) | 前 1 后 1 | 3**** **X | 定長 18 位 |
| 姓名 | 隱姓 | *世仁 | 將姓氏隱藏 |
| IP 地址 | 前 1 后 1 | 10...27 | 隱藏 IP 地址的第 2、第 3 段 |
| 郵箱地址 | 前 1 后 1 | w**3@gmail.com | 僅對(duì) @ 之前的郵箱名稱進(jìn)行掩碼,掩碼部分不管多少位均輸出 ** * |
| 地址 | 隱號(hào)碼 | 上海市西藏北路 *** 號(hào) ** * 樓 *** 室 |
上述僅列取出部分?jǐn)?shù)據(jù)的顯示要求,其他的顯示原則為通過掩碼后的數(shù)據(jù)無法得知原始數(shù)據(jù)。
實(shí)現(xiàn)了如上掩碼的工具類,參考:https://github.com/frankiegao123/mask-utils
異常堆棧
異常堆棧一般會(huì)出現(xiàn)在 ERROR 或者 WARN 級(jí)別的日志中,異常堆棧含有方法調(diào)用鏈的系統(tǒng),以及異常產(chǎn)生的根源。異常堆棧的日志屬于上一行日志的,在日志收集時(shí)需要將其劃至上一行中。
日志文件
日志文件放置于固定的目錄中,按照一定的模板進(jìn)行命名,推薦的日志文件名稱:
當(dāng)前正在寫入的日志文件名:<應(yīng)用名>[-<功能名>].log
已經(jīng)滾入歷史的日志文件名:<應(yīng)用名>[-<功能名>].log.
日志配置
輸出
根據(jù)不同的環(huán)境配置不同的日志輸出方式:
本地調(diào)試可以將日志輸出到控制臺(tái)上
測試環(huán)境或者生產(chǎn)環(huán)境輸出到文件中,每天產(chǎn)生一個(gè)文件,如果日志量龐大可以每個(gè)小時(shí)產(chǎn)生一個(gè)日志文件
生產(chǎn)環(huán)境中的文件輸出,可以考慮使用異步文件輸出,該種方式日志并不會(huì)馬上刷新到文件中去,會(huì)產(chǎn)生日志延時(shí),在停止應(yīng)用時(shí)可能會(huì)導(dǎo)致一些還在內(nèi)存中的日志未能及時(shí)刷新到文件中去而產(chǎn)生丟失,如果對(duì)于應(yīng)用的要求并不是非常高的話,可暫不考慮異步日志
logback 日志工具可以在日志文件滾動(dòng)后將前一文件進(jìn)行壓縮,以減少磁盤空間占用,若使用 logback 對(duì)于日志量龐大的應(yīng)用建議開啟該功能。
推薦文章今天給大家推薦 6 個(gè) Spring Boot 項(xiàng)目,拿來就可以賺錢!
交公糧了:我經(jīng)常逛的技術(shù)網(wǎng)站
圈子哥推薦一款基于 Spring Boot 開發(fā) OA 開源產(chǎn)品,學(xué)習(xí)/搞外快都是不二選擇!
硬剛一周,3W字總結(jié),一年的經(jīng)驗(yàn)告訴你如何準(zhǔn)備校招!
總結(jié)
以上是生活随笔為你收集整理的你真的会正确使用日志吗?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java仿百度网盘,拿来学习/搞外快,都
- 下一篇: Redis性能问题排查解决手册(值得收藏