Skip to content

sutantodadang/go-rotate-logs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

go-rotate-logs

Go Reference MIT License CI

Go-Rotate-Logs is a cross-platform helper for rolling your logfile, cleaning up old log files, and backing up your log files when they exceed your desired size. It works with any logging library that implements io.Writer.

✨ Features

  • 🚀 High Performance: 54x faster than V1, zero allocations
  • 🌍 Cross-Platform: Works on Linux, macOS (Intel & ARM), and Windows
  • 🔄 Automatic Rotation: Rotate logs by size
  • 🕒 Time-Based Naming: Add timestamps to log filenames
  • 🧹 Auto Cleanup: Remove old log files automatically
  • 📦 Sequential Backups: Numbered backup files (backup-0, backup-1, etc.)
  • 🔒 Thread-Safe: Safe for concurrent writes
  • 📝 io.Writer Compatible: Works with any Go logging library

🌍 Cross-Platform Support

Fully tested on:

  • Linux (AMD64, ARM64)
  • macOS (Intel, Apple Silicon M1/M2/M3)
  • Windows (AMD64)

Automatic filename sanitization for Windows compatibility

Recent Updates (2025)

Performance improvements applied:

  • 54x faster than original implementation
  • Zero memory allocations per write
  • Keeps file open between writes for maximum performance

Critical bug fixes:

  • Fixed file path bug in cleanup function
  • Added Close() method to prevent file handle leaks
  • Comprehensive test suite with 79.8% coverage
  • Cross-platform compatibility verified (Linux, macOS, Windows)

⚠️ Important: Always Call Close()

New in this version: Always call Close() when you're done with the logger to prevent file handle leaks:

logFile := &rotate.RotateLogsWriter{
    Config: rotate.Config{
        Directory: "logs",
        Filename:  "app.log",
        MaxSize:   10,
    },
}
defer logFile.Close() // IMPORTANT: Always close!

Installation

go get github.com/sutantodadang/go-rotate-logs

Quick Start

import (
    "log"
    rotate "github.com/sutantodadang/go-rotate-logs"
)

logFile := &rotate.RotateLogsWriter{
    Config: rotate.Config{
        Directory: "logs",
        Filename:  "app.log",
        MaxSize:   10, // 10MB
    },
}
defer logFile.Close() // IMPORTANT: Always close!

log.SetOutput(logFile)
log.Println("Hello, rotating logs!")

Config

All configuration options for RotateLogsWriter:

Field Type Required Default Description
Directory string ✅ Yes - Directory where log files will be stored
Filename string ✅ Yes - Log filename (must end with .log)
MaxSize int ✅ Yes - Maximum file size in MB before rotation
BackupName string ⬜ No "backup" Prefix for backup files (e.g., app-backup-0.log)
UsingTime bool ⬜ No false Add timestamp to filename
FormatTime string ⬜ No time.RFC3339 Time format for filename (requires UsingTime: true)
CleanOldFiles bool ⬜ No false Enable automatic cleanup of old files
MaxAge int ⬜ No 7 Days to keep old files (requires CleanOldFiles: true)

Time Format Examples

FormatTime: "2006-01-02"          // Date: 2025-11-03
FormatTime: "2006-01-02-15"       // Date + Hour: 2025-11-03-14
FormatTime: time.RFC3339          // Full timestamp: 2025-11-03T14-30-00+07-00 (sanitized)
FormatTime: "200601021504"        // Compact: 202511031430

Note: Invalid filename characters (:, /, \) are automatically replaced with - for Windows compatibility.

Usage Examples

Basic Usage

import (
    "log"
    rotate "github.com/sutantodadang/go-rotate-logs"
)

logFile := &rotate.RotateLogsWriter{
    Config: rotate.Config{
        Directory: "logs",
        Filename:  "app.log",
        MaxSize:   10, // 10MB
    },
}
defer logFile.Close()

log.SetOutput(logFile)
log.Println("Application started")

Time-Based Filenames

logFile := &rotate.RotateLogsWriter{
    Config: rotate.Config{
        Directory:  "logs",
        Filename:   "app.log",
        MaxSize:    10,
        UsingTime:  true,
        FormatTime: "2006-01-02", // Creates: app-2025-11-03.log
    },
}
defer logFile.Close()

log.SetOutput(logFile)

With Auto Cleanup

logFile := &rotate.RotateLogsWriter{
    Config: rotate.Config{
        Directory:     "logs",
        Filename:      "app.log",
        MaxSize:       10,
        CleanOldFiles: true,
        MaxAge:        30, // Keep logs for 30 days
    },
}
defer logFile.Close()

log.SetOutput(logFile)

With MultiWriter (File + Console)

import (
    "io"
    "log"
    "os"
    rotate "github.com/sutantodadang/go-rotate-logs"
)

logFile := &rotate.RotateLogsWriter{
    Config: rotate.Config{
        Directory: "logs",
        Filename:  "app.log",
        MaxSize:   10,
    },
}
defer logFile.Close()

// Write to both file and stdout
mw := io.MultiWriter(logFile, os.Stdout)
log.SetOutput(mw)
log.Println("Logged to file and console!")

With Zerolog

import (
    "os"
    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
    rotate "github.com/sutantodadang/go-rotate-logs"
)

logFile := &rotate.RotateLogsWriter{
    Config: rotate.Config{
        Directory:     "logs",
        Filename:      "app.log",
        MaxSize:       10,
        UsingTime:     true,
        FormatTime:    "2006-01-02",
        CleanOldFiles: true,
        MaxAge:        30,
    },
}
defer logFile.Close()

mw := zerolog.MultiWriter(logFile, os.Stdout)
log.Logger = zerolog.New(mw).With().Timestamp().Caller().Logger()

log.Info().Msg("Application started")

Thread-Safe Concurrent Usage

logFile := &rotate.RotateLogsWriter{
    Config: rotate.Config{
        Directory: "logs",
        Filename:  "concurrent.log",
        MaxSize:   20,
    },
}
defer logFile.Close()

logger := log.New(logFile, "", log.LstdFlags)

// Safe to use from multiple goroutines
for i := 0; i < 10; i++ {
    go func(id int) {
        logger.Printf("Goroutine %d is writing\n", id)
    }(i)
}

See examples/main.go for more complete examples.

Testing

The package includes comprehensive tests with 79.8% code coverage:

# Run all tests
go test -v

# Run with race detection
go test -race

# Run with coverage
go test -cover

# Run cross-platform tests
go test -v -run TestCrossPlatform

# Run benchmarks
go test -bench=Benchmark -benchmem -run=^$

Performance

The current implementation is highly optimized:

  • Fast: Keeps file open between writes (no repeated open/close)
  • Efficient: Zero memory allocations per write operation
  • Thread-Safe: Mutex-protected for concurrent access
  • Cross-Platform: Works identically on Linux, macOS, and Windows

Benchmark Results

Tested on AMD Ryzen 5 7500F (6-Core), Windows 11:

BenchmarkRotateLogsWriter_Write-12         609350    1928 ns/op      0 B/op    0 allocs/op
BenchmarkRotateLogsWriter_LargeWrite-12    463408    2540 ns/op      0 B/op    0 allocs/op
  • Small writes (22 bytes): ~1,928 ns/op, zero allocations
  • Large writes (1 KB): ~2,540 ns/op, zero allocations
  • 54x faster than the original implementation!

How It Works

  1. Write Detection: Monitors total bytes written to current file
  2. Size Check: Before each write, checks if size would exceed MaxSize
  3. Rotation: When threshold exceeded:
    • Current file renamed to {filename}-backup-{N}.log (sequential numbering)
    • New file created with original name
    • Counter increments for next rotation
  4. Cleanup (optional): Removes files older than MaxAge days
  5. Thread-Safe: All operations protected by mutex for concurrent access

File Naming Examples

Without time:

app.log                    # Current log
app-backup-0.log          # First backup
app-backup-1.log          # Second backup
app-backup-2.log          # Third backup

With time (FormatTime: "2006-01-02"):

app-2025-11-03.log                # Current log
app-2025-11-03-backup-0.log      # First backup
app-2025-11-03-backup-1.log      # Second backup

With time (FormatTime: time.RFC3339):

app-2025-11-03T14-30-00+07-00.log               # Current log (sanitized)
app-2025-11-03T14-30-00+07-00-backup-0.log     # First backup

Best Practices

  1. Always call Close(): Use defer logFile.Close() to prevent file handle leaks
  2. Choose appropriate MaxSize: Balance between rotation frequency and file size
  3. Use time-based names for daily logs: FormatTime: "2006-01-02"
  4. Enable cleanup for long-running apps: Set CleanOldFiles: true and appropriate MaxAge
  5. Use date format for readability: Prefer "2006-01-02" over time.RFC3339 for simpler filenames

Documentation

FAQ

Q: Do I need to manually rotate logs?
A: No, rotation happens automatically when file size exceeds MaxSize.

Q: Can I use this with any logging library?
A: Yes! It implements io.Writer, so it works with standard library log, zerolog, logrus, zap, etc.

Q: Is it safe for concurrent use?
A: Yes, all operations are protected by a mutex.

Q: What happens to old backups?
A: They're numbered sequentially (0, 1, 2...). If CleanOldFiles: true, files older than MaxAge days are automatically deleted.

Q: Does it work on Windows?
A: Yes! Filenames are automatically sanitized for Windows compatibility (:, /, \-).

Q: How do I check for cleanup errors?
A: Use GetCleanupError() method to check if cleanup encountered any errors.

Contributing

Contributions are welcome! The package includes:

  • ✅ 16 comprehensive tests with 79.8% code coverage
  • ✅ Cross-platform compatibility tests
  • ✅ GitHub Actions CI for automated testing
  • ✅ Race condition detection

Authors

About

Go-Rotate-Logs is helper to rolling your logfile, cleanup old log file and backup your log file if more than size you want. its works on different logging that implement io.writer.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages