Docker提供了多种日志机制以动态记录容器运行过程中的痕迹。Docker通过"--log-driver"标志来指定使用哪种类型的日志驱动。在docker-1.12.6版本中合法的log driver包括json-file,journald,syslog等等,其中默认使用的是json-file。
这里我们就以json-file为例子,来探索一下docker中的日志机制的运转过程。
Docker充分利用了golang中提供的面向对象的机理,将各个不同的log driver的创建方法代理给一个工厂来执行,这样调用者就无需了解各个log driver的创建细节,只需要简单的知道自己使用的log driver的名字即可。
1 相关结构和变量
1.1 日志驱动的工厂结构
日志驱动的工厂结构类型用于屏蔽具体的日志驱动实现,为日志驱动的使用者提供一个统一平台。
######daemon/logger/factory.gotype logdriverFactory struct {
registry map[string]Creator //logdriver的构造方法
optValidator map[string]LogOptValidator //log option检查方法
m sync.Mutex
}
这个logdriverFactory中有三个成员:registry,optValidator和互斥锁m。
其中registry是map[string]Creator类型,它以log driver的名字作为key,以log driver的构造方法作为value;
与registry类似,optValidator也是map类型,只不过map的key对应的不是构造函数而是option检查函数。
1.2 log driver工厂实例
在docker中,定义了一个工厂实例factory:
// global factory instance
var factory = &logdriverFactory{registry: make(map[string]Creator), optValidator: make(map[string]LogOptValidator)}
系统中所有log driver的构造方法都会注册到这个实例factory中。
1.3 json-file 日志驱动与构造器
在docker中通过logger.Logger这个结构来表示所有的log driver,不过它只是一个接口类型,具体的日志驱动实现了根据自己的特点对logger.Logger提供的接口做了不同的实现。
// Logger is the interface for docker logging drivers.
type Logger interface {
Log(*Message) error
Name() string
Close() error
}
而json-file定义了自己的logger.Logger:
######daemon/logger/jsonfilelog/jsonfilelog.go
// JSONFileLogger is Logger implementation for default Docker logging.
type JSONFileLogger struct {
buf *bytes.Buffer
writer *loggerutils.RotateFileWriter
mu sync.Mutex
readers map[*logger.LogWatcher]struct{} // stores the active log followers
extra []byte // json-encoded extra attributes
}
JSONFileLogger要称为一个logger.Logger接口,必须实现Logger提供的Log()、Name()、Close()三个函数。其中最重要的就是Log()函数,它实现了具体的日志记录方法,这个后面涉及到。
1.4 日志驱动名字
到此我们已经了解到日志驱动的注册都由factory屏蔽底层实现原理来统一安排,但是要使用不同的日志驱动如何来鉴别呢?答案就是日志驱动的“名字”。
每个日志驱动都预定义了表示自己的 string类型的名字,这些名字都定义在各自的包中。这里的json日志驱动的名字为"json-file",定义如下:
// Name is the name of the file that the jsonlogger logs to.
const Name = "json-file"
即json日志驱动的名字就叫"json-file",使用json日志时就用此名字来注册。
2 json-file 日志驱动构造器的注册
前面已经了解到各个日志驱动的实例通过自己的构造方法来构造,而不同日志驱动的构造方法都注册到factory中以便为调用者提供统一的接口。下面来看下json-file构造器的注册。
json-file的构造器是在jsonfilelog包的init()函数中注册的:
func init() {
if err := logger.RegisterLogDriver(Name, New); err != nil {
logrus.Fatal(err)
}
if err := logger.RegisterLogOptValidator(Name, ValidateLogOpt); err != nil {
logrus.Fatal(err)
}
}
我们知道golang中包的init()函数比main()函数要先执行,因而在实际使用factory创建日志驱动之前,json-file就在自己的包中完成了初始化。jsonfilelog.init()完成两个事情:1) 注册日志驱动的构造方法;2) 注册日志参数检查器。下面我们来依次分析。
2.1 注册构造方法
这一部分由logger.RegisterLogDriver(Name, New)来完成。我们来解释一下涉及到几个变量和参数:
1) logger.RegisterLogDriver()函数定义在github.com/docker/docker/daemon/logger包中;
2) Name在1.1节讲过,其值为"json-file";
3) New是json日志驱动实现了Creator接口的构造方法。
我们看下整个logger包的RegisterLogDriver()函数的细节:
// RegisterLogDriver registers the given logging driver builder with given logging
// driver name.
func RegisterLogDriver(name string, c Creator) error {
return factory.register(name, c)
}
看到RegisterLogDriver()函数是转手交给无所不能的factory处理:
func (lf *logdriverFactory) register(name string, c Creator) error {
if lf.driverRegistered(name) {
return fmt.Errorf("logger: log driver named '%s' is already registered", name)
}
lf.m.Lock()
lf.registry[name] = c
lf.m.Unlock()
return nil
}
factory.register()函数首先判断"json-file"这个名字是否已经注册;然后再将名字 "json-file"作为key,构造方法jsonfilelog.New()作为value放到factory.registry[]这个map中。
到此,json日志驱动的构造方法就已经注册到factory中。后续有使用json日志驱动时,就可以通过"json-file"这个名字找到json日志驱动的构造方法jsonfilelog.New();进而通过jsonfilelog.New()创建出实现了logger.Logger接口的结构JSONFileLogger,然后就可以JSONFileLogger提供的方法来记录json日志了。