@@ -32,8 +32,10 @@ import (
3232 "time"
3333
3434 "github.com/stretchr/testify/assert"
35+ "github.com/stretchr/testify/require"
3536 "go.uber.org/zap/zapcore"
3637
38+ "github.com/elastic/elastic-agent-libs/file"
3739 "github.com/elastic/elastic-agent-libs/logp"
3840)
3941
@@ -437,3 +439,66 @@ func takeAllLogsFromPath(t *testing.T, path string) []map[string]any {
437439
438440 return entries
439441}
442+
443+ func TestLoggerRotateSymlink (t * testing.T ) {
444+ dir := t .TempDir ()
445+
446+ cfg := logp .DefaultConfig (logp .DefaultEnvironment )
447+ cfg .Beat = "logger"
448+ cfg .ToFiles = true
449+ cfg .Files .Path = dir
450+ cfg .Files .MaxBackups = 1
451+ cfg .Files .RotateOnStartup = false
452+
453+ logname := cfg .Beat
454+
455+ privateFileContents := []byte ("original contents" )
456+ privateFile := filepath .Join (dir , "private" )
457+ err := os .WriteFile (privateFile , privateFileContents , 0644 )
458+ require .NoError (t , err )
459+
460+ // Plant a symlink to the private file by guessing the log filename.
461+ guessedFilename := filepath .Join (dir , fmt .Sprintf ("%s-%s.ndjson" , logname , time .Now ().Format (file .DateFormat )))
462+ err = os .Symlink (privateFile , guessedFilename )
463+ require .NoError (t , err )
464+
465+ err = logp .Configure (cfg )
466+ require .NoError (t , err )
467+
468+ logLine := "a info message"
469+ logp .L ().Info (logLine )
470+
471+ // The file rotation should have detected the destination is a symlink and rotated before writing.
472+ rotatedFilename := filepath .Join (dir , fmt .Sprintf ("%s-%s-1.ndjson" , logname , time .Now ().Format (file .DateFormat )))
473+ assertDirContents (t , dir , filepath .Base (privateFile ), filepath .Base (guessedFilename ), filepath .Base (rotatedFilename ))
474+
475+ got , err := os .ReadFile (privateFile )
476+ require .NoError (t , err )
477+ assert .Equal (t , privateFileContents , got , "The symlink target should not have been modified" )
478+
479+ got , err = os .ReadFile (rotatedFilename )
480+ require .NoError (t , err )
481+ assert .Contains (t , string (got ), logLine , "The rotated file should contain the log message" )
482+
483+ assert .NoError (t , logp .L ().Close ())
484+
485+ // Error: TempDir RemoveAll cleanup: remove t.TempDir() The process cannot access the file because it is being used by another process.
486+ require .NoError (t , os .RemoveAll (dir ))
487+ }
488+
489+ func assertDirContents (t * testing.T , dir string , files ... string ) {
490+ t .Helper ()
491+
492+ f , err := os .Open (dir )
493+ if err != nil {
494+ t .Fatal (err )
495+ }
496+ defer f .Close ()
497+
498+ names , err := f .Readdirnames (- 1 )
499+ if err != nil {
500+ t .Fatal (err )
501+ }
502+
503+ assert .ElementsMatch (t , files , names )
504+ }
0 commit comments