package starlog import ( "errors" "os" "path/filepath" "strings" "sync" "time" ) const defaultRotateNamePattern = "20060102-150405" func normalizeRotateNamePattern(pattern string) string { pattern = strings.TrimSpace(pattern) if pattern == "" { return defaultRotateNamePattern } return pattern } func buildRotatePathWithPattern(current string, now time.Time, pattern string) string { pattern = normalizeRotateNamePattern(pattern) dir := filepath.Dir(current) base := filepath.Base(current) ext := filepath.Ext(base) stem := strings.TrimSuffix(base, ext) suffix := now.Format(pattern) if ext == "" { return filepath.Join(dir, stem+"."+suffix) } return filepath.Join(dir, stem+"."+suffix+ext) } func resolveEntryTime(entry *Entry) time.Time { if entry != nil && !entry.Time.IsZero() { return entry.Time } return time.Now() } type RotateByTimePolicy struct { interval time.Duration pattern string mu sync.Mutex lastRotate time.Time } func NewRotateByTimePolicy(interval time.Duration) *RotateByTimePolicy { return NewRotateByTimePolicyWithPattern(interval, "") } func NewRotateByTimePolicyWithPattern(interval time.Duration, pattern string) *RotateByTimePolicy { if interval <= 0 { interval = 24 * time.Hour } return &RotateByTimePolicy{ interval: interval, pattern: normalizeRotateNamePattern(pattern), } } func (policy *RotateByTimePolicy) ShouldRotate(info FileInfo, entry *Entry) bool { if policy == nil || info == nil { return false } now := resolveEntryTime(entry) policy.mu.Lock() defer policy.mu.Unlock() if policy.lastRotate.IsZero() { policy.lastRotate = GetFileCreationTime(info) if policy.lastRotate.IsZero() { policy.lastRotate = now } return false } if now.Sub(policy.lastRotate) >= policy.interval { policy.lastRotate = now return true } return false } func (policy *RotateByTimePolicy) NextPath(current string, now time.Time) string { if policy == nil { return buildRotatePathWithPattern(current, now, "") } return buildRotatePathWithPattern(current, now, policy.pattern) } type RotateBySizePolicy struct { maxSize int64 pattern string } func NewRotateBySizePolicy(maxSizeBytes int64) *RotateBySizePolicy { return NewRotateBySizePolicyWithPattern(maxSizeBytes, "") } func NewRotateBySizePolicyWithPattern(maxSizeBytes int64, pattern string) *RotateBySizePolicy { if maxSizeBytes <= 0 { maxSizeBytes = 100 * 1024 * 1024 } return &RotateBySizePolicy{ maxSize: maxSizeBytes, pattern: normalizeRotateNamePattern(pattern), } } func (policy *RotateBySizePolicy) ShouldRotate(info FileInfo, entry *Entry) bool { _ = entry if policy == nil || info == nil { return false } return info.Size() >= policy.maxSize } func (policy *RotateBySizePolicy) NextPath(current string, now time.Time) string { if policy == nil { return buildRotatePathWithPattern(current, now, "") } return buildRotatePathWithPattern(current, now, policy.pattern) } type RotateByTimeSizePolicy struct { interval time.Duration maxSize int64 pattern string mu sync.Mutex lastRotate time.Time } func NewRotateByTimeSizePolicy(interval time.Duration, maxSizeBytes int64) *RotateByTimeSizePolicy { return NewRotateByTimeSizePolicyWithPattern(interval, maxSizeBytes, "") } func NewRotateByTimeSizePolicyWithPattern(interval time.Duration, maxSizeBytes int64, pattern string) *RotateByTimeSizePolicy { if interval <= 0 { interval = 24 * time.Hour } if maxSizeBytes <= 0 { maxSizeBytes = 100 * 1024 * 1024 } return &RotateByTimeSizePolicy{ interval: interval, maxSize: maxSizeBytes, pattern: normalizeRotateNamePattern(pattern), } } func (policy *RotateByTimeSizePolicy) ShouldRotate(info FileInfo, entry *Entry) bool { if policy == nil || info == nil { return false } now := resolveEntryTime(entry) policy.mu.Lock() defer policy.mu.Unlock() if policy.lastRotate.IsZero() { policy.lastRotate = GetFileCreationTime(info) if policy.lastRotate.IsZero() { policy.lastRotate = now } } if info.Size() >= policy.maxSize { policy.lastRotate = now return true } if now.Sub(policy.lastRotate) >= policy.interval { policy.lastRotate = now return true } return false } func (policy *RotateByTimeSizePolicy) NextPath(current string, now time.Time) string { if policy == nil { return buildRotatePathWithPattern(current, now, "") } return buildRotatePathWithPattern(current, now, policy.pattern) } func StartRotateByTime(logger *StarLogger, interval time.Duration, checkInterval int64) error { return StartRotatePolicy(logger, NewRotateByTimePolicy(interval), checkInterval) } func StartRotateBySize(logger *StarLogger, maxSizeBytes int64, checkInterval int64) error { return StartRotatePolicy(logger, NewRotateBySizePolicy(maxSizeBytes), checkInterval) } func StartRotateByTimeSize(logger *StarLogger, interval time.Duration, maxSizeBytes int64, checkInterval int64) error { return StartRotatePolicy(logger, NewRotateByTimeSizePolicy(interval, maxSizeBytes), checkInterval) } func StartManagedRotatePolicy(logger *StarLogger, policy RotatePolicy, checkInterval int64, options RotateManageOptions) error { if policy == nil { return errors.New("rotate policy is nil") } strategy := buildRotateStrategy(policy, checkInterval) strategy.afterHook = func(logger *StarLogger, archivePath string, currentPath string, info os.FileInfo) error { return ApplyRotateManageOptions(archivePath, currentPath, options) } return startArchiveWithStrategy(logger, strategy) } func StartManagedRotateByTime(logger *StarLogger, interval time.Duration, checkInterval int64, options RotateManageOptions) error { return StartManagedRotatePolicy(logger, NewRotateByTimePolicy(interval), checkInterval, options) } func StartManagedRotateBySize(logger *StarLogger, maxSizeBytes int64, checkInterval int64, options RotateManageOptions) error { return StartManagedRotatePolicy(logger, NewRotateBySizePolicy(maxSizeBytes), checkInterval, options) } func StartManagedRotateByTimeSize(logger *StarLogger, interval time.Duration, maxSizeBytes int64, checkInterval int64, options RotateManageOptions) error { return StartManagedRotatePolicy(logger, NewRotateByTimeSizePolicy(interval, maxSizeBytes), checkInterval, options) }