logrus 通過實現 Hook
接口擴展 hook 機制,可以根據需求將日志分發到任意的存儲介質, 比如 es, mq 或者監控報警系統,及時獲取異常日志。可以說極大的提高了日志系統的可擴展性。
hook 內部實現
Hook
接口定義如下:
type Hook interface {
// 定義哪些等級的日志觸發 hook 機制
Levels() []Level
// hook 觸發器的具體執行操作
// 如果 Fire 執行失敗,錯誤日志會重定向到標准錯誤流
Fire(*Entry) error
}
那logrus
的內部是怎么實現觸發的呢, logrus
中有個內部結構LevelHooks
用來存儲所有定義的 hook 函數。
// 存儲全局 hooks, 以日志等級為鍵聚合存儲
type LevelHooks map[Level][]Hook
// 添加 hooks
func (hooks LevelHooks) Add(hook Hook) {
for _, level := range hook.Levels() {
hooks[level] = append(hooks[level], hook)
}
}
// 根據日志等級觸發 hooks
func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
for _, hook := range hooks[level] {
if err := hook.Fire(entry); err != nil {
return err
}
}
return nil
}
在打印日志時, entry
會調用 fireHooks()
函數,該函數會觸發所有對應的日志等級 的 hook 邏輯。
// 觸發 hooks
func (entry *Entry) fireHooks() {
entry.Logger.mu.Lock()
defer entry.Logger.mu.Unlock()
err := entry.Logger.Hooks.Fire(entry.Level, entry)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
}
}
自定義 hook
說了這么多,我們寫一個簡單的自定義 hook 的例子。在這個例子中我們希望當系統發生error
或者panic
的時候,將錯誤日志打印到單獨的 err.log 文件中便於我們排查錯誤(實際開發中不會這么做)
// MyHook ...
type MyHook struct {
}
// Levels 只定義 error 和 panic 等級的日志,其他日志等級不會觸發 hook
func (h *MyHook) Levels() []log.Level {
return []log.Level{
log.ErrorLevel,
log.PanicLevel,
}
}
// Fire 將異常日志寫入到指定日志文件中
func (h *MyHook) Fire(entry *log.Entry) error {
f, err := os.OpenFile("err.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return err
}
if _, err := f.Write([]byte(entry.Message)); err != nil {
return err
}
return nil
}
func main() {
log.AddHook(&MyHook{})
log.Error("some errors\n")
log.Panic("some panic\n")
log.Print("hello world\n")
}
運行后會創建一個 err.log
文件,文件中存儲了:
some errors
some panic
結構符合我們的預期,至此一個自定義的logrus
hook
就完成了。