| // 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 ( |
| "bytes" |
| "encoding/json" |
| "fmt" |
| "strconv" |
| "sync" |
| |
| "github.com/Sirupsen/logrus" |
| "github.com/docker/docker/daemon/logger" |
| "github.com/docker/docker/daemon/logger/loggerutils" |
| "github.com/docker/docker/pkg/jsonlog" |
| "github.com/docker/go-units" |
| ) |
| |
| // Name is the name of the file that the jsonlogger logs to. |
| const Name = "json-file" |
| |
| // 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 |
| } |
| |
| func init() { |
| if err := logger.RegisterLogDriver(Name, New); err != nil { |
| logrus.Fatal(err) |
| } |
| if err := logger.RegisterLogOptValidator(Name, ValidateLogOpt); err != nil { |
| logrus.Fatal(err) |
| } |
| } |
| |
| // New creates new JSONFileLogger which writes to filename passed in |
| // on given context. |
| func New(ctx logger.Context) (logger.Logger, error) { |
| var capval int64 = -1 |
| if capacity, ok := ctx.Config["max-size"]; ok { |
| var err error |
| capval, err = units.FromHumanSize(capacity) |
| if err != nil { |
| return nil, err |
| } |
| } |
| var maxFiles = 1 |
| if maxFileString, ok := ctx.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") |
| } |
| } |
| |
| writer, err := loggerutils.NewRotateFileWriter(ctx.LogPath, capval, maxFiles) |
| if err != nil { |
| return nil, err |
| } |
| |
| var extra []byte |
| if attrs := ctx.ExtraAttributes(nil); len(attrs) > 0 { |
| var err error |
| extra, err = json.Marshal(attrs) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| return &JSONFileLogger{ |
| buf: bytes.NewBuffer(nil), |
| writer: writer, |
| readers: make(map[*logger.LogWatcher]struct{}), |
| extra: extra, |
| }, nil |
| } |
| |
| // Log converts logger.Message to jsonlog.JSONLog and serializes it to file. |
| func (l *JSONFileLogger) Log(msg *logger.Message) error { |
| timestamp, err := jsonlog.FastTimeMarshalJSON(msg.Timestamp) |
| if err != nil { |
| return err |
| } |
| l.mu.Lock() |
| logline := msg.Line |
| if !msg.Partial { |
| logline = append(msg.Line, '\n') |
| } |
| err = (&jsonlog.JSONLogs{ |
| Log: logline, |
| Stream: msg.Source, |
| Created: timestamp, |
| RawAttrs: l.extra, |
| }).MarshalJSONBuf(l.buf) |
| if err != nil { |
| l.mu.Unlock() |
| return err |
| } |
| |
| l.buf.WriteByte('\n') |
| _, err = l.writer.Write(l.buf.Bytes()) |
| l.buf.Reset() |
| l.mu.Unlock() |
| |
| return err |
| } |
| |
| // 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 "labels": |
| case "env": |
| default: |
| return fmt.Errorf("unknown log opt '%s' for json-file log driver", key) |
| } |
| } |
| return nil |
| } |
| |
| // LogPath returns the location the given json logger logs to. |
| func (l *JSONFileLogger) LogPath() string { |
| return l.writer.LogPath() |
| } |
| |
| // Close closes underlying file and signals all readers to stop. |
| func (l *JSONFileLogger) Close() error { |
| l.mu.Lock() |
| err := l.writer.Close() |
| for r := range l.readers { |
| r.Close() |
| delete(l.readers, r) |
| } |
| l.mu.Unlock() |
| return err |
| } |
| |
| // Name returns name of this logger. |
| func (l *JSONFileLogger) Name() string { |
| return Name |
| } |