diff --git a/.changelog/11386.txt b/.changelog/11386.txt new file mode 100644 index 000000000..285623860 --- /dev/null +++ b/.changelog/11386.txt @@ -0,0 +1,3 @@ +```release-note:bug +agent: Fixed an issue that could cause previous log lines to be overwritten +``` diff --git a/command/agent/log_file.go b/command/agent/log_file.go index da660c4ea..7bba86a31 100644 --- a/command/agent/log_file.go +++ b/command/agent/log_file.go @@ -62,18 +62,23 @@ func (l *logFile) fileNamePattern() string { } func (l *logFile) openNew() error { - createTime := now() newfilePath := filepath.Join(l.logPath, l.fileName) - // Try creating a file. We truncate the file because we are the only authority to write the logs - filePointer, err := os.OpenFile(newfilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0640) + + // Try creating or opening the active log file. Since the active log file + // always has the same name, append log entries to prevent overwriting + // previous log data. + filePointer, err := os.OpenFile(newfilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0640) + if err != nil { + return err + } + stat, err := filePointer.Stat() if err != nil { return err } l.FileInfo = filePointer - // New file, new bytes tracker, new creation time :) - l.LastCreated = createTime - l.BytesWritten = 0 + l.BytesWritten = stat.Size() + l.LastCreated = l.createTime(stat) return nil } diff --git a/command/agent/log_file_bsd.go b/command/agent/log_file_bsd.go new file mode 100644 index 000000000..ae6ec23e1 --- /dev/null +++ b/command/agent/log_file_bsd.go @@ -0,0 +1,16 @@ +//go:build darwin || freebsd || netbsd || openbsd +// +build darwin freebsd netbsd openbsd + +package agent + +import ( + "os" + "syscall" + "time" +) + +func (l *logFile) createTime(stat os.FileInfo) time.Time { + stat_t := stat.Sys().(*syscall.Stat_t) + createTime := stat_t.Ctimespec + return time.Unix(createTime.Sec, createTime.Nsec) +} diff --git a/command/agent/log_file_linux.go b/command/agent/log_file_linux.go new file mode 100644 index 000000000..2f873b90f --- /dev/null +++ b/command/agent/log_file_linux.go @@ -0,0 +1,17 @@ +//go:build dragonfly || linux || solaris +// +build dragonfly linux solaris + +package agent + +import ( + "os" + "syscall" + "time" +) + +func (l *logFile) createTime(stat os.FileInfo) time.Time { + stat_t := stat.Sys().(*syscall.Stat_t) + createTime := stat_t.Ctim + // Sec and Nsec are int32 in 32-bit architectures. + return time.Unix(int64(createTime.Sec), int64(createTime.Nsec)) //nolint:unconvert +} diff --git a/command/agent/log_file_test.go b/command/agent/log_file_test.go index 402560ac0..8bcc52a00 100644 --- a/command/agent/log_file_test.go +++ b/command/agent/log_file_test.go @@ -50,13 +50,33 @@ func TestLogFile_openNew(t *testing.T) { require.NoError(err) defer os.Remove(tempDir) - logFile := logFile{fileName: testFileName, logPath: tempDir, duration: testDuration} + filt := LevelFilter() + filt.MinLevel = logutils.LogLevel("INFO") + logFile := logFile{ + logFilter: filt, + fileName: testFileName, + logPath: tempDir, + MaxBytes: testBytes, + duration: 24 * time.Hour, + } require.NoError(logFile.openNew()) _, err = ioutil.ReadFile(logFile.FileInfo.Name()) require.NoError(err) require.Equal(logFile.FileInfo.Name(), filepath.Join(tempDir, testFileName)) + + // Check if create time and bytes written are kept when opening the active + // log file again. + bytesWritten, err := logFile.Write([]byte("test")) + require.NoError(err) + + time.Sleep(2 * time.Second) + require.NoError(logFile.openNew()) + + timeDelta := time.Now().Sub(logFile.LastCreated) + require.GreaterOrEqual(timeDelta, 2*time.Second) + require.Equal(logFile.BytesWritten, int64(bytesWritten)) } func TestLogFile_byteRotation(t *testing.T) { diff --git a/command/agent/log_file_windows.go b/command/agent/log_file_windows.go new file mode 100644 index 000000000..ea6a691f2 --- /dev/null +++ b/command/agent/log_file_windows.go @@ -0,0 +1,14 @@ +package agent + +import ( + "os" + "time" +) + +func (l *logFile) createTime(stat os.FileInfo) time.Time { + // Use `ModTime` as an approximation if the exact create time is not + // available. + // On Windows, the file create time is not updated after the active log + // rotates, so use `ModTime` as an approximation as well. + return stat.ModTime() +}