logrus

Written by with ♥ on in Go

log "github.com/sirupsen/logrus"

Formatter 格式化

包内提供 JSONFormatterTEXTFormatter 这两种常用的日志格式。

func init() {
  log.SetFormatter(&log.JSONFormatter{})
  log.SetFormatter(&log.TEXTFormatter{})

  // Output to stdout instead of the default stderr
  // Can be any io.Writer, see below for File example
  log.SetOutput(os.Stdout)
}

Level

六个日志等级

  • PanicLevel:记录日志后调用 panic()
  • FatalLevel:记录日志后调用 logger.Exit(1)
  • ErrorLevel
  • WarnLevel
  • InfoLevel(默认日志级别)
  • DebugLevel
  • TraceLevel

设置日志等级,只处理该等级以上的日志。

// Will log anything that is info or above (warn, error, fatal, panic). Default.
log.SetLevel(log.InfoLevel)

默认 Logger 实例

  • 默认输出到标准错误;
  • 默认使用 TextFormatter
  • 默认日志级别为 InfoLevel;
var std = New()

// It's recommended to make this a global instance called `log`.
func New() *Logger {
	return &Logger{
		Out:          os.Stderr,
		Formatter:    new(TextFormatter),
		Hooks:        make(LevelHooks),
		Level:        InfoLevel,
		ExitFunc:     os.Exit,
		ReportCaller: false,
	}
}

Field

log.WithFields(log.Fields{
"animal": "walrus",
"size":   10,
}).Info("A group of walrus emerges from the ocean")

log.WithFields(log.Fields{
"omg":    true,
"number": 122,
}).Warn("The group's number increased tremendously!")

log.WithFields(log.Fields{
"omg":    true,
"number": 100,
}).Fatal("The ice breaks!")

// A common pattern is to re-use fields between logging statements by re-using
// the logrus.Entry returned from WithFields()
contextLogger := log.WithFields(log.Fields{
"common": "this is a common field",
"other": "I also should be logged always",
})

contextLogger.Info("I'll be logged with common and other field")
contextLogger.Info("Me too")

Hook

原理

通过 logrus.Infof() 函数分析内部流程:

logrus.Infof("", "")

->

// 导出函数 Infof 内部调用 std.Infof
// std 是默认的 Logger 实例
func Infof(format string, args ...interface{}) {
	std.Infof(format, args...)
}

->

// 调用 Logger 结构体的 Logf 方法,传入 InfoLevel 为第一个参数
// 在这里可以发现所有的 Level 都是调到 Logf 方法,只是第一个参数传入的 Level 不一样
func (logger *Logger) Infof(format string, args ...interface{}) {
	logger.Logf(InfoLevel, format, args...)
}

// 初始化内部的 Entry 实例
// 调用 Entey 的 logf 方法打印日志
// Level 在这里的作用只是与内部的 Level 作比较,判断是否需要继续往下执行
func (logger *Logger) Logf(level Level, format string, args ...interface{}) {
	if logger.IsLevelEnabled(level) {
		entry := logger.newEntry()
		entry.Logf(level, format, args...)
		logger.releaseEntry(entry)
	}
}

实际上完成工作的是 Entry 实例,下面看看 Entry 的实现。

Entry

logger.newEntry()entryPool 获取可复用的 Entry 实例。在每个 Logger 实例内部有一个对象池用于复用 Entry 实例。

// entryPoll 里没有可用的 entry 实例时,会调用 NewEntry 函数创建一个新的返回
func (logger *Logger) newEntry() *Entry {
	entry, ok := logger.entryPool.Get().(*Entry)
	if ok {
		return entry
	}
	return NewEntry(logger)
}

func NewEntry(logger *Logger) *Entry {
	return &Entry{
		Logger: logger,
		// Default is three fields, plus one optional.  Give a little extra room.
		Data: make(Fields, 6),
	}
}

在使用完以后调用 logger.releaseEntry(entry) 方法把 entry 实例释放回 poll 复用。

func (logger *Logger) releaseEntry(entry *Entry) {
	entry.Data = map[string]interface{}{}
	logger.entryPool.Put(entry)
}

明白了 entry 的创建和销毁,下面看看其实现的功能。

entry.Logf

// Logf 方法最终调用 Log 方法
func (entry *Entry) Logf(level Level, format string, args ...interface{}) {
	if entry.Logger.IsLevelEnabled(level) {
		entry.Log(level, fmt.Sprintf(format, args...))
	}
}

// Log 方法调用非导出函数 log 实现功能
func (entry *Entry) Log(level Level, args ...interface{}) {
	if entry.Logger.IsLevelEnabled(level) {
		entry.log(level, fmt.Sprint(args...))
	}
}

entry.log 没有声明为指针方法,因为在多个 goroutine 调用时可能会出现 race conditions

// 进入最终的 log 输出日志到 io.Writer 接口
// This function is not declared with a pointer value because otherwise
// race conditions will occur when using multiple goroutines
func (entry Entry) log(level Level, msg string) {
	var buffer *bytes.Buffer

	// Default to now, but allow users to override if they want.
	//
	// We don't have to worry about polluting future calls to Entry#log()
	// with this assignment because this function is declared with a
	// non-pointer receiver.
	if entry.Time.IsZero() {
		entry.Time = time.Now()
	}

	entry.Level = level
	entry.Message = msg
	if entry.Logger.ReportCaller {
		entry.Caller = getCaller()
	}

	entry.fireHooks() // 触发对应 Level 的 Hooks

	buffer = bufferPool.Get().(*bytes.Buffer)
	buffer.Reset()
	defer bufferPool.Put(buffer)
	entry.Buffer = buffer
    
	entry.write() // 写入数据到 Output
    
	entry.Buffer = nil

	// To avoid Entry#log() returning a value that only would make sense for
	// panic() to use in Entry#Panic(), we avoid the allocation by checking
	// directly here.
	if level <= PanicLevel {
		panic(&entry)
	}
}