Browse Source

Refactor log file writer

Make the `*RotateFileWriter` specifically about writing
`logger.Message`'s, which is what it's used for.

This allows for future changes where the log writer can cache details
about log entries such as (e.g.) the timestamps included in a particular
log file, which can be used to optimize reads.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
Brian Goff 8 years ago
parent
commit
52d82b4fbc

+ 15 - 23
daemon/logger/jsonfilelog/jsonfilelog.go

@@ -7,7 +7,6 @@ import (
 	"bytes"
 	"bytes"
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
-	"io"
 	"strconv"
 	"strconv"
 	"sync"
 	"sync"
 
 
@@ -24,10 +23,7 @@ const Name = "json-file"
 
 
 // JSONFileLogger is Logger implementation for default Docker logging.
 // JSONFileLogger is Logger implementation for default Docker logging.
 type JSONFileLogger struct {
 type JSONFileLogger struct {
-	extra []byte // json-encoded extra attributes
-
 	mu      sync.RWMutex
 	mu      sync.RWMutex
-	buf     *bytes.Buffer // avoids allocating a new buffer on each call to `Log()`
 	closed  bool
 	closed  bool
 	writer  *loggerutils.RotateFileWriter
 	writer  *loggerutils.RotateFileWriter
 	readers map[*logger.LogWatcher]struct{} // stores the active log followers
 	readers map[*logger.LogWatcher]struct{} // stores the active log followers
@@ -65,11 +61,6 @@ func New(info logger.Info) (logger.Logger, error) {
 		}
 		}
 	}
 	}
 
 
-	writer, err := loggerutils.NewRotateFileWriter(info.LogPath, capval, maxFiles)
-	if err != nil {
-		return nil, err
-	}
-
 	var extra []byte
 	var extra []byte
 	attrs, err := info.ExtraAttributes(nil)
 	attrs, err := info.ExtraAttributes(nil)
 	if err != nil {
 	if err != nil {
@@ -83,33 +74,34 @@ func New(info logger.Info) (logger.Logger, error) {
 		}
 		}
 	}
 	}
 
 
+	buf := bytes.NewBuffer(nil)
+	marshalFunc := func(msg *logger.Message) ([]byte, error) {
+		if err := marshalMessage(msg, extra, buf); err != nil {
+			return nil, err
+		}
+		b := buf.Bytes()
+		buf.Reset()
+		return b, nil
+	}
+	writer, err := loggerutils.NewRotateFileWriter(info.LogPath, capval, maxFiles, marshalFunc)
+	if err != nil {
+		return nil, err
+	}
+
 	return &JSONFileLogger{
 	return &JSONFileLogger{
-		buf:     bytes.NewBuffer(nil),
 		writer:  writer,
 		writer:  writer,
 		readers: make(map[*logger.LogWatcher]struct{}),
 		readers: make(map[*logger.LogWatcher]struct{}),
-		extra:   extra,
 	}, nil
 	}, nil
 }
 }
 
 
 // Log converts logger.Message to jsonlog.JSONLog and serializes it to file.
 // Log converts logger.Message to jsonlog.JSONLog and serializes it to file.
 func (l *JSONFileLogger) Log(msg *logger.Message) error {
 func (l *JSONFileLogger) Log(msg *logger.Message) error {
 	l.mu.Lock()
 	l.mu.Lock()
-	err := writeMessageBuf(l.writer, msg, l.extra, l.buf)
-	l.buf.Reset()
+	err := l.writer.WriteLogEntry(msg)
 	l.mu.Unlock()
 	l.mu.Unlock()
 	return err
 	return err
 }
 }
 
 
-func writeMessageBuf(w io.Writer, m *logger.Message, extra json.RawMessage, buf *bytes.Buffer) error {
-	if err := marshalMessage(m, extra, buf); err != nil {
-		logger.PutMessage(m)
-		return err
-	}
-	logger.PutMessage(m)
-	_, err := w.Write(buf.Bytes())
-	return errors.Wrap(err, "error writing log entry")
-}
-
 func marshalMessage(msg *logger.Message, extra json.RawMessage, buf *bytes.Buffer) error {
 func marshalMessage(msg *logger.Message, extra json.RawMessage, buf *bytes.Buffer) error {
 	logLine := msg.Line
 	logLine := msg.Line
 	if !msg.Partial {
 	if !msg.Partial {

+ 3 - 0
daemon/logger/logger.go

@@ -140,3 +140,6 @@ type Capability struct {
 	// Determines if a log driver can read back logs
 	// Determines if a log driver can read back logs
 	ReadLogs bool
 	ReadLogs bool
 }
 }
+
+// MarshalFunc is a func that marshals a message into an arbitrary format
+type MarshalFunc func(*Message) ([]byte, error)

+ 23 - 11
daemon/logger/loggerutils/rotatefilewriter.go

@@ -1,12 +1,13 @@
 package loggerutils
 package loggerutils
 
 
 import (
 import (
-	"errors"
 	"os"
 	"os"
 	"strconv"
 	"strconv"
 	"sync"
 	"sync"
 
 
+	"github.com/docker/docker/daemon/logger"
 	"github.com/docker/docker/pkg/pubsub"
 	"github.com/docker/docker/pkg/pubsub"
+	"github.com/pkg/errors"
 )
 )
 
 
 // RotateFileWriter is Logger implementation for default Docker logging.
 // RotateFileWriter is Logger implementation for default Docker logging.
@@ -18,10 +19,11 @@ type RotateFileWriter struct {
 	currentSize  int64 // current size of the latest file
 	currentSize  int64 // current size of the latest file
 	maxFiles     int   //maximum number of files
 	maxFiles     int   //maximum number of files
 	notifyRotate *pubsub.Publisher
 	notifyRotate *pubsub.Publisher
+	marshal      logger.MarshalFunc
 }
 }
 
 
 //NewRotateFileWriter creates new RotateFileWriter
 //NewRotateFileWriter creates new RotateFileWriter
-func NewRotateFileWriter(logPath string, capacity int64, maxFiles int) (*RotateFileWriter, error) {
+func NewRotateFileWriter(logPath string, capacity int64, maxFiles int, marshaller logger.MarshalFunc) (*RotateFileWriter, error) {
 	log, err := os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0640)
 	log, err := os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0640)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -38,27 +40,37 @@ func NewRotateFileWriter(logPath string, capacity int64, maxFiles int) (*RotateF
 		currentSize:  size,
 		currentSize:  size,
 		maxFiles:     maxFiles,
 		maxFiles:     maxFiles,
 		notifyRotate: pubsub.NewPublisher(0, 1),
 		notifyRotate: pubsub.NewPublisher(0, 1),
+		marshal:      marshaller,
 	}, nil
 	}, nil
 }
 }
 
 
-//WriteLog write log message to File
-func (w *RotateFileWriter) Write(message []byte) (int, error) {
+// WriteLogEntry writes the provided log message to the current log file.
+// This may trigger a rotation event if the max file/capacity limits are hit.
+func (w *RotateFileWriter) WriteLogEntry(msg *logger.Message) error {
+	b, err := w.marshal(msg)
+	if err != nil {
+		return errors.Wrap(err, "error marshalling log message")
+	}
+
+	logger.PutMessage(msg)
+
 	w.mu.Lock()
 	w.mu.Lock()
 	if w.closed {
 	if w.closed {
 		w.mu.Unlock()
 		w.mu.Unlock()
-		return -1, errors.New("cannot write because the output file was closed")
+		return errors.New("cannot write because the output file was closed")
 	}
 	}
+
 	if err := w.checkCapacityAndRotate(); err != nil {
 	if err := w.checkCapacityAndRotate(); err != nil {
 		w.mu.Unlock()
 		w.mu.Unlock()
-		return -1, err
+		return err
 	}
 	}
 
 
-	n, err := w.f.Write(message)
+	n, err := w.f.Write(b)
 	if err == nil {
 	if err == nil {
 		w.currentSize += int64(n)
 		w.currentSize += int64(n)
 	}
 	}
 	w.mu.Unlock()
 	w.mu.Unlock()
-	return n, err
+	return err
 }
 }
 
 
 func (w *RotateFileWriter) checkCapacityAndRotate() error {
 func (w *RotateFileWriter) checkCapacityAndRotate() error {
@@ -69,7 +81,7 @@ func (w *RotateFileWriter) checkCapacityAndRotate() error {
 	if w.currentSize >= w.capacity {
 	if w.currentSize >= w.capacity {
 		name := w.f.Name()
 		name := w.f.Name()
 		if err := w.f.Close(); err != nil {
 		if err := w.f.Close(); err != nil {
-			return err
+			return errors.Wrap(err, "error closing file")
 		}
 		}
 		if err := rotate(name, w.maxFiles); err != nil {
 		if err := rotate(name, w.maxFiles); err != nil {
 			return err
 			return err
@@ -94,12 +106,12 @@ func rotate(name string, maxFiles int) error {
 		toPath := name + "." + strconv.Itoa(i)
 		toPath := name + "." + strconv.Itoa(i)
 		fromPath := name + "." + strconv.Itoa(i-1)
 		fromPath := name + "." + strconv.Itoa(i-1)
 		if err := os.Rename(fromPath, toPath); err != nil && !os.IsNotExist(err) {
 		if err := os.Rename(fromPath, toPath); err != nil && !os.IsNotExist(err) {
-			return err
+			return errors.Wrap(err, "error rotating old log entries")
 		}
 		}
 	}
 	}
 
 
 	if err := os.Rename(name, name+".1"); err != nil && !os.IsNotExist(err) {
 	if err := os.Rename(name, name+".1"); err != nil && !os.IsNotExist(err) {
-		return err
+		return errors.Wrap(err, "error rotating current log")
 	}
 	}
 	return nil
 	return nil
 }
 }