docker中logrus与日志原理(一)

本文基于docker-1.12.6。

    在docker中使用logrus来打印到终端,类似于fmt.Print系列函数,我们来跟踪看下它的原理。

    logrus在docker的vendor/src/github.com/Sirupsen/logrus目录以一个包的形式存在。它提供了不同的打印级别,如logrus.Debugf(), logrus.Infof(), logrus.Warnf() 等等,其中logrus.Infof()是默认的打印级别,下面我们就以logrus.Infof()入手,探讨一下它的原理。

    logrus.Infof()函数定义在vendor/src/github.com/Sirupsen/logrus/exported.go文件中,如下:

// Infof logs a message at level Info on the standard logger.
func Infof(format string, args ...interface{}) {
	std.Infof(format, args...)
}
    可以看到logrus.Info()实际上调用的是std.Infof()。这个std又是什么呢?它是一个type Logger struct结构的引用:
var (
	// std is the name of the standard logger in stdlib `log`
	std = New()		//std是type Logger struct结构的一个对象
)
    std在所有程序运行之前就通过这个New()函数得以创建,New()定义在vendor/src/github.com/Sirupsen/logrus/logger.go文件:
func New() *Logger {
	return &Logger{
		Out:       os.Stderr,
		Formatter: new(TextFormatter),
		Hooks:     make(LevelHooks),
		Level:     InfoLevel,
	}
}

    上面的New()创建并初始化一个Logger对象:

    Logger.Out初始化为os.Stderr,即系统标准出错; Logger.Formatter初始化为一个TextFormatter对象;Logger.Hooks初始化一个LevelHooks接口对象,用于容器的日志记录;Logger.Level 使用Infolevel级别来初始化,也即使默认打印级别。

    同样在此文件中,又为 Logger结构实现了Infof()方法:
func (logger *Logger) Infof(format string, args ...interface{}) {
	if logger.Level >= InfoLevel {
		NewEntry(logger).Infof(format, args...)
	}
}

    Logger.Infof()先判断日志的级别,如果级别大于InfoLevel就:1) 先通过NewEntry(logger)创建一个type Entry struct对象;2) 再调用Entry的方法Entry.Infof().

    type Entry struct结构以及它的方法Entry.Infof()定义在vendor/src/github.com/Sirupsen/logrus/entry.go文件中:

func (entry *Entry) Infof(format string, args ...interface{}) {
	if entry.Logger.Level >= InfoLevel {
		entry.Info(fmt.Sprintf(format, args...))
	}
}

func (entry *Entry) Info(args ...interface{}) {
	if entry.Logger.Level >= InfoLevel {
		entry.log(InfoLevel, fmt.Sprint(args...))
	}
}
    可以看到,调用链从Entry.Infof()-->走到-->Entry.Info(),最终走到Entry.log()。这个Entry.log()就是最终的logrus.Infof()实现打印和日志记录的函数。下面我们就来分析一下:

func (entry Entry) log(level Level, msg string) {
	entry.Time = time.Now()
	entry.Level = level
	entry.Message = msg

	if err := entry.Logger.Hooks.Fire(level, &entry); err != nil {
		entry.Logger.mu.Lock()
		fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
		entry.Logger.mu.Unlock()
	}

	reader, err := entry.Reader()
	if err != nil {
		entry.Logger.mu.Lock()
		fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
		entry.Logger.mu.Unlock()
	}

	entry.Logger.mu.Lock()
	defer entry.Logger.mu.Unlock()

	_, err = io.Copy(entry.Logger.Out, reader)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
	}

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

    这个log()函数就是容器中调试信息与日志记录的基本骨架,具体做了下面一些事情:

  1 首先调用entry.Logger.Hooks.Fire()来完成 已注册到entry.Logger.Hooks[]中的 所有日志driver的日志记录工作;

// Fire all the hooks for the passed level. Used by `entry.log` to fire
// appropriate hooks for a log entry.
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
}

    这个函数在vendor/src/github.com/Sirupsen/logrus/hooks.go文件中,具体的日志记录实现将在以后再去分析。

  2 调用reader, err := entry.Reader() 来格式化将要打印的信息:

     vendor/src/github.com/Sirupsen/logrus/entry.go

// Returns a reader for the entry, which is a proxy to the formatter.
func (entry *Entry) Reader() (*bytes.Buffer, error) {
	serialized, err := entry.Logger.Formatter.Format(entry)
	return bytes.NewBuffer(serialized), err
}

    其中entry.Logger.Fromatter在前面初始化为TextFormatter对象,这里就调用TextFormatter.Fromat()来进行格式化,其实现在vendor/src/github.com/Sirupsen/logrus/text_formatter.go文件中。

  3  将格式化后的信息写到etnry.Logger.Out对应的文件。

    具体来讲是通过_, err = io.Copy(entry.Logger.Out, reader)这个函数将reader中的信息拷贝到entry.Logger.Out,前面已经将entry.Logger.Out初始化为os.Stderr;而reader则是第2步创建的butes.Buffer结构。

你可能感兴趣的:(容器)