package starlog import ( "os" "path/filepath" "testing" "time" ) type archiveNoop struct { interval int64 } func (archive *archiveNoop) ShouldArchiveNow(logger *StarLogger, fullpath string, info os.FileInfo) bool { return false } func (archive *archiveNoop) NextLogFilePath(logger *StarLogger, oldpath string, info os.FileInfo) string { return oldpath } func (archive *archiveNoop) ArchiveLogFilePath(logger *StarLogger, oldpath string, info os.FileInfo) string { return oldpath } func (archive *archiveNoop) Interval() int64 { return archive.interval } func (archive *archiveNoop) HookBeforArchive() func(*StarLogger, string, string, os.FileInfo) error { return nil } func (archive *archiveNoop) HookAfterArchive() func(*StarLogger, string, string, os.FileInfo) error { return nil } func (archive *archiveNoop) DoArchive() func(*StarLogger, string, string, os.FileInfo) error { return nil } func TestArchiveStopCanRestart(t *testing.T) { logger := NewStarlog(nil) logger.SetShowStd(false) logPath := filepath.Join(testBinDir(t), "archive.log") if err := SetLogFile(logPath, logger, false); err != nil { t.Fatalf("SetLogFile failed: %v", err) } defer Close(logger) archive := &archiveNoop{interval: 1} if err := StartArchive(logger, archive); err != nil { t.Fatalf("StartArchive first run failed: %v", err) } if !IsArchiveRun(logger) { t.Fatalf("archive should be running after StartArchive") } StopArchive(logger) if IsArchiveRun(logger) { t.Fatalf("archive should be stopped after StopArchive") } if err := StartArchive(logger, archive); err != nil { t.Fatalf("StartArchive second run failed: %v", err) } StopArchive(logger) } func TestArchiveZeroIntervalDoesNotFail(t *testing.T) { logger := NewStarlog(nil) logger.SetShowStd(false) logPath := filepath.Join(testBinDir(t), "archive_zero.log") if err := SetLogFile(logPath, logger, false); err != nil { t.Fatalf("SetLogFile failed: %v", err) } defer Close(logger) archive := &archiveNoop{interval: 0} if err := StartArchive(logger, archive); err != nil { t.Fatalf("StartArchive should accept zero interval: %v", err) } StopArchive(logger) } type rotateWhenNonEmptyPolicy struct{} func (policy *rotateWhenNonEmptyPolicy) ShouldRotate(info FileInfo, entry *Entry) bool { return info != nil && info.Size() > 0 } func (policy *rotateWhenNonEmptyPolicy) NextPath(current string, now time.Time) string { return current + "." + now.Format("20060102150405.000000") + ".bak" } type rotatePreferArchivePathPolicy struct{} func (policy *rotatePreferArchivePathPolicy) ShouldRotate(info FileInfo, entry *Entry) bool { return info != nil && info.Size() > 0 } func (policy *rotatePreferArchivePathPolicy) NextPath(current string, now time.Time) string { return current + "." + now.Format("20060102150405.000000") + ".next.bak" } func (policy *rotatePreferArchivePathPolicy) ArchivePath(current string, now time.Time) string { return current + "." + now.Format("20060102150405.000000") + ".archive.bak" } type rotateArchivePathFallbackPolicy struct{} func (policy *rotateArchivePathFallbackPolicy) ShouldRotate(info FileInfo, entry *Entry) bool { return info != nil && info.Size() > 0 } func (policy *rotateArchivePathFallbackPolicy) NextPath(current string, now time.Time) string { return current + "." + now.Format("20060102150405.000000") + ".nextonly.bak" } func (policy *rotateArchivePathFallbackPolicy) ArchivePath(current string, now time.Time) string { return "" } func TestStartRotatePolicyPrimaryPath(t *testing.T) { logger := NewStarlog(nil) logger.SetShowStd(false) logger.SetShowColor(false) logPath := filepath.Join(testBinDir(t), "rotate.log") if err := SetLogFile(logPath, logger, false); err != nil { t.Fatalf("SetLogFile failed: %v", err) } defer Close(logger) defer StopArchive(logger) if err := StartRotatePolicy(logger, &rotateWhenNonEmptyPolicy{}, 1); err != nil { t.Fatalf("StartRotatePolicy failed: %v", err) } logger.Infoln("trigger rotate") var found bool deadline := time.Now().Add(3 * time.Second) for time.Now().Before(deadline) { matches, err := filepath.Glob(logPath + ".*.bak") if err == nil && len(matches) > 0 { found = true break } time.Sleep(100 * time.Millisecond) } if !found { t.Fatalf("rotate policy should create archived files") } } func TestStartRotatePolicyPrefersArchivePathProvider(t *testing.T) { logger := NewStarlog(nil) logger.SetShowStd(false) logger.SetShowColor(false) logPath := filepath.Join(testBinDir(t), "rotate_provider.log") if err := SetLogFile(logPath, logger, false); err != nil { t.Fatalf("SetLogFile failed: %v", err) } defer Close(logger) defer StopArchive(logger) if err := StartRotatePolicy(logger, &rotatePreferArchivePathPolicy{}, 1); err != nil { t.Fatalf("StartRotatePolicy failed: %v", err) } logger.Infoln("trigger rotate with provider") var foundArchive bool deadline := time.Now().Add(3 * time.Second) for time.Now().Before(deadline) { matches, err := filepath.Glob(logPath + ".*.archive.bak") if err == nil && len(matches) > 0 { foundArchive = true break } time.Sleep(100 * time.Millisecond) } if !foundArchive { t.Fatalf("rotate policy should use ArchivePath when provider is implemented") } nextMatches, _ := filepath.Glob(logPath + ".*.next.bak") if len(nextMatches) > 0 { t.Fatalf("rotate policy should not use NextPath when ArchivePath is available") } } func TestStartRotatePolicyArchivePathProviderFallbackToNextPath(t *testing.T) { logger := NewStarlog(nil) logger.SetShowStd(false) logger.SetShowColor(false) logPath := filepath.Join(testBinDir(t), "rotate_provider_fallback.log") if err := SetLogFile(logPath, logger, false); err != nil { t.Fatalf("SetLogFile failed: %v", err) } defer Close(logger) defer StopArchive(logger) if err := StartRotatePolicy(logger, &rotateArchivePathFallbackPolicy{}, 1); err != nil { t.Fatalf("StartRotatePolicy failed: %v", err) } logger.Infoln("trigger rotate fallback") var foundNext bool deadline := time.Now().Add(3 * time.Second) for time.Now().Before(deadline) { matches, err := filepath.Glob(logPath + ".*.nextonly.bak") if err == nil && len(matches) > 0 { foundNext = true break } time.Sleep(100 * time.Millisecond) } if !foundNext { t.Fatalf("rotate policy should fallback to NextPath when ArchivePath returns empty") } } func TestArchiveHookBeforeAlias(t *testing.T) { archive := NewArchiveBySize(1024, 1, "app.log", "app.log.2006", false, nil, nil) hook := func(*StarLogger, string, string, os.FileInfo) error { return nil } archive.SetHookBeforeArchive(hook) if archive.HookBeforArchive() == nil || archive.HookBeforeArchive() == nil { t.Fatalf("both hook getter names should work") } } func TestCloseLogFileClearsManagedWriter(t *testing.T) { logger := NewStarlog(nil) logger.SetShowStd(false) logPath := filepath.Join(testBinDir(t), "close_logfile.log") if err := SetLogFile(logPath, logger, false); err != nil { t.Fatalf("SetLogFile failed: %v", err) } if err := CloseLogFile(logger); err != nil { t.Fatalf("CloseLogFile failed: %v", err) } if logger.GetWriter() != nil { t.Fatalf("CloseLogFile should clear managed writer") } }