dotcloud/docker

View on GitHub
daemon/logger/jsonfilelog/jsonfilelog.go

Summary

Maintainability
A
0 mins
Test Coverage
// Package jsonfilelog provides the default Logger implementation for
// Docker logging. This logger logs to files on the host server in the
// JSON format.
package jsonfilelog // import "github.com/docker/docker/daemon/logger/jsonfilelog"

import (
    "bytes"
    "encoding/json"
    "fmt"
    "strconv"
    "sync"

    "github.com/docker/docker/daemon/logger"
    "github.com/docker/docker/daemon/logger/jsonfilelog/jsonlog"
    "github.com/docker/docker/daemon/logger/loggerutils"
    units "github.com/docker/go-units"
    "github.com/pkg/errors"
)

// Name is the name of the file that the jsonlogger logs to.
const Name = "json-file"

// Every buffer will have to store the same constant json structure with the message
// len(`{"log":"","stream:"stdout","time":"2000-01-01T00:00:00.000000000Z"}\n`) = 68.
// So let's start with a buffer bigger than this.
const initialBufSize = 256

var buffersPool = sync.Pool{New: func() interface{} { return bytes.NewBuffer(make([]byte, 0, initialBufSize)) }}

// JSONFileLogger is Logger implementation for default Docker logging.
type JSONFileLogger struct {
    writer *loggerutils.LogFile
    tag    string // tag values requested by the user to log
    extra  json.RawMessage
}

func init() {
    if err := logger.RegisterLogDriver(Name, New); err != nil {
        panic(err)
    }
    if err := logger.RegisterLogOptValidator(Name, ValidateLogOpt); err != nil {
        panic(err)
    }
}

// New creates new JSONFileLogger which writes to filename passed in
// on given context.
func New(info logger.Info) (logger.Logger, error) {
    var capval int64 = -1
    if capacity, ok := info.Config["max-size"]; ok {
        var err error
        capval, err = units.FromHumanSize(capacity)
        if err != nil {
            return nil, err
        }
        if capval <= 0 {
            return nil, fmt.Errorf("max-size must be a positive number")
        }
    }
    maxFiles := 1
    if maxFileString, ok := info.Config["max-file"]; ok {
        var err error
        maxFiles, err = strconv.Atoi(maxFileString)
        if err != nil {
            return nil, err
        }
        if maxFiles < 1 {
            return nil, fmt.Errorf("max-file cannot be less than 1")
        }
    }

    var compress bool
    if compressString, ok := info.Config["compress"]; ok {
        var err error
        compress, err = strconv.ParseBool(compressString)
        if err != nil {
            return nil, err
        }
        if compress && (maxFiles == 1 || capval == -1) {
            return nil, fmt.Errorf("compress cannot be true when max-file is less than 2 or max-size is not set")
        }
    }

    attrs, err := info.ExtraAttributes(nil)
    if err != nil {
        return nil, err
    }

    // no default template. only use a tag if the user asked for it
    tag, err := loggerutils.ParseLogTag(info, "")
    if err != nil {
        return nil, err
    }
    if tag != "" {
        attrs["tag"] = tag
    }

    var extra json.RawMessage
    if len(attrs) > 0 {
        var err error
        extra, err = json.Marshal(attrs)
        if err != nil {
            return nil, err
        }
    }

    writer, err := loggerutils.NewLogFile(info.LogPath, capval, maxFiles, compress, decodeFunc, 0o640, getTailReader)
    if err != nil {
        return nil, err
    }

    return &JSONFileLogger{
        writer: writer,
        tag:    tag,
        extra:  extra,
    }, nil
}

// Log converts logger.Message to jsonlog.JSONLog and serializes it to file.
func (l *JSONFileLogger) Log(msg *logger.Message) error {
    buf := buffersPool.Get().(*bytes.Buffer)
    buf.Reset()
    defer buffersPool.Put(buf)

    timestamp := msg.Timestamp
    err := marshalMessage(msg, l.extra, buf)
    logger.PutMessage(msg)

    if err != nil {
        return err
    }

    return l.writer.WriteLogEntry(timestamp, buf.Bytes())
}

func marshalMessage(msg *logger.Message, extra json.RawMessage, buf *bytes.Buffer) error {
    logLine := msg.Line
    if msg.PLogMetaData == nil || (msg.PLogMetaData != nil && msg.PLogMetaData.Last) {
        logLine = append(msg.Line, '\n')
    }
    err := (&jsonlog.JSONLogs{
        Log:      logLine,
        Stream:   msg.Source,
        Created:  msg.Timestamp,
        RawAttrs: extra,
    }).MarshalJSONBuf(buf)
    if err != nil {
        return errors.Wrap(err, "error writing log message to buffer")
    }
    err = buf.WriteByte('\n')
    return errors.Wrap(err, "error finalizing log buffer")
}

// ValidateLogOpt looks for json specific log options max-file & max-size.
func ValidateLogOpt(cfg map[string]string) error {
    for key := range cfg {
        switch key {
        case "max-file":
        case "max-size":
        case "compress":
        case "labels":
        case "labels-regex":
        case "env":
        case "env-regex":
        case "tag":
        default:
            return fmt.Errorf("unknown log opt '%s' for json-file log driver", key)
        }
    }
    return nil
}

// Close closes underlying file and signals all the readers
// that the logs producer is gone.
func (l *JSONFileLogger) Close() error {
    return l.writer.Close()
}

// Name returns name of this logger.
func (l *JSONFileLogger) Name() string {
    return Name
}