logrus 源码分析

logrus 特性

  1. 完全兼容Go标准库日志模块。logrus拥有六种日志级别:debug、info、warn、error、fatal和panic,这是Go标准库日志模块的API的超集。如果你的项目使用标准库日志模块,完全可以用最低的代价迁移到logrus上。
  2. 可扩展的Hook机制。允许使用者通过hook方式,将日志分发到任意地方,如本地文件系统、标准输出等。
  3. 可选的日志输出格式。logrus内置了两种日志格式,JSONFormatter和TextFormatter。如果这两个格式不满足需求,可以自己动手实现接口Formatter,来定义自己的日志格式。
  4. Field机制。logrus鼓励通过Field机制进行精细化、结构化的日志记录,而不是通过冗长的消息来记录日志。
  5. logrus是一个可插拔的、结构化的日志框架。

logrus 源码结构

logrus 对外提供实现的接口(exported.go), 提供的接口默认使用的stderr输出,TextFormater格式。


var (
    // std is the name of the standard logger in stdlib `log`
    std = New()
)

// SetOutput sets the standard logger output.
func SetOutput(out io.Writer) {
    std.SetOutput(out)
}

// SetFormatter sets the standard logger formatter.
func SetFormatter(formatter Formatter) {
    std.SetFormatter(formatter)
}

// SetReportCaller sets whether the standard logger will include the calling
// method as a field.
func SetReportCaller(include bool) {
    std.SetReportCaller(include)
}

// SetLevel sets the standard logger level.
func SetLevel(level Level) {
    std.SetLevel(level)
}

// GetLevel returns the standard logger level.
func GetLevel() Level {
    return std.GetLevel()
}

func IsLevelEnabled(level Level) bool {
    return std.IsLevelEnabled(level)
}

// AddHook adds a hook to the standard logger hooks.
func AddHook(hook Hook) {
    std.AddHook(hook)
}

// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
func WithError(err error) *Entry {
    return std.WithField(ErrorKey, err)
}

// WithContext creates an entry from the standard logger and adds a context to it.
func WithContext(ctx context.Context) *Entry {
    return std.WithContext(ctx)
}

// WithField creates an entry from the standard logger and adds a field to
// it. If you want multiple fields, use `WithFields`.
//
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
// or Panic on the Entry it returns.
func WithField(key string, value interface{}) *Entry {
    return std.WithField(key, value)
}

// WithFields creates an entry from the standard logger and adds multiple
// fields to it. This is simply a helper for `WithField`, invoking it
// once for each field.
//
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
// or Panic on the Entry it returns.
func WithFields(fields Fields) *Entry {
    return std.WithFields(fields)
}

// WithTime creates an entry from the standard logger and overrides the time of
// logs generated with it.
//
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
// or Panic on the Entry it returns.
func WithTime(t time.Time) *Entry {
    return std.WithTime(t)
}

// Trace logs a message at level Trace on the standard logger.
func Trace(args ...interface{}) {
    std.Trace(args...)
}

// Debug logs a message at level Debug on the standard logger.
func Debug(args ...interface{}) {
    std.Debug(args...)
}

// Print logs a message at level Info on the standard logger.
func Print(args ...interface{}) {
    std.Print(args...)
}

// Info logs a message at level Info on the standard logger.
func Info(args ...interface{}) {
    std.Info(args...)
}

// Warn logs a message at level Warn on the standard logger.
func Warn(args ...interface{}) {
    std.Warn(args...)
}

// Warning logs a message at level Warn on the standard logger.
func Warning(args ...interface{}) {
    std.Warning(args...)
}

// Error logs a message at level Error on the standard logger.
func Error(args ...interface{}) {
    std.Error(args...)
}

// Panic logs a message at level Panic on the standard logger.
func Panic(args ...interface{}) {
    std.Panic(args...)
}

// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
func Fatal(args ...interface{}) {
    std.Fatal(args...)
}

// TraceFn logs a message from a func at level Trace on the standard logger.
func TraceFn(fn LogFunction) {
    std.TraceFn(fn)
}

// DebugFn logs a message from a func at level Debug on the standard logger.
func DebugFn(fn LogFunction) {
    std.DebugFn(fn)
}

// PrintFn logs a message from a func at level Info on the standard logger.
func PrintFn(fn LogFunction) {
    std.PrintFn(fn)
}

// InfoFn logs a message from a func at level Info on the standard logger.
func InfoFn(fn LogFunction) {
    std.InfoFn(fn)
}

// WarnFn logs a message from a func at level Warn on the standard logger.
func WarnFn(fn LogFunction) {
    std.WarnFn(fn)
}

// WarningFn logs a message from a func at level Warn on the standard logger.
func WarningFn(fn LogFunction) {
    std.WarningFn(fn)
}

// ErrorFn logs a message from a func at level Error on the standard logger.
func ErrorFn(fn LogFunction) {
    std.ErrorFn(fn)
}

// PanicFn logs a message from a func at level Panic on the standard logger.
func PanicFn(fn LogFunction) {
    std.PanicFn(fn)
}

// FatalFn logs a message from a func at level Fatal on the standard logger then the process will exit with status set to 1.
func FatalFn(fn LogFunction) {
    std.FatalFn(fn)
}

// Tracef logs a message at level Trace on the standard logger.
func Tracef(format string, args ...interface{}) {
    std.Tracef(format, args...)
}

// Debugf logs a message at level Debug on the standard logger.
func Debugf(format string, args ...interface{}) {
    std.Debugf(format, args...)
}

// Printf logs a message at level Info on the standard logger.
func Printf(format string, args ...interface{}) {
    std.Printf(format, args...)
}

// Infof logs a message at level Info on the standard logger.
func Infof(format string, args ...interface{}) {
    std.Infof(format, args...)
}

// Warnf logs a message at level Warn on the standard logger.
func Warnf(format string, args ...interface{}) {
    std.Warnf(format, args...)
}

// Warningf logs a message at level Warn on the standard logger.
func Warningf(format string, args ...interface{}) {
    std.Warningf(format, args...)
}

// Errorf logs a message at level Error on the standard logger.
func Errorf(format string, args ...interface{}) {
    std.Errorf(format, args...)
}

// Panicf logs a message at level Panic on the standard logger.
func Panicf(format string, args ...interface{}) {
    std.Panicf(format, args...)
}

// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
func Fatalf(format string, args ...interface{}) {
    std.Fatalf(format, args...)
}

// Traceln logs a message at level Trace on the standard logger.
func Traceln(args ...interface{}) {
    std.Traceln(args...)
}

// Debugln logs a message at level Debug on the standard logger.
func Debugln(args ...interface{}) {
    std.Debugln(args...)
}

// Println logs a message at level Info on the standard logger.
func Println(args ...interface{}) {
    std.Println(args...)
}

// Infoln logs a message at level Info on the standard logger.
func Infoln(args ...interface{}) {
    std.Infoln(args...)
}

// Warnln logs a message at level Warn on the standard logger.
func Warnln(args ...interface{}) {
    std.Warnln(args...)
}

// Warningln logs a message at level Warn on the standard logger.
func Warningln(args ...interface{}) {
    std.Warningln(args...)
}

// Errorln logs a message at level Error on the standard logger.
func Errorln(args ...interface{}) {
    std.Errorln(args...)
}

func Panicln(args ...interface{}) {
    std.Panicln(args...)
}

func Fatalln(args ...interface{}) {
    std.Fatalln(args...)
}

如果想要输出日志到文件需要SetOutput 设置文件输出file。接下来主要解析三个接口:

  1. logrus.Info("hello logrus")
  2. logrus.WithFields(logrus.Fields{
         "age": 12,
     }).Info("hello logrus")
  3. logrus.Infof("hello %s", "logrus")

1. WithFields

Fields 是一个map类型的结构: type Fields map[string]interface{}
根据Fields类型封装成Entry结构体返回。

func (logger *Logger) WithFields(fields Fields) *Entry {
    entry := logger.newEntry() // 通过sync.Pool获取Entry结构体
    defer logger.releaseEntry(entry) // 把entry 放回sync.Pool
    return entry.WithFields(fields) 
}

func (logger *Logger) newEntry() *Entry {
    entry, ok := logger.entryPool.Get().(*Entry)
    if ok {
        return entry
    }
    return NewEntry(logger)
}

func (entry *Entry) WithFields(fields Fields) *Entry {
    data := make(Fields, len(entry.Data)+len(fields))
    for k, v := range entry.Data {
        data[k] = v
    }
    fieldErr := entry.err
    for k, v := range fields {
        isErrField := false
        if t := reflect.TypeOf(v); t != nil {
            switch {
            case t.Kind() == reflect.Func, t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Func:
                isErrField = true
            }
        }
        if isErrField {
            tmp := fmt.Sprintf("can not add field %q", k)
            if fieldErr != "" {
                fieldErr = entry.err + ", " + tmp
            } else {
                fieldErr = tmp
            }
        } else {
            data[k] = v
        }
    }
    return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context}
}

2. Info

func (logger *Logger) Info(args ...interface{}) {
    logger.Log(InfoLevel, args...)
}

func (logger *Logger) Log(level Level, args ...interface{}) {
    if logger.IsLevelEnabled(level) { // 判断是否可以打印
        entry := logger.newEntry() // 获取新的Entry
        entry.Log(level, args...)  // entry 去打印
        logger.releaseEntry(entry)
    }
}

func (entry *Entry) Log(level Level, args ...interface{}) {
    if entry.Logger.IsLevelEnabled(level) { // entry里再判断是否可以打印
        entry.log(level, fmt.Sprint(args...)) //格式化args 成string
    } 
}

func (entry *Entry) log(level Level, msg string) {
    var buffer *bytes.Buffer //用来接收字节

    newEntry := entry.Dup()

    if newEntry.Time.IsZero() {
        newEntry.Time = time.Now()
    }

    newEntry.Level = level
    newEntry.Message = msg

    newEntry.Logger.mu.Lock()
    reportCaller := newEntry.Logger.ReportCaller
    bufPool := newEntry.getBufferPool()
    newEntry.Logger.mu.Unlock()

    if reportCaller {
        newEntry.Caller = getCaller()
    }

    newEntry.fireHooks()
    buffer = bufPool.Get()
    defer func() {
        newEntry.Buffer = nil
        buffer.Reset()
        bufPool.Put(buffer)
    }()
    buffer.Reset()
    newEntry.Buffer = buffer

    newEntry.write() // 写入数据

    newEntry.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(newEntry)
    }
}

func (entry *Entry) write() {
    serialized, err := entry.Logger.Formatter.Format(entry) //格式化entry
    if err != nil {
        fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
        return
    }
    entry.Logger.mu.Lock()
    defer entry.Logger.mu.Unlock()
    if _, err := entry.Logger.Out.Write(serialized); err != nil { // 写入数据
        fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
    }
}

格式化:text_format和 json_format

TextFormatter:

// Format renders a single log entry
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
    data := make(Fields)
    for k, v := range entry.Data {
        data[k] = v
    }
    prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
    keys := make([]string, 0, len(data))
    for k := range data {
        keys = append(keys, k)
    }

    var funcVal, fileVal string

    fixedKeys := make([]string, 0, 4+len(data))
    if !f.DisableTimestamp {
        fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime))
    }
    fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel))
    if entry.Message != "" {
        fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg))
    }
    if entry.err != "" {
        fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError))
    }
    if entry.HasCaller() {
        if f.CallerPrettyfier != nil {
            funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
        } else {
            funcVal = entry.Caller.Function
            fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
        }

        if funcVal != "" {
            fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFunc))
        }
        if fileVal != "" {
            fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFile))
        }
    }

    if !f.DisableSorting {
        if f.SortingFunc == nil {
            sort.Strings(keys)
            fixedKeys = append(fixedKeys, keys...)
        } else {
            if !f.isColored() {
                fixedKeys = append(fixedKeys, keys...)
                f.SortingFunc(fixedKeys)
            } else {
                f.SortingFunc(keys)
            }
        }
    } else {
        fixedKeys = append(fixedKeys, keys...)
    }

    var b *bytes.Buffer
    if entry.Buffer != nil {
        b = entry.Buffer
    } else {
        b = &bytes.Buffer{}
    }

    f.terminalInitOnce.Do(func() { f.init(entry) })

    timestampFormat := f.TimestampFormat
    if timestampFormat == "" {
        timestampFormat = defaultTimestampFormat
    }
    if f.isColored() {
        f.printColored(b, entry, keys, data, timestampFormat)
    } else {

        for _, key := range fixedKeys {
            var value interface{}
            switch {
            case key == f.FieldMap.resolve(FieldKeyTime):
                value = entry.Time.Format(timestampFormat)
            case key == f.FieldMap.resolve(FieldKeyLevel):
                value = entry.Level.String()
            case key == f.FieldMap.resolve(FieldKeyMsg):
                value = entry.Message
            case key == f.FieldMap.resolve(FieldKeyLogrusError):
                value = entry.err
            case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller():
                value = funcVal
            case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller():
                value = fileVal
            default:
                value = data[key]
            }
            f.appendKeyValue(b, key, value) // key 和 value 写入b 字节
        }
    }

    b.WriteByte('\n')
    return b.Bytes(), nil
}

json_format

func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
    data := make(Fields, len(entry.Data)+4)
    for k, v := range entry.Data {
        switch v := v.(type) {
        case error:
            // Otherwise errors are ignored by `encoding/json`
            // https://github.com/sirupsen/logrus/issues/137
            data[k] = v.Error()
        default:
            data[k] = v
        }
    }

    if f.DataKey != "" {
        newData := make(Fields, 4)
        newData[f.DataKey] = data
        data = newData
    }

    prefixFieldClashes(data, f.FieldMap, entry.HasCaller())

    timestampFormat := f.TimestampFormat
    if timestampFormat == "" {
        timestampFormat = defaultTimestampFormat
    }

    if entry.err != "" {
        data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err
    }
    if !f.DisableTimestamp {
        data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
    }
    data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
    data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
    if entry.HasCaller() {
        funcVal := entry.Caller.Function
        fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
        if f.CallerPrettyfier != nil {
            funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
        }
        if funcVal != "" {
            data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
        }
        if fileVal != "" {
            data[f.FieldMap.resolve(FieldKeyFile)] = fileVal
        }
    }

    var b *bytes.Buffer
    if entry.Buffer != nil {
        b = entry.Buffer
    } else {
        b = &bytes.Buffer{}
    }

    encoder := json.NewEncoder(b) //使用Json库序列化
    encoder.SetEscapeHTML(!f.DisableHTMLEscape)
    if f.PrettyPrint {
        encoder.SetIndent("", "  ")
    }
    if err := encoder.Encode(data); err != nil {
        return nil, fmt.Errorf("failed to marshal fields to JSON, %w", err)
    }

    return b.Bytes(), nil
}

3. Infof

和Info底层一致,上层使用fmt.Sprintf格式化。

func (logger *Logger) Infof(format string, args ...interface{}) {
    logger.Logf(InfoLevel, format, args...)
}

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)
    }
}

func (entry *Entry) Logf(level Level, format string, args ...interface{}) {
    if entry.Logger.IsLevelEnabled(level) {
        entry.Log(level, fmt.Sprintf(format, args...))
    }
}

小结

和zap库比较

  1. logrus 对外提供的接口相对比较零散而且如果日志输出到文件时,文件管理还需要自己做。zap提供了一些标准的接口,通过配置来管理,文件只需要提供文件名即可,不用另外自己管理。
  2. logrus的json格式使用的标准库json. zap的json格式是自己封装的,性能比json库强。
  3. logrus 使用sync.Pool时,如果没有Get到会自己生成,zap并没有。示例如下:
    logrus:

    func (logger *Logger) newEntry() *Entry {
     entry, ok := logger.entryPool.Get().(*Entry)
     if ok {
         return entry
     }
     return NewEntry(logger)
    }

白沙云影
1 声望2 粉丝

一个专注于voip的频道