diff --git a/logp/core_test.go b/logp/core_test.go index a881200b..f732333a 100644 --- a/logp/core_test.go +++ b/logp/core_test.go @@ -18,8 +18,12 @@ package logp import ( + "encoding/json" "io/ioutil" golog "log" + "os" + "path/filepath" + "runtime" "testing" "github.com/stretchr/testify/assert" @@ -212,3 +216,117 @@ func TestLoggingECSFields(t *testing.T) { } } } + +func TestWithFileOutput(t *testing.T) { + var tempDir1, tempDir2 string + // Because of the way logp and zap work, when the test finishes, the log + // file is still open, this creates a problem on Windows because the + // temporary directory cannot be removed if a file inside it is still + // open. + // + // To circumvent this problem on Windows we use os.MkdirTemp + // leaving it behind and delegating to the OS the responsibility + // of cleaning it up (usually on restart). + if runtime.GOOS == "windows" { + var err error + tempDir1, err = os.MkdirTemp("", t.Name()+"-*") + if err != nil { + t.Fatalf("could not create temporary directory: %s", err) + } + tempDir2, err = os.MkdirTemp("", t.Name()+"-*") + if err != nil { + t.Fatalf("could not create temporary directory: %s", err) + } + } else { + // We have no problems on Linux and Darwin, so we can rely on t.TempDir + // that will remove the files once the tests finishes. + tempDir1 = t.TempDir() + tempDir2 = t.TempDir() + } + + expectedLogMessage := "this is a log message" + expectedLogLogger := t.Name() + "-second" + + // We follow the same approach as on a Beat, first the logger + // (always global) is configured and used, then we instantiate + // a new one, secondLogger, and perform the tests on it. + loggerCfg := DefaultConfig(DefaultEnvironment) + loggerCfg.Beat = t.Name() + "-first" + loggerCfg.ToFiles = true + loggerCfg.ToStderr = false + loggerCfg.Files.Name = "test-log-file-first" + // We want a separate directory for this logger + // and we don't need to inspect it. + loggerCfg.Files.Path = tempDir1 + + // Configures the global logger with the "default" log configuration. + if err := Configure(loggerCfg); err != nil { + t.Errorf("could not initialise logger: %s", err) + } + logger := L() + + // Create a log entry just to "test" the logger + logger.Info("not the message we want") + if err := logger.Sync(); err != nil { + t.Fatalf("could not sync log file from fist logger: %s", err) + } + + // Actually clones the logger and use the "WithFileOutput" function + secondCfg := DefaultConfig(DefaultEnvironment) + secondCfg.ToFiles = true + secondCfg.ToStderr = false + secondCfg.Files.Name = "test-log-file" + secondCfg.Files.Path = tempDir2 + + // We do not call Configure here as we do not want to affect + // the global logger configuration + secondLogger := NewLogger(t.Name() + "-second") + secondLogger = secondLogger.WithOptions(zap.WrapCore(WithFileOutput(secondCfg))) + secondLogger.Info(expectedLogMessage) + if err := secondLogger.Sync(); err != nil { + t.Fatalf("could not sync log file from second logger: %s", err) + } + + // Writes again with the first logger to ensure it has not been affected + // by the new configuration on the second logger. + logger.Info("not the message we want") + if err := logger.Sync(); err != nil { + t.Fatalf("could not sync log file from fist logger: %s", err) + } + + // Find the log file. The file name gets the date added, so we list the + // directory and ensure there is only one file there. + files, err := os.ReadDir(tempDir2) + if err != nil { + t.Fatalf("could not read temporary directory '%s': %s", tempDir2, err) + } + + // If there is more than one file, list all files + // and fail the test. + if len(files) != 1 { + t.Errorf("found %d files in '%s', there must be only one", len(files), tempDir2) + t.Errorf("Files in '%s':", tempDir2) + for _, f := range files { + t.Error(f.Name()) + } + t.FailNow() + } + + logData, err := os.ReadFile(filepath.Join(tempDir2, files[0].Name())) + if err != nil { + t.Fatalf("could not read log file: %s", err) + } + + logEntry := map[string]any{} + if err := json.Unmarshal(logData, &logEntry); err != nil { + t.Fatalf("could not read log entry as JSON. Log entry: '%s'", string(logData)) + } + + // Ensure a couple of fields exist + if logEntry["log.logger"] != expectedLogLogger { + t.Fatalf("expecting 'log.logger' to be '%s', got '%s' instead", expectedLogLogger, logEntry["log.logger"]) + } + if logEntry["message"] != expectedLogMessage { + t.Fatalf("expecting 'message' to be '%s, got '%s' instead", expectedLogMessage, logEntry["message"]) + } +}