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