logrus
log "github.com/sirupsen/logrus"
Formatter 格式化
包内提供 JSONFormatter
和 TEXTFormatter
这两种常用的日志格式。
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)
}
}