moby--moby/daemon/logger/jsonfilelog/jsonfilelog.go

200 lines
4.2 KiB
Go
Raw Normal View History

package jsonfilelog
import (
"bytes"
"fmt"
"io"
"os"
"strconv"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/logger"
"github.com/docker/docker/pkg/jsonlog"
"github.com/docker/docker/pkg/timeutils"
"github.com/docker/docker/pkg/units"
)
const (
Name = "json-file"
)
// JSONFileLogger is Logger implementation for default docker logging:
// JSON objects to file
type JSONFileLogger struct {
buf *bytes.Buffer
f *os.File // store for closing
mu sync.Mutex // protects buffer
capacity int64 //maximum size of each file
n int //maximum number of files
ctx logger.Context
}
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
func New(ctx logger.Context) (logger.Logger, error) {
log, err := os.OpenFile(ctx.LogPath, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
if err != nil {
return nil, err
}
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 int = 1
if maxFileString, ok := ctx.Config["max-file"]; ok {
maxFiles, err = strconv.Atoi(maxFileString)
if err != nil {
return nil, err
}
if maxFiles < 1 {
return nil, fmt.Errorf("max-files cannot be less than 1.")
}
}
return &JSONFileLogger{
f: log,
buf: bytes.NewBuffer(nil),
ctx: ctx,
capacity: capval,
n: maxFiles,
}, nil
}
// Log converts logger.Message to jsonlog.JSONLog and serializes it to file
func (l *JSONFileLogger) Log(msg *logger.Message) error {
l.mu.Lock()
defer l.mu.Unlock()
timestamp, err := timeutils.FastMarshalJSON(msg.Timestamp)
if err != nil {
return err
}
err = (&jsonlog.JSONLogBytes{Log: append(msg.Line, '\n'), Stream: msg.Source, Created: timestamp}).MarshalJSONBuf(l.buf)
if err != nil {
return err
}
l.buf.WriteByte('\n')
_, err = writeLog(l)
return err
}
func writeLog(l *JSONFileLogger) (int64, error) {
if l.capacity == -1 {
return writeToBuf(l)
}
meta, err := l.f.Stat()
if err != nil {
return -1, err
}
if meta.Size() >= l.capacity {
name := l.f.Name()
if err := l.f.Close(); err != nil {
return -1, err
}
if err := rotate(name, l.n); err != nil {
return -1, err
}
file, err := os.OpenFile(name, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
if err != nil {
return -1, err
}
l.f = file
}
return writeToBuf(l)
}
func writeToBuf(l *JSONFileLogger) (int64, error) {
i, err := l.buf.WriteTo(l.f)
if err != nil {
l.buf = bytes.NewBuffer(nil)
}
return i, err
}
func rotate(name string, n int) error {
if n < 2 {
return nil
}
for i := n - 1; i > 1; i-- {
oldFile := name + "." + strconv.Itoa(i)
replacingFile := name + "." + strconv.Itoa(i-1)
if err := backup(oldFile, replacingFile); err != nil {
return err
}
}
if err := backup(name+".1", name); err != nil {
return err
}
return nil
}
func backup(old, curr string) error {
if _, err := os.Stat(old); !os.IsNotExist(err) {
err := os.Remove(old)
if err != nil {
return err
}
}
if _, err := os.Stat(curr); os.IsNotExist(err) {
if f, err := os.Create(curr); err != nil {
return err
} else {
f.Close()
}
}
return os.Rename(curr, old)
}
func ValidateLogOpt(cfg map[string]string) error {
for key := range cfg {
switch key {
case "max-file":
case "max-size":
default:
return fmt.Errorf("unknown log opt '%s' for json-file log driver", key)
}
}
return nil
}
func (l *JSONFileLogger) ReadLog(args ...string) (io.Reader, error) {
pth := l.ctx.LogPath
if len(args) > 0 {
//check if args[0] is an integer index
index, err := strconv.ParseInt(args[0], 0, 0)
if err != nil {
return nil, err
}
if index > 0 {
pth = pth + "." + args[0]
}
}
return os.Open(pth)
}
func (l *JSONFileLogger) LogPath() string {
return l.ctx.LogPath
}
// Close closes underlying file
func (l *JSONFileLogger) Close() error {
return l.f.Close()
}
// Name returns name of this logger
func (l *JSONFileLogger) Name() string {
return Name
}