概述

这是我在了解 Logrus 这个日志库时做的一个笔记记录,主要是介绍一些 logrus 的特性,以及我尝试这些特性时的一些个人观点,同时,我还会简单解析一下 logrus 是如何实现这些特性的。

核心组件

  1. Logger
  2. - Out io.Writer
  3. - Hooks LevelHooks
  4. - Formatter Formatter
  5. - Level Level
  6. - entryPool sync.Pool
  7. - BufferPool BufferPool
  8. Hook <interface>
  9. - Levels() []Level
  10. - Fire(*Entry) error
  11. Formatter <interface>
  12. - Format(*Entry) ([]byte, error)
  13. Entry
  14. - Logger *Logger
  15. - Data Fields
  16. - Time time.Time
  17. - Level Level
  18. - Caller *runtime.Frame
  19. - Buffer *bytes.Buffer
  20. - Context context.Context

组件交互顺序

所以从核心组件的字段来看,大概也可以了解到顺序是怎么样的,这里再给出一张图以打印一个 Info 级别的日志内容来看下实际顺序是怎么样的:

代码版本:v1.8.1

  1. [root@liqiang.io]# logrus.Infof("xxxx")
  2. logger.go:164 func (logger *Logger) Infof(format string, args ...interface{}) {
  3. --> logger.go:165 logger.Logf(InfoLevel, format, args...)
  4. --> logger.go:148 func (logger *Logger) Logf(level Level, format string, args ...interface{}) {
  5. --> logger.go:149 if logger.IsLevelEnabled(level) {
  6. --> logger.go:150 entry := logger.newEntry()
  7. --> logger.go:96 entry, ok := logger.entryPool.Get().(*Entry)
  8. --> logger.go:151 entry.Logf(level, format, args...)
  9. --> entry.go:336 func (entry *Entry) Logf(level Level, format string, args ...interface{}) {
  10. --> entry.go:337 if entry.Logger.IsLevelEnabled(level) {
  11. --> entry.go:338 entry.Log(level, fmt.Sprintf(format, args...))
  12. --> entry.go:292 if entry.Logger.IsLevelEnabled(level) {
  13. --> entry.go:293 entry.log(level, fmt.Sprint(args...))
  14. --> entry.go:221 func (entry *Entry) log(level Level, msg string) {
  15. --> entry.go:224 newEntry := entry.Dup()
  16. --> entry.go:233 newEntry.Logger.mu.Lock()
  17. --> entry.go:234 reportCaller := newEntry.Logger.ReportCaller
  18. --> entry.go:235 newEntry.Logger.mu.Unlock()
  19. --> entry.go:237 if reportCaller {
  20. --> entry.go:238 newEntry.Caller = getCaller()
  21. --> entry.go:241 newEntry.fireHooks()
  22. --> entry.go:265 entry.Logger.mu.Lock()
  23. --> entry.go:266 tmpHooks = make(LevelHooks, len(entry.Logger.Hooks))
  24. --> entry.go:267 for k, v := range entry.Logger.Hooks {
  25. --> entry.go:268 tmpHooks[k] = v
  26. --> entry.go:270 entry.Logger.mu.Unlock()
  27. --> entry.go:272 err := tmpHooks.Fire(entry.Level, entry)
  28. --> hooks.go:27 for _, hook := range hooks[level] {
  29. --> hooks.go:28 if err := hook.Fire(entry); err != nil {
  30. --> entry.go:243 buffer = getBuffer()
  31. --> entry.go:249 newEntry.Buffer = buffer
  32. --> entry.go:251 newEntry.write()
  33. --> entry.go:279 serialized, err := entry.Logger.Formatter.Format(entry)
  34. --> entry.go:284 entry.Logger.mu.Lock()
  35. --> entry.go:285 defer entry.Logger.mu.Unlock()
  36. --> entry.go:286 if _, err := entry.Logger.Out.Write(serialized); err != nil {
  37. --> logger.go:152 logger.releaseEntry(entry)
  38. --> logger.go:104 entry.Data = map[string]interface{}{}
  39. --> logger.go:105 logger.entryPool.Put(entry)

从上面可以看出,主要的业务逻辑还是在 Entry 中完成,然后 Entry 中包含了一个 Logger 的引用,formatter 和 hook 都是通过这个引用来获取的,然后在 entry 中执行,同时还需要注意到的是这里面调用了好几次的锁,但是持续时间都很短,可能性能影响也不是很大(这个需要 benchmark 一下看看)。

总的来说,整体链条都还比较清晰,没有过于复杂的设计,概念也比较明确,所以就不看再多的东西了,值得学习的一个是这里面用到了几个池,这个对于高性能场景还是很有借鉴意义的,减少内存的分配和回收,还有比如一些小细节:tmpHooks = make(LevelHooks, len(entry.Logger.Hooks)),这些都是值得学习的。

问题解答

hook 是什么

hook 是对特定的 log level 执行的一段代码,可以针对特定的 log level 做特定的工作,例如 Error 的时候,向 错误跟踪服务发送一个信息之类的。(我觉得不是太必要,可以通过多个 handler 来实现?)

Formatter 是什么

  1. [root@liqiang.io]# cat formatter.go
  2. type Formatter interface {
  3. Format(*Entry) ([]byte, error)
  4. }

就是定义怎么将 Entry 转化成 []byte 的结构。

比较独特的特性

一些评价

优点

坏处