Timberjack is a forked and enhanced version of lumberjack, adding features such as time-based rotation and better testability.
Package timberjack provides a rolling logger with support for size-based and time-based log rotation.
go get github.com/DeRuina/timberjackimport "github.com/DeRuina/timberjack"Timberjack is intended to be one part of a logging infrastructure. It is a pluggable
component that manages log file writing and rotation. It plays well with any logging package that can write to an
io.Writer, including the standard library's log package.
⚠️ Timberjack assumes that only one process writes to the log file. Using the same configuration from multiple processes on the same machine may result in unexpected behavior.
To use timberjack with the standard library's log package, including interval-based and scheduled minute-based rotation:
import (
"log"
"time"
"github.com/DeRuina/timberjack"
)
func main() {
logger := &timberjack.Logger{
Filename: "/var/log/myapp/foo.log", // Choose an appropriate path
MaxSize: 500, // megabytes
MaxBackups: 3, // backups
MaxAge: 28, // days
Compress: true, // default: false
LocalTime: true, // default: false (use UTC)
RotationInterval: time.Hour * 24, // Rotate daily if no other rotation met
RotateAtMinutes: []int{0, 15, 30, 45}, // Also rotate at HH:00, HH:15, HH:30, HH:45
BackupTimeFormat: "2006-01-02-15-04-05" // Rotated files will have format <logfilename>-2006-01-02-15-04-05-<rotationCriterion>-timberjack.log
}
log.SetOutput(logger)
defer logger.Close() // Ensure logger is closed on application exit to stop goroutines
// Example log writes
log.Println("Application started")
// ... your application logic ...
log.Println("Application shutting down")
}To trigger rotation on demand (e.g. in response to SIGHUP):
l := &timberjack.Logger{}
log.SetOutput(l)
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP)
go func() {
for range c {
l.Rotate()
}
}()type Logger struct {
Filename string // File to write logs to
MaxSize int // Max size (MB) before rotation (default: 100)
MaxAge int // Max age (days) to retain old logs
MaxBackups int // Max number of backups to keep
LocalTime bool // Use local time in rotated filenames
Compress bool // Compress rotated logs (gzip)
RotationInterval time.Duration // Rotate after this duration (if > 0)
RotateAtMinutes []int // Specific minutes within an hour (0-59) to trigger a rotation.
BackupTimeFormat string // Optional. If unset or invalid, defaults to 2006-01-02T15-04-05.000 (with fallback warning).- Size-Based: If a write operation causes the current log file to exceed
MaxSize, the file is rotated before the write. The backup filename will include-sizeas the reason. - Time-Based: If
RotationIntervalis set (e.g.,time.Hour * 24for daily rotation) and this duration has passed since the last rotation (of any type that updates the interval timer), the file is rotated upon the next write. The backup filename will include-timeas the reason. - Scheduled Minute-Based: If
RotateAtMinutesis configured (e.g.,[]int{0, 30}the rotation will happen every hour atHH:00:00andHH:30:00), a dedicated goroutine will trigger a rotation when the current time matches one of these minute marks. This rotation also uses-timeas the reason in the backup filename. - Manual: You can call
Logger.Rotate()directly to force a rotation at any time. The reason in the backup filename will be"-time"if an interval rotation was also due, otherwise it defaults to"-size".
Rotated files are renamed using the pattern:
<name>-<timestamp>-<reason>.log
For example:
/var/log/myapp/foo-2025-04-30T15-00-00.000-size.log
/var/log/myapp/foo-2025-04-30T22-15-42.123-time.log
/var/log/myapp/foo-2025-05-01T10:30:00.000-time.log.gz (if scheduled at HH:30 and compressed)
-
BackupTimeFormatValues must be valid and should not change after initialization
TheBackupTimeFormatvalue must be valid and must follow the timestamp layout rules specified here: https://pkg.go.dev/time#pkg-constants.BackupTimeFormatsupports more formats but it's recommended to use standard formats. If an invalidBackupTimeFormatis configured, Timberjack logs a warning toos.Stderrand falls back to the default format:2006-01-02T15-04-05.000. Rotation will still work, but the resulting filenames may not match your expectations. -
Silent Ignoring of Invalid
RotateAtMinutesValues
Values outside the valid range (0–59) or duplicates inRotateAtMinutesare silently ignored. No warnings or errors will be logged. This allows the program to continue safely, but the rotation behavior may not match your expectations if values are invalid. -
Logger Must Be Closed
Always calllogger.Close()when done logging. This shuts down internal goroutines used for scheduled rotation and cleanup. Failing to close the logger can result in orphaned background processes, open file handles, and memory leaks. -
Size-Based Rotation Is Always Active
Regardless ofRotationIntervalorRotateAtMinutes, size-based rotation is always enforced. If a write causes the log to exceedMaxSize(default: 100MB), it triggers an immediate rotation. -
If Only
RotationIntervalIs Set
The logger will rotate after the configured time has passed since the last rotation, regardless of file size progression. -
If Only
RotateAtMinutesIs Set
The logger will rotate at the clock times specified, regardless of file size or duration passed. This is handled by a background goroutine. Rotated logs might be even empty if no write has occurred. -
If Both Are Set
Both time-based strategies (RotationIntervalandRotateAtMinutes) are evaluated. Whichever condition occurs first triggers rotation. However:- Both update the internal
lastRotationTimefield. - This means if a rotation happens due to
RotateAtMinutes, it resets the interval timer, potentially delaying or preventing aRotationInterval-based rotation.
This behavior ensures you won’t get redundant rotations, but it may make
RotationIntervalfeel unpredictable ifRotateAtMinutesis also configured. - Both update the internal
When a new log file is created:
- Older backups beyond
MaxBackupsare deleted. - Files older than
MaxAgedays are deleted. - If
Compressis true, older files are gzip-compressed.
We welcome contributions!
Please see our contributing guidelines before submitting a pull request.
MIT