在開發(fā)過程中,我們有時(shí)需要將重要的錯(cuò)誤日志通過郵件發(fā)送給相關(guān)的責(zé)任人,這樣能即時(shí)發(fā)現(xiàn)錯(cuò)誤,即時(shí)解決。如使用Log4J,一般會(huì)做如下配置:
log4j.rootLogger = debug,mail
# 發(fā)送日志到指定郵件
log4j.appender.mail=org.apache.log4j.net.SMTPAppender
log4j.appender.mail.Threshold=INFO
log4j.appender.mail.BufferSize=10
log4j.appender.mail.From=from@qq.com
log4j.appender.mail.To=to@163.com
log4j.appender.mail.SMTPHost=smtp.qq.com
#發(fā)送郵件箱的用戶
log4j.appender.mail.SMTPUsername=from@qq.com
#郵箱的授權(quán)碼
log4j.appender.mail.SMTPPassword=
但是我在使用過程中發(fā)現(xiàn)標(biāo)準(zhǔn)的org.apache.log4j.net.SMTPAppender有如下幾個(gè)問題。
- 同步發(fā)送郵件。這樣會(huì)阻塞業(yè)務(wù)正常進(jìn)行(比如等待一個(gè)SQL查詢,需要等待郵件發(fā)送后才顯示結(jié)果,顯然不能忍受)
解決辦法: 使用線程池的方式,將發(fā)送郵件包裝成Runnable任務(wù),送到線程池中執(zhí)行。同步隊(duì)列我選擇的是設(shè)置了固定大小的LinkedBlockingQueue,設(shè)置固定大小是因?yàn)樾枰l(fā)郵件的重要日志不是太多,二是不能因?yàn)猷]件任務(wù)占用了太多的內(nèi)存;選擇LinkedBlockingQueue,是因?yàn)?strong>LinkedBlockingQueue讀寫鎖分離,可以邊添加任務(wù),邊發(fā)送郵件;核心線程數(shù)和最大線程數(shù),可以根據(jù)業(yè)務(wù)量和CPU核數(shù)設(shè)定。
- 緩存大小bufferSize(日志事件的個(gè)數(shù))只是設(shè)置緩存大小,并不能等到緩存滿時(shí)才發(fā)送(其實(shí)是只要有發(fā)生ERROR級(jí)別及以上的的事件時(shí)就將緩存中保存的所有滿足threshold級(jí)別的日志都發(fā)送,在發(fā)送之前緩存滿時(shí)會(huì)從頭開始,新的日志覆蓋舊的)
解決辦法: 去掉默認(rèn)實(shí)現(xiàn)類CyclicBuffer,改成同步隊(duì)列;因?yàn)?strong>CyclicBuffer線程不安全,添加日志和獲取日志并不是同一個(gè)線程,所以采用了線程安全的同步隊(duì)列,而且還需要實(shí)現(xiàn)當(dāng)同步隊(duì)列中日志快滿時(shí)將觸發(fā)發(fā)送郵件;所以需要自定義同步隊(duì)列,加上一個(gè)閥值factor,當(dāng)同步隊(duì)列中的日志個(gè)數(shù)達(dá)到bufferSize*factor時(shí)就發(fā)送郵件,這樣可以預(yù)留一部分空間存放后添加進(jìn)來的日志;同步隊(duì)列我選擇的是 LinkedBlockingQueue,可以邊添加日志,邊讀取日志,吞吐量比較大。目前發(fā)送郵件觸發(fā)的條件是:發(fā)生了 ERROR 或 ERROR以上級(jí)別 的錯(cuò)誤時(shí)發(fā)送郵件,改成當(dāng)緩存同步隊(duì)列中元素個(gè)數(shù)大于或等于 bufferSize*factor 時(shí),觸發(fā)回調(diào)函數(shù),啟動(dòng)發(fā)送郵件任務(wù)。
改造步驟:
1. 定義回調(diào)接口AlertWillBeFull
2. 自定義同步隊(duì)列 AlertLinkedBlockingQueue ,繼承 LinkedBlockingQueue ,添加成員變量factor及回調(diào)接口,在所有添加動(dòng)作之前進(jìn)行判斷是否達(dá)到閥值。
3. 去掉SMTPAppender類中的實(shí)現(xiàn)類DefaultEvaluator及所有調(diào)用它的地方
4. 在創(chuàng)建緩存同步隊(duì)列時(shí),傳入回調(diào)對象,等待同步隊(duì)列調(diào)用
5. 添加日志到緩存同步隊(duì)列和從緩存同步隊(duì)列讀取日志分別使用offer和poll方法,不阻塞線程也不拋異常,以免影響實(shí)際業(yè)務(wù)進(jìn)行,而且少少量日志影響也不大。
- 發(fā)送的日志比較雜亂,需要排除某些包下的日志(比如有些不重要的日志,或者只想看某些包下的日志
解決方法:添加成員變量excludePackages和includePackages,修改checkEntryConditions方法邏輯
這樣就能在log4j.properties配置文件中配置緩存大小,以及添加排除或只關(guān)心的記錄日志的包,也可以添加多個(gè)發(fā)送郵件的配置,將不同包下的日志發(fā)送給不同的責(zé)任人。
最終配置如下:
log4j.rootLogger = debug,mail
# 發(fā)送日志到指定郵件
log4j.appender.mail=org.apache.log4j.net.SMTPAppender
#排除的包(多個(gè)包,以英文逗號(hào)隔開)
#log4j.appender.mail.excludePackage=com.alibaba.druid
#僅關(guān)心的包,一般excludePackage與includePackage任選一即可,多個(gè)包以英文逗號(hào)隔開
log4j.appender.mail.includePackage=cn.yang.practise.service,cn.yang.practise.controller,com.alibaba.druid
log4j.appender.mail.Threshold=INFO
log4j.appender.mail.BufferSize=16
log4j.appender.mail.From=from@qq.com
log4j.appender.mail.To=to@163.com
log4j.appender.mail.SMTPHost=smtp.qq.com
#發(fā)送郵件箱的用戶
log4j.appender.mail.SMTPUsername=from@qq.com
#郵箱的授權(quán)碼
log4j.appender.mail.SMTPPassword=
改造后完整后的org.apache.log4j.net.SMTPAppender,AlertLinkedBlockingQueue