diff --git a/daemon/logger/bufferlog/bufferlog.go b/daemon/logger/bufferlog/bufferlog.go new file mode 100644 index 0000000000..1e15657577 --- /dev/null +++ b/daemon/logger/bufferlog/bufferlog.go @@ -0,0 +1,92 @@ +package bufferlog + +import ( + "github.com/alibaba/pouch/daemon/logger" + rb "github.com/alibaba/pouch/pkg/ringbuffer" + + "github.com/docker/go-units" + "github.com/sirupsen/logrus" +) + +type BufferLog struct { + ringBuffer *rb.RingBuffer + logger logger.LogDriver +} + +// NewBufferLog return a new BufferLog. +func NewBufferLog(logDriver logger.LogDriver, info logger.Info) (logger.LogDriver, error) { + bufferSize := int64(-1) + // translate the max buffer size if not nil + if s, ok := info.LogConfig["max-buffer-size"]; ok { + var err error + bufferSize, err = units.RAMInBytes(s) + if err != nil { + return nil, err + } + } + + bl := &BufferLog{ + logger: logDriver, + ringBuffer: rb.New(int(bufferSize)), + } + + // use a goroutine to write logs continuously with specified log driver + go bl.run() + return bl, nil +} + +// Name return the log driver's name. +func (bl *BufferLog) Name() string { + return bl.logger.Name() +} + +// WriteLogMessage will write the LogMessage to the ringBuffer. +func (bl *BufferLog) WriteLogMessage(msg *logger.LogMessage) error { + covered, err := bl.ringBuffer.Push(msg) + if covered { + logrus.Warnf("data coverage occurs: %v", msg) + } + return err +} + +// Close close the ringBuffer and drain the messages. +func (bl *BufferLog) Close() error { + bl.ringBuffer.Close() + var hasErr bool + for _, msg := range bl.ringBuffer.Drain() { + msgTemp, ok := msg.(*logger.LogMessage) + if !ok { + // TODO anything else should be done? + logrus.Warnf("failed to do type assertion when closing: %v", msg) + continue + } + if hasErr { + bl.ringBuffer.Push(msgTemp) + continue + } + if err := bl.logger.WriteLogMessage(msgTemp); err != nil { + logrus.Warnf("failed to write log %v when closing with log driver %s", msgTemp, bl.logger.Name()) + hasErr = true + } + } + + return bl.logger.Close() +} + +// write logs continuously with specified log driver from ringBuffer. +func (bl *BufferLog) run() { + for { + msg, err := bl.ringBuffer.Pop() + if err != nil { + return + } + msgTemp, ok := msg.(*logger.LogMessage) + if !ok { + logrus.Warnf("failed to do type assertion: %v", msg) + return + } + if err := bl.logger.WriteLogMessage(msgTemp); err != nil { + logrus.Warnf("failed to write log %v with log driver %s", msgTemp, bl.logger.Name()) + } + } +} diff --git a/daemon/mgr/container_logger.go b/daemon/mgr/container_logger.go index 22c3debc89..75ca936e6b 100644 --- a/daemon/mgr/container_logger.go +++ b/daemon/mgr/container_logger.go @@ -5,13 +5,22 @@ import ( "github.com/alibaba/pouch/apis/types" "github.com/alibaba/pouch/daemon/logger" + "github.com/alibaba/pouch/daemon/logger/bufferlog" "github.com/alibaba/pouch/daemon/logger/jsonfile" "github.com/alibaba/pouch/daemon/logger/syslog" "github.com/sirupsen/logrus" ) +const ( + // LogNonBlocking means to use buffer to make logs non blocking. + LogNonBlocking = "non-blocking" +) + func logOptionsForContainerio(c *Container, info logger.Info) (logger.LogDriver, error) { + var ld logger.LogDriver + var err error + cfg := c.HostConfig.LogConfig if cfg == nil || cfg.LogDriver == types.LogConfigLogDriverNone { return nil, nil @@ -19,13 +28,23 @@ func logOptionsForContainerio(c *Container, info logger.Info) (logger.LogDriver, switch cfg.LogDriver { case types.LogConfigLogDriverJSONFile: - return jsonfile.Init(info) + ld, err = jsonfile.Init(info) case types.LogConfigLogDriverSyslog: - return syslog.Init(info) + ld, err = syslog.Init(info) default: logrus.Warnf("not support (%v) log driver yet", cfg.LogDriver) return nil, nil } + + if err != nil { + return nil, err + } + + if info.LogConfig["mode"] == LogNonBlocking { + return bufferlog.NewBufferLog(ld, info) + } + + return ld, err } // convContainerToLoggerInfo uses logger.Info to wrap container information.