改造apache的開源日誌專案來實現 分散式日誌收集系統
概述:
在分散式系統中,經常需要採集各個節點的日誌,然後統一分析。
本文提供一種簡單的方案,本文采用開源日誌專案 + 統一資料庫結構的方式,在各個開發環境中,提供統一的配置及呼叫方法,所有的日誌均記錄在日誌伺服器中,可以追蹤查詢任意一個系統節點上任意應用的任意執行緒的執行狀況。
考究現在比較流行的apache的開源日誌專案log4j以及它在其他平臺的衍生產物(log4net log4py等)。其由appender模組向不同目標輸出日誌。
比如log4j 中使用jdbcAppender可以基本實現插入資料庫的功能。log4j可以提供如下資料:
日誌資訊,
日誌級別,
時間,
執行緒名,
檔案路徑,
類路徑,
行號,
方法名
這些資料在單個客戶端模組已經可以很好的定位日誌發生的各種需要資訊。但是對於我們的分散式日誌收集,還缺少 機器的定位。以及應用、執行緒的定位。
比如在同一個機器上跑著兩個相同的應用,在同處同時往日誌系統中記錄,我們在現有的lo4j基礎上是區分不出來的。
可能有人說,用log4j的NDC——NDC也不行,其的標識是字串,而且用NDC的話會加大應用使用的複雜度。
那麼我們如何定位 機器、應用、執行緒 呢?對應三者我們可以呼叫系統函式獲得 hostname、processId(程序號)、threadId(執行緒號)
而在log4j中這三者是無法通過配置獲取到的,如何解決呢?
1。 然後很簡單的想法,我們可以封裝其logger的 debug、info、warn等介面來實現。——這有個很操蛋的地方,在你封裝了之後,log4j記錄日誌時拿到的location information(日誌發起的時間、執行緒等)就變成咱們自己封裝的位置了,所以行不通。
2。接下來我們自然的想到改寫appender,比如我們改寫jdbcAppender的寫拼SQL語句的方法,將我們需要的三個變數給加進去。但是會發現這個問題:log4j寫日誌是由一個任務佇列執行緒來控制的,在這個佇列的位置獲取hostname和processId可能是正確的,但是獲取執行緒ID肯定是錯誤的。。所以也行不通
3。所以很無奈,我們只能修改log4j的原始碼。我們會發現logger類所有的記錄日誌方法,在通過許可校驗後,都會呼叫一個forceLog的函式(貌似叫這個名),裡頭new了一個LogingEvent的物件,我們修改該建構函式,在此處將PID及執行緒ID傳進去,然後繼承jdbcAppender,重寫拼SQL語句的函式,搞定。
同理,在log4net中也按照如此修改即可(架構基本相似)。
這樣我們就獲得了自己的日誌jar包和dll,引用之後,可以按照原來的配置方法,以及完全相容log4j 和log4net的方法記錄日誌。同時提供我們自己私有的appender,可以作為我們分散式日誌收集系統的呼叫入口。
擴充話題:
1。 關於日誌庫規模太大了檢索效率十分低下怎麼處理?
方案1, 可以使用lucene,從日誌庫中提取資訊,專用於檢索。
方案2, 定期清理、備份資料庫。
2。 此方案適用的情景
此方案適用於中等規模(如1000個併發應用以下節點),並且節點間具有穩定、快速的通訊通道(如區域網),對實時性要求、記錄完整型要求不是十分苛刻的系統。
3。 為何不用socket相關的appender
經瞭解,log4j及其各個衍生品居然在tcp應用層協議上不統一,遂不用。
4。 版權許可?
apache許可,適合商用,如果釋出你修改的程式碼,需要做如下事情:
1。釋出時需要給使用者一份apache許可。
2。需要在原始碼檔案裡註明你修改了哪。
3。釋出需要帶有原工程的協議、商標、專利宣告以及其中一切要求衍生軟體/類庫宣告的內容。
4。釋出需要包含一個Notice檔案,裡面包含Apache許可證及你自己要求宣告的內容。(該內容不可與apache協議本身衝突)
相關程式碼及演示:
log4j 配置
log4j.rootLogger=DEBUG, DATABASE
log4j.appender.DATABASE=com.netvideo.log.NvJdbcAppender
log4j.appender.DATABASE.layout=org.apache.log4j.PatternLayout
log4j.appender.DATABASE.URL=jdbc:mysql://192.168.25.156:3306/test?useUnicode=true&characterEncoding=utf-8
log4j.appender.DATABASE.driver=com.mysql.jdbc.Driver
log4j.appender.DATABASE.user=XXX
log4j.appender.DATABASE.password=XXXX
log4j.appender.DATABASE.tableName=log
log4j.appender.DATABASE.consolePrint=true
使用方法(和log4j完全一致):
Logger logger = Logger.getLogger(Test.class);
logger.info("info called");
NvJdbcAppender原始碼
(有個小插曲,jdbcAppender是有漏洞的。。message裡頭無法帶單引號,不然會造成SQL拼寫錯誤,我在此處修正了這個問題)
log4net配置:
使用方法:
log4net.ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
logger.Debug("test");
NetVideoAppender程式碼: