logrus 剖析之 hook

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就完成了。

posted @ 2019-11-10 14:51  jssyjam  阅读(3676)  评论(0编辑  收藏  举报