log4go 的 4.0.2 版本(https://github.com/ccpaging/log4go/tree/4.0.2)發布以后,
看了看別的 go 語言日志文件設計。發現了一篇好文:
log4go 和 logrus 的對比與分析
https://www.doraemonext.com/archives/783.html
順藤摸瓜,找了一窩關於日志的設計。鏈接如下(含老的鏈接):
- https://github.com/alecthomas/log4go/
這是log4go項目的“鼻祖” - https://github.com/ngmoco/timber
實現了結構化,寫文件緩沖,熱配置等。把log4go重構的面目全非 - https://github.com/siddontang/go/tree/master/log
- https://github.com/sirupsen/logrus
- https://github.com/YoungPioneers/blog4go
- https://github.com/YoungPioneers/blog4go-benchmark 各種 go log 的benchmark對比
- https://github.com/cihub/seelog
異步寫入日志
log4go 的特點之一是異步寫入。格式化日志記錄、寫入文件、轉儲日志等,都會消耗 CPU 的時間,並可能因為錯誤處理而阻塞主線程。
但日志系統僅僅是一個輔助功能,所以,保證主線程的高效運行是首先要達到的設計要求。異步寫入是可行的方案之一。
自擴展日志接口
其實,log4go 是支持類似 logrus 的擴展特性的。
正好糾結於 color text term log 的設計如何處理的問題……因為這個功能使用了第三方包。放在log4go里增加了它的依賴性。但這確實又是我特別特別喜歡的一個功能。
不如把 color text term log 做成擴展日志接口。說干就干……
先搞清楚 log4go 中可用的擴展接口:
type LogWriter interface {
LogWrite(rec *LogRecord)
// This should clean up anything lingering about the LogWriter, as it is called before
// the LogWriter is removed. LogWrite should not be called after Close.
Close()
}
type Filter struct {
Level Level
rec chan *LogRecord // write queue
closed bool // true if Socket was closed at API level
LogWriter
}
type Logger map[string]*Filter
func (log Logger) AddFilter(name string, lvl Level, writer LogWriter) Logger {
log[name] = NewFilter(lvl, writer)
return log
}
擴展程序只要做:
NewXXXLogWrite
,初始化擴展要使用的資源。LogWrite(rec *LogRecord)
,輸出日志記錄- 在
Close()
中關閉或釋放資源 - 在應用程序中調用
AddFilter
把新的日志擴展加入到log4go日志結構中
大功告成了。
其中,Add filter name 是 Logger map 的索引關鍵字,log4go 使用了:
"stdout", "file", "syslog"
如果新加的 Filter 的關鍵字已存在,log4go(4.0.2以后的版本)將自動關閉原來的,再增加新的。代碼如下:
func (log Logger) AddFilter(name string, lvl Level, writer LogWriter) Logger {
if filt, isExist := log[name]; isExist {
filt.Close()
delete(log, name)
}
log[name] = NewFilter(lvl, writer)
return log
}
借助擴展接口,log4go的日志記錄可以采用任何你希望的封裝格式,例如 xml 和 json,這是已經實現的。
以后還可以擴展csv(使日志文件導入到Excel中)或者json封裝的message。
可擴展的日志接口包括:
-
Send error messages as a mail
-
Make TCP/UDP server and let client pull the messages
-
websocket
-
nanomsg pub/sub
-
Store log messages in MySQL
自擴展日志配置接口
log4go 4.0.2 支持 xml 和 json 配置。日志文件的配置有三種方式:
- 在應用程序中配置
- 單獨的配置文件
- 存於主程序配置文件中
日志系統作為一個輔助功能,常常面臨的是第三種情況。而配置文件的格式多種多樣。例如:
windows ini, linux config, json, xml ...
郁悶。log4go 不應當去支持所有的配置文件格式,而是提供接口,讓用戶可以根據自己的主程序的設計需要,自行擴展。
也許應該把 xml 和 json 配置文件支持都以擴展配置文件接口的方式實現,而不是跟 log4go 的主程序捆綁在一起。
文件日志的寫緩沖
已經測試了兩層緩沖寫文件。
第一層是格式化日志記錄,一個單獨的go routine,另一個寫文件,邊格式化記錄邊寫文件,消耗降低了40%。
第二層是用bufio。達到一定的緩沖數量如4k、8k,一次寫文件。消耗降低了80%。
通過判斷Channel中的記錄長度來決定系統何時空閑。當長度為0時,后續沒有新的日志記錄,做一次Flush()。
這種方案簡單。
另外加上 rotate 的優化,效率提高了5倍。
BenchmarkFileLog-4 200000 10675 ns/op
BenchmarkFileNotLogged-4 20000000 106 ns/op
BenchmarkFileUtilLog-4 200000 10660 ns/op
BenchmarkFileUtilNotLog-4 5000000 239 ns/op
BenchmarkCacheFileLog-4 1000000 2191 ns/op
BenchmarkCacheFileNotLogged-4 20000000 106 ns/op
BenchmarkCacheFileUtilLog-4 500000 3680 ns/op
BenchmarkCacheFileUtilNotLog-4 5000000 240 ns/op
Rotate 的改進設想
log4go 自帶 rotate。
linux 系統本來是有 logrotate 的,用 cron 定時執行。非常棒的設計。
簡單說,就是寫日志文件歸寫日志文件,不要去做任何轉儲的判斷。程序員可根據系統的實際運行情況,
自行設置轉儲的時間間隔。轉儲時:
-
加鎖。使 log4go 暫時停止寫日志,這可能是在 linux 系統中 log4go 沒有使用 logrotate 的原因之一。
-
對當前日志文件進行處理。
-
解鎖。盡快恢復 log4go,繼續寫日志到當前日志文件。
-
另開 go routine 對歷史日志文件進行處理。
好吧。暫時就想到這么多了。很多有趣的工作正在進行……
再次感謝 doraemonext@gmail.com 童鞋的好文:log4go 和 logrus 的對比與分析
請關注: