diff --git a/daemon/logger/loggerutils/logfile.go b/daemon/logger/loggerutils/logfile.go index 2f6e7381df..0f0e8f7bbe 100644 --- a/daemon/logger/loggerutils/logfile.go +++ b/daemon/logger/loggerutils/logfile.go @@ -175,8 +175,11 @@ func (w *LogFile) checkCapacityAndRotate() error { w.rotateMu.Lock() fname := w.f.Name() if err := w.f.Close(); err != nil { - w.rotateMu.Unlock() - return errors.Wrap(err, "error closing file") + // if there was an error during a prior rotate, the file could already be closed + if !errors.Is(err, os.ErrClosed) { + w.rotateMu.Unlock() + return errors.Wrap(err, "error closing file") + } } if err := rotate(fname, w.maxFiles, w.compress); err != nil { w.rotateMu.Unlock() diff --git a/daemon/logger/loggerutils/logfile_test.go b/daemon/logger/loggerutils/logfile_test.go index ae3a74ced7..d7ff34e242 100644 --- a/daemon/logger/loggerutils/logfile_test.go +++ b/daemon/logger/loggerutils/logfile_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/docker/docker/daemon/logger" + "github.com/docker/docker/pkg/pubsub" "github.com/docker/docker/pkg/tailfile" "gotest.tools/assert" ) @@ -201,3 +202,66 @@ func TestFollowLogsProducerGone(t *testing.T) { default: } } + +func TestCheckCapacityAndRotate(t *testing.T) { + dir, err := ioutil.TempDir("", t.Name()) + assert.NilError(t, err) + defer os.RemoveAll(dir) + + f, err := ioutil.TempFile(dir, "log") + assert.NilError(t, err) + + l := &LogFile{ + f: f, + capacity: 5, + maxFiles: 3, + compress: true, + notifyRotate: pubsub.NewPublisher(0, 1), + perms: 0600, + marshal: func(msg *logger.Message) ([]byte, error) { + return msg.Line, nil + }, + } + defer l.Close() + + assert.NilError(t, l.WriteLogEntry(&logger.Message{Line: []byte("hello world!")})) + + dStringer := dirStringer{dir} + + _, err = os.Stat(f.Name() + ".1") + assert.Assert(t, os.IsNotExist(err), dStringer) + + assert.NilError(t, l.WriteLogEntry(&logger.Message{Line: []byte("hello world!")})) + _, err = os.Stat(f.Name() + ".1") + assert.NilError(t, err, dStringer) + + assert.NilError(t, l.WriteLogEntry(&logger.Message{Line: []byte("hello world!")})) + _, err = os.Stat(f.Name() + ".1") + assert.NilError(t, err, dStringer) + _, err = os.Stat(f.Name() + ".2.gz") + assert.NilError(t, err, dStringer) + + // Now let's simulate a failed rotation where the file was able to be closed but something else happened elsewhere + // down the line. + // We want to make sure that we can recover in the case that `l.f` was closed while attempting a rotation. + l.f.Close() + assert.NilError(t, l.WriteLogEntry(&logger.Message{Line: []byte("hello world!")})) +} + +type dirStringer struct { + d string +} + +func (d dirStringer) String() string { + ls, err := ioutil.ReadDir(d.d) + if err != nil { + return "" + } + var s strings.Builder + s.WriteString("\n") + + for _, fi := range ls { + s.WriteString(fi.Name() + "\n") + } + return s.String() +}