blob: 4ccf5d7cd29f84a722f48b86ed03601c8df834d5 [file] [edit]
package local
import (
"encoding/binary"
"io"
"math/bits"
"sync"
"time"
"github.com/moby/moby/v2/daemon/logger"
"github.com/moby/moby/v2/daemon/logger/internal/logdriver"
"github.com/moby/moby/v2/daemon/logger/loggerutils"
"github.com/moby/moby/v2/daemon/server/backend"
"github.com/moby/moby/v2/errdefs"
"github.com/pkg/errors"
)
const (
// Name is the name of the driver
Name = "local"
encodeBinaryLen = 4
initialBufSize = 2048
maxDecodeRetry = 20000
defaultMaxFileSize int64 = 20 * 1024 * 1024
defaultMaxFileCount = 5
defaultCompressLogs = true
)
var buffersPool = sync.Pool{New: func() any {
b := make([]byte, initialBufSize)
return &b
}}
// LogOptKeys are the keys names used for log opts passed in to initialize the driver.
var LogOptKeys = map[string]bool{
"max-file": true,
"max-size": true,
"compress": true,
}
// ValidateLogOpt looks for log driver specific options.
func ValidateLogOpt(cfg map[string]string) error {
for key := range cfg {
if !LogOptKeys[key] {
return errors.Errorf("unknown log opt '%s' for log driver %s", key, Name)
}
}
return nil
}
type driver struct {
logfile *loggerutils.LogFile
}
// New creates a new local logger
// You must provide the `LogPath` in the passed in info argument, this is the file path that logs are written to.
func New(info logger.Info) (logger.Logger, error) {
if info.LogPath == "" {
return nil, errdefs.System(errors.New("log path is missing -- this is a bug and should not happen"))
}
cfg, err := newConfig(info.Config)
if err != nil {
return nil, errdefs.InvalidParameter(err)
}
lf, err := loggerutils.NewLogFile(info.LogPath, cfg.MaxFileSize, cfg.MaxFileCount, !cfg.DisableCompression, decodeFunc, 0o640, getTailReader)
if err != nil {
return nil, err
}
return &driver{
logfile: lf,
}, nil
}
func marshal(m *logger.Message, buffer *[]byte) error {
var proto logdriver.LogEntry
var md logdriver.PartialLogEntryMetadata
messageToProto(m, &proto, &md)
protoSize := proto.Size()
writeLen := protoSize + (2 * encodeBinaryLen) // + len(messageDelimiter)
buf := *buffer
if writeLen > cap(buf) {
// If we already need to reallocate the buffer, make it larger to be more reusable.
// Round to the next power of two.
capacity := 1 << (bits.Len(uint(writeLen)) + 1)
buf = make([]byte, writeLen, capacity)
} else {
buf = buf[:writeLen]
}
*buffer = buf
binary.BigEndian.PutUint32(buf[:encodeBinaryLen], uint32(protoSize))
n, err := proto.MarshalTo(buf[encodeBinaryLen:writeLen])
if err != nil {
return errors.Wrap(err, "error marshaling log entry")
}
if n+(encodeBinaryLen*2) != writeLen {
return io.ErrShortWrite
}
binary.BigEndian.PutUint32(buf[writeLen-encodeBinaryLen:writeLen], uint32(protoSize))
return nil
}
func (d *driver) Name() string {
return Name
}
func (d *driver) Log(msg *logger.Message) error {
buf := buffersPool.Get().(*[]byte)
defer buffersPool.Put(buf)
timestamp := msg.Timestamp
err := marshal(msg, buf)
logger.PutMessage(msg)
if err != nil {
return errors.Wrap(err, "error marshalling logger.Message")
}
return d.logfile.WriteLogEntry(timestamp, *buf)
}
func (d *driver) Close() error {
return d.logfile.Close()
}
func messageToProto(msg *logger.Message, proto *logdriver.LogEntry, partial *logdriver.PartialLogEntryMetadata) {
proto.Source = msg.Source
proto.TimeNano = msg.Timestamp.UnixNano()
proto.Line = append(proto.Line[:0], msg.Line...)
proto.Partial = msg.PLogMetaData != nil
if proto.Partial {
partial.Ordinal = int32(msg.PLogMetaData.Ordinal)
partial.Last = msg.PLogMetaData.Last
partial.Id = msg.PLogMetaData.ID
proto.PartialLogMetadata = partial
} else {
proto.PartialLogMetadata = nil
}
}
func protoToMessage(proto *logdriver.LogEntry) *logger.Message {
msg := &logger.Message{
Source: proto.Source,
Timestamp: time.Unix(0, proto.TimeNano).UTC(),
}
if proto.Partial {
msg.PLogMetaData = &backend.PartialLogMetaData{
Last: proto.GetPartialLogMetadata().GetLast(),
ID: proto.GetPartialLogMetadata().GetId(),
Ordinal: int(proto.GetPartialLogMetadata().GetOrdinal()),
}
}
msg.Line = append(msg.Line[:0], proto.Line...)
return msg
}
func resetProto(proto *logdriver.LogEntry) {
proto.Source = ""
proto.Line = proto.Line[:0]
proto.TimeNano = 0
proto.Partial = false
if proto.PartialLogMetadata != nil {
proto.PartialLogMetadata.Id = ""
proto.PartialLogMetadata.Last = false
proto.PartialLogMetadata.Ordinal = 0
}
proto.PartialLogMetadata = nil
}