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