1. 程式人生 > >【處理多服務器日誌合並處理問題】多服務器的日誌合並統計——apache日誌的cronolog輪循

【處理多服務器日誌合並處理問題】多服務器的日誌合並統計——apache日誌的cronolog輪循

方式 body 擴展性 生成 先後 沒有 href fig 最終

轉發:http://www.chedong.com/tech/rotate_merge_log.html 內容摘要:你完全不必耐心地看完下面的所有內容,因為結論無非以下2點:
1 用 cronolog 幹凈,安全地輪循apache“日”誌
2 用 sort -m 合並排序多個日誌
或者用: clfmerge合並日誌
根據個人的使用經歷:
1 先介紹apache日誌的合並方法;
2 然後根據由此引出的問題說明日誌輪循的必要性和解決方法,介紹如何通過cronolog對apache日誌進行輪循;
中間有很多在設計日誌合並過程中一些相關工具的使用技巧和一些嘗試的失敗經歷……
我相信解決以上問題的路徑不止這一條途徑,以下方案肯定不是最簡便或者說成本最低的,希望能和大家有更多的交流。

多服務器日誌合並統計的必要性

越來越多大型的WEB服務使用DNS輪循來實現負載均衡:使用多個同樣角色的服務器做前臺的WEB服務,這大大方便了服務的分布規劃和擴展性,但多個服務 器的分布使得日誌的分析統計也變得有些麻煩。如果使用webalizer等日誌分析工具對每臺機器分別做日誌統計:
1 會對數據的匯總帶來很多麻煩,比如:統計的總訪問量需要將SERVER1 SERVER2...上指定月份的數字相加。
2 會大大影響統計結果中唯一訪客數unique visits,唯一站點數unique sites的等指標的統計,因為這幾個指標並非幾臺機器的代數相加。

統一日誌統計所帶來的好處是顯而易見的,但如何把所有機器的統計合並到一個統計結果裏呢?


首先也許會想:多個服務器能不能將日誌記錄到同一個遠程文件裏呢?我們不考慮使用遠程文件系統記錄日誌的問題,因為帶來的麻煩遠比你獲得的方便多的多……
因此,要統計的多個服務器的日誌還是:分別記錄=>並通過一定方式定期同步到後臺=>合並=>後用日誌分析工具來進行分析。

首先,要說明為什麽要合並日誌:因為webalizer沒有將同一天的多個日誌合並的功能
先後運行
webalizer log1
webalizer log2
webalizer log3
這樣最後的結果是:只有log3的結果。

能不能將log1<<log2<<log3簡單疊加呢?
因為一個日誌的分析工具不是將日誌一次全部讀取後進行分析,而且流式的讀取日誌並按一定時間間隔,保存階段性的統計結果。因此時間跨度過大(比如2條日誌 間隔超過5分鐘),一些日誌統計工具的算法就會將前面的結果“忘掉”。因此, log1<<log2<<log3直接文件連接的統計結果還是:只有log3的統計結果。


多臺服務日誌合並問題:把多個日誌中的記錄按時間排序後合並成一個文件

典型的多個日誌文件的時間字段是這樣的:
log1 log2 log3
00:15:00 00:14:00 00:11:00
00:16:00 00:15:00 00:12:00
00:17:00 00:18:00 00:13:00
00:18:00 00:19:00 00:14:00
14:18:00 11:19:00 10:14:00
15:18:00 17:19:00 11:14:00
23:18:00 23:19:00 23:14:00

日誌合並必須是按時間將多個日誌的交叉合並。合並後的日誌應該是:
00:15:00 來自log1
00:15:00 來自log2
00:16:00 來自log1
00:17:00 來自log3
00:18:00 來自log2
00:19:00 來自log1
....

如何合並多個日誌文件?
下面以標準的clf格式日誌(apache)為例:
apche的日誌格式是這樣的:
%h %l %u %t \"%r\" %>s %b
具體的例子:
111.222.111.222 - - [03/Apr/2002:10:30:17 +0800] "GET /index.html HTTP/1.1" 200 419

最簡單的想法是將日誌一一讀出來,然後按日誌中的時間字段排序
cat log1 log2 log3 |sort -k 4 -t " "
註釋:
-t " ": 日誌字段分割符號是空格
-k 4: 按第4個字段排序,也就是:[03/Apr/2002:10:30:17 +0800] 這個字段
-o log_all: 輸出到log_all這個文件中

但這樣的效率比較低,要知道。如果一個服務已經需要使用負載均衡,其服務的單機日誌條數往往都超過了千萬級,大小在幾百M,這樣要同時對多個幾百M的日誌 進行排序,機器的負載可想而之……
其實有一個優化的途徑,要知道:即使單個日誌本身已經是一個“已經按照時間排好序“的文件了,而sort對於這種文件的排序合並提供了一個優化合並算法: 使用 -m merge合並選項,
因此:合並這樣格式的3個日誌文件log1 log2 log3並輸出到log_all中比較好方法是:
sort -m -t " " -k 4 -o log_all log1 log2 log3
註釋:
-m: 使用 merge優化算法

註意:合並後的日誌輸出最好壓縮以後再發給webalizer處理
有的系統能處理2G的文件,有的不能。有的程序能處理大於2G的文件,有的不能。盡量避免大於2G的文件,除非確認所有參與處理的程序和操作系統都能處理 這樣的文件。所以輸出後的文件如果大於2G,最好將日誌gzip後再發給webalizer處理:大於2G的文件分析過程中文件系統出錯的可能性比較大, 並且gzip後也能大大降低分析期間的I/O操作。

日誌的按時間排序合並就是這樣實現的。

日誌的輪循機制

讓我們關心一下數據源問題:webalizer其實是一個按月統計的工具,支持增量統計:因此對於大型的服務,我可以按天將apache的日誌合並後送給 webalizer統計。WEB日誌是如何按天(比如每天子夜00:00:00)截斷呢?
如果你每天使用crontab:每天0點準時將日誌備份成access_log_yesterday
mv /path/to/apache/log/access_log /path/to/apache/log/access_log_yesterday
的話:你還需要:馬上運行一下:apache restart 否則:apache會因為的日誌文件句柄丟失不知道將日誌記錄到哪裏去了。這樣歸檔每天子夜重啟apache服務會受到影響。
比較簡便不影響服務的方法是:先復制,後清空
cp /path/to/apache/log/access_log /path/to/apache/log/access_log_yesterday
echo >/path/to/apache/log/access_log

嚴肅的分析員會這樣做發現一個問題:
但cp不可能嚴格保證嚴格的0點截斷。加入復制過程用了6秒,截斷的access_log_yesterday日誌中會出現復制過程到00:00:06期 間的日誌。對於單個日誌統計這些每天多出來幾百行日誌是沒有問題的。但對於多個日誌在跨月的1天會有一個合並的排序問題:
[31/Mar/2002:59:59:59 +0800]
[31/Mar/2002:23:59:59 +0800]
[01/Apr/2002:00:00:00 +0800]
[01/Apr/2002:00:00:00 +0800]

要知道[01/Apr/2002:00:00:00 這個字段是不可以進行“跨天排序”的。因為日期中使用了dd/mm/yyyy,月份還是英文名,如果按照字母排序,很有可能是這樣的結果:排序導致了日誌 的錯誤
[01/Apr/2002:00:00:00 +0800]
[01/Apr/2002:00:00:00 +0800]
[01/Apr/2002:00:00:00 +0800]
[01/Apr/2002:00:00:00 +0800]
[01/Apr/2002:00:00:00 +0800]
[01/Apr/2002:00:00:00 +0800]
[01/Apr/2002:00:00:00 +0800]
[31/Mar/2002:59:59:59 +0800]
[31/Mar/2002:59:59:59 +0800]
[31/Mar/2002:23:59:59 +0800]
[31/Mar/2002:59:59:59 +0800]
[31/Mar/2002:23:59:59 +0800]

這些跨天過程中的非正常數據對於webalizer等分析工具來說簡直就好像是吃了一個臭蟲一樣,運行的結果是:它可能會把前一個月所有的數據都丟失!因 此這樣的數據會有很多風險出現在處理上月最後一天的數據的過程中。

問題的解決有幾個思路:
1 事後處理:
。所以一個事後的處理的方法是:用grep命令在每月第1天將日誌跨月的日誌去掉,比如:
grep -v "01/Apr" access_log_04_01 > access_log_new

修改SORT後的日誌:所有跨天的數據去掉。也許對日誌的事後處理是一個途徑,雖然sort命令中有對日期排序的特殊選項 -M(註意是:大寫M),可以讓指定字段按照英文月份排序而非字母順序,但對於apache日誌來說,用SORT命令切分出月份字段很麻煩。(我嘗試過用 "/"做分割符,並且使用“月份” “年:時間”這兩個字段排序)。雖然用一些PERL的腳本肯定可以實現,但最終我還是放棄了。這不符合系統管理員的設計原則:通用性。 並且你需要一直問自己:有沒有更簡單的方法呢?
還有就是將日誌格式改成用TIMESTAMP(象SQUID的日誌就沒有這個問題,它的日誌本身就是使用TIMESTAMP做時間時間戳的),但我無法保 證所有的日誌工具都能識別你在日期這個字段使用了特別的格式。

2 優化數據源:
最好的辦法還是優化數據源。將數據源保證按天輪循,同一天的日誌中的數據都在同一天內。這樣以後你無論使用什麽工具(商業的,免費的)來分析日誌,都不會 因為日誌復雜的預處理機制受到影響。

首先可能會想到的是控制截取日誌的時間:比如嚴格從0點開始截取日誌,但在子夜前1分鐘還是後一分鐘開始截取是沒有區別的,你仍然無法控制一個日誌中有跨 2天記錄的問題,而且你也無法預測日誌歸檔過程使用的時間。
因此必須要好好考慮一下使用日誌輪循工具的問題,這些日誌輪循工具要符合:
1 不中斷WEB服務:不能停apache=>移動日誌=>重啟apache
2 保證同一天日誌能夠按天輪循:每天一個日誌00:00:00-23:59:59
3 不受apache重啟的影響:如果apache每次重啟都會生成一個新的日誌是不符合要求的
4 安裝配置簡單

首先考慮了apache/bin目錄下自帶的一個輪循工具:rotatelogs 這個工具基本是用來按時間或按大小控制日誌的,無法控制何時截斷和如何按天歸檔。
然後考慮logrotate後臺服務:logrotate是一個專門對各種系統日誌(syslogd,mail)進行輪循的後臺服務,比如SYSTEM LOG,但其配置比較復雜,放棄,實際上它也是對相應服務進程發出一個-HUP重啟命令來實現日誌的截斷歸檔的。

在apache的FAQ中,推薦了經過近2年發展已經比較成熟的一個工具cronolog:安裝很簡單:configure=>make=> make install

他的一個配置的例子會讓你了解它有多麽適合日誌按天輪循:對httpd.conf做一個很小的修改就能實現:
TransferLog "|/usr/sbin/cronolog /web/logs/%Y/%m/%d/access.log"
ErrorLog "|/usr/sbin/cronolog /web/logs/%Y/%m/%d/errors.log"

然後:日誌將寫入
/web/logs/2002/12/31/access.log
/web/logs/2002/12/31/errors.log
午夜過後:日誌將寫入
/web/logs/2003/01/01/access.log
/web/logs/2003/01/01/errors.log
而2003 2003/01 和 2003/01/01 如果不存在的話,將自動創建

所以,只要你不在0點調整系統時間之類的話,日誌應該是完全按天存放的(00:00:00-23:59:59),後面日誌分析中: [31/Mar/2002:15:44:59這個字段就和日期無關了,只和時間有關。

測試:考慮到系統硬盤容量,決定按星期輪循日誌
apache配置中加入:
#%w weekday
TransferLog "|/usr/sbin/cronolog /path/to/apache/logs/%w/access_log"

重啟apache後,除了原來的CustomLog /path/to/apche/logs/access_log繼續增長外,系統log目錄下新建立了 3/目錄(測試是在周3),過了一會兒,我忽然發現2個日誌的增長速度居然不一樣!
分別tail了2個日誌才發現:
我設置CustomLog使用的是combined格式,就是包含(擴展信息的),而TransferLog使用的是缺省日誌格式,看了apache的手 冊才知道,TransferLog是用配置文件中離它自己最近的一個格式作為日誌格式的。我的httpd.conf裏寫的是:
LogFormat ..... combined
LogFormat ... common
...
CustomLog ... combined
TransferLog ...

所以TrasferLog日誌用的是缺省格式,手冊裏說要讓TRANSFER日誌使用指定的格式需要:
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
TransferLog "|/usr/local/sbin/cronolog /path/to/apache/logs/%w/access_log"

重啟,OK,日誌格式一樣了。
這樣的設置結果其實是同時在logs目錄下分別記錄2個日誌access_log和%w/access_log,能不能只記錄%w/下的日誌那?
查apache手冊,更簡單的方法:直接讓CustomLog輸出到cronolog歸檔日誌,並且還能指定格式。
CustomLog "|/usr/local/sbin/cronolog /path/to/apache/logs/%w/access_log" combined

最後是一個日誌同步的問題。

任務:每天淩晨找到前1天的日誌,另存一個文件準備發送到服務器上。
比如我要保留前1周的日誌:每天復制前1天的日誌到指定目錄,等待日誌服務器來抓取:
/bin/cp -f /path/to/apache/logs/`date -v-1d +%w`/access_log /path/for/backup/logs/access_log_yesterday

在FREEBSD上使用以下命令
date -v-1d +%w
註釋:
-v-1d: 前1天,而在GNU/Linux上這個選項應該是date -d yesterday
+%w: weekday,由於使用的都是標準時間函數庫,所有工具中的WEEKDAY定義都是一樣的 0-6 => 周日-周六

註意:
寫到CRONTAB裏的時候"%"前面需要加一個"\"轉義:每天0點5分進行一次日誌歸檔,
另外一個問題就是在cront中需要用:rm -f {} ; 而不是rm -f {}\;
5 0 * * * /bin/cp /path/to/logs/`date -v-1d +\%w`/access_log /path/to/for_sync/logs/access_yesterday
37 10 * * * /usr/bin/find /home/apache/logs/ -name access_log -mtime +1 -exec /bin/rm -f {} ;

首次開始cronolog日誌統計是周3,一周以後日誌又將輪循回3/access_log
但這次日誌是追加到3/access_log還是重新創建一個文件呢?>>access_log or >access_log?
我測試的結果是日誌將被追加:
[01/Apr/2002:23:59:59 +0800]
[01/Apr/2002:23:59:59 +0800]
[08/Apr/2002:00:00:00 +0800]
[08/Apr/2002:00:00:00 +0800]

肯定是不希望每次日誌還帶著上周的數據的並重復統計一次的(雖然對結果沒影響),而且這樣%w/下的日誌不是也越來越多了嗎?
解決方法1 把每天的cp改成mv
解決方法2 每天復制完成後:刪除6天以前的access_log日誌
find /path/to/apache/logs -name access_log -mtime +6 -exec rm -f {}\;
多保留幾天的日誌還是有必要的:萬一日誌分析服務器壞了一天呢?

以下是把apache安裝在/home/apache下每天統計的一個腳本文件:
#!/bin/sh

#backup old log
/bin/cp -f /home/apache/logs/`date -d yesterday +%w`/access_log /home/apache/logs/access_log_yesterday

#remove old log
/usr/bin/find /home/apache/logs -name access_log -mtime +6 -exec rm -f {}\;

#analysis with webalizer
/usr/local/sbin/webalizer

總結:
1 用 cronolog 幹凈,安全地輪循日誌
2 用 sort -m 排序合並多個日誌


參考資料:

日誌分析統計工具:
http://directory.google.com/Top/Computers/Software/Internet/Site_Management/Log_Analysis/

Apche的日誌設置:
http://httpd.apache.org/docs/mod/mod_log_config.html

Apache的日誌輪循:
http://httpd.apache.org/docs/misc/FAQ.html#rotate

Cronolog
http://www.cronolog.org

Webalizer
http://www.mrunix.net/webalizer/
Webalzer的Windows版
http://www.medasys-lille.com/webalizer/

AWStats的使用簡介
http://www.chedong.com/tech/awstats.html

【處理多服務器日誌合並處理問題】多服務器的日誌合並統計——apache日誌的cronolog輪循