本文探討在大規模日志數據收集過程中,針對日志文件的處理需要注意的技術細節。
1. 通配符和目錄遞歸搜索
大多數場景下,日志往往被分散在不同的目錄中,比如以日期為名的目錄。因此,工具必需支持對目錄的遞歸搜索和某種模式匹配。
POSIX
標準定義了一組用于通配的特殊符號(Pattern Matching Notation):
- *:匹配一個或多個字符
- ?:匹配一個字符
- []:匹配一個在范圍內的字符
- [!]:匹配一個不在范圍內的字符
在shell
中很多常見命令都可以應用這種模式匹配規則,比如:
find / -name "*.so"
作為日志收集工具,也需要能夠做到這種匹配,從而對日志進行初步篩選。在Unix系統中,可以使用fnmatch函數實現。
2. 熱日志分析
日志的其中一個特點是:往往同時只有少數的日志文件正在寫入,大部分日志文件都不是正在寫入狀態。那么在收集日志的時候,為了降低資源的消耗,需要有一種機制來判斷哪些是“熱更新”
的日志,只對熱更新日志進行讀取。我們采用的方式是,如果在若干次采樣周期內,發現讀取文件都是EOF
,那么認為這個文件屬于冷日志,并將其剔除出熱日志隊列,并加入一個新的日志文件作為熱日志文件,循環往復。這樣,大多數情況下,即保證了實時性,又降低了資源的無效損耗。
3. 新文件檢測
對于采集一個目錄下的文件
這種需求,必需要考慮到新文件增加的情況。通常設置一個間隔時間(比如2s
),對目錄進行一個遍歷,然后對比當前已經被納管的文件,看是不是新文件。如果是新文件,應立刻加入到熱文件隊列
,因為新的文件往往會立刻被寫入數據。這里采用hashmap
能提高對比性能。
4. 采集點保存
程序總會因為某種原因退出,但是采集任務往往并沒有結束,這個時候,程序就需要有能力記錄下一些信息,以便下一次繼續從結束的點開始工作,以防止重復采集。針對每個文件,記錄當前讀取到的offset
,并在程序退出時,及時刷寫進磁盤。
5. log rotate的探測
log rotate
是常用的一種日志策略。當達到rotate
的條件時,當前正在寫入的文件會重命名,并且不再寫入數據;然后創建一個新的文件來繼續寫入。當文件數量超過一定量時,將最早產生的文件刪除,這樣能防止日志無限制暴漲造成文件系統空間浪費。
基于log rotate
的特點(會產生重命名文件的情況),日志工具可以通過記錄并對比inode
來判斷文件是否是重命名的。
6. 歸檔模式采集
遍歷和watch
一個擁有百萬級文件個數的目錄,是件十分浪費資源的事情。因為,實際情況下,這種目錄的數據都是歷史數據,而且不會發生變化。因此,日志工具應當能夠支持我們稱為歸檔模式
的工作模式,這種模式下,遍歷和watch
目錄將采用極低的頻次,這樣不會浪費資源。
7. 文件狀態異常
文件狀態異常是指下列可能的情況:
- 讀取文件的權限發生變化
- 讀取文件時發生某種錯誤
程序應避免對這種文件“一刀切”
,因為可能過一段時間文件又變成正常狀態了。所以,應當定期把有問題的文件再嘗試讀取,這樣不會遺漏。
8. 字符集的探測和轉化
在一些老的系統中,日志的編碼格式依舊會采用本地編碼。典型的是GBK
編碼的日志。在日志上報的過程中,應采用統一的編碼格式。幾乎所有的系統都支持libiconv
庫,這是一個可進行編碼轉化的常用庫。
9. 多行合并
日志數據由應用程序生成,?許多應用程序在寫入日志的時候,一條邏輯日志包含多行。比如下面的java
異常日志,打印出了堆棧的信息:
11 五月 2016 11:35:52,602 ERROR java.lang.IllegalArgumentException: No bean specified
at org.apache.commons.beanutils.PropertyUtilsBean.getNestedProperty(PropertyUtilsBean.java:632)
at org.apache.commons.beanutils.PropertyUtilsBean.getProperty(PropertyUtilsBean.java:715)
at org.apache.commons.beanutils.PropertyUtils.getProperty(PropertyUtils.java:290)
at lib.util.BeanUtil.getBeanProperty(BeanUtil.java:184)
at lib.comm.services.CommWebService.getResponse(CommWebService.java:173)
at lib.comm.services.CommWebService.SendSimplePack(CommWebService.java:307)
at lib.comm.services.CommWebService.exchange(CommWebService.java:40)
at lib.comm.CommunicationUtil.exchange(CommunicationUtil.java:46)
at lib.comm.CommunicationUtil.exchangeFull(CommunicationUtil.java:105)
at lib.helper.TradeHelper.tellerBasicInfoQuery(TradeHelper.java:1520)
從日志收集程序的角度,這里有很多行,但是,此時如果按行來分割是完全不行的。因此,應當提供一種可以合并多行日志為一行日志的能力。注意到這個日志以一個時間為開始(通常都是這樣),那么我們就可以設置一個正則匹配規則,匹配到就認為是一個邏輯日志行的開始:
/\d{2} \S+ \d{4} \d{2}:\d{2}:\d{2},\d{3}/
這樣收集上來的日志才便于處理和分析。通過靈活的設置正則,極大的降低了后端處理日志的難度。
10. Follow Symbolic Link
采集器能夠支持一個開關,用于設置是否對鏈接進行跟蹤,即讀取鏈接實際指向的文件或目錄。
其實,此外還有很多亮點功能值得探討,比如:
- 對歷史的歸檔日志(tar包),直接讀取歸檔壓縮文件,從而避免先解壓再讀取的麻煩。
- 在通配模式下,排除(exclude)或包含(include)某些特殊日志