logrotate

package
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 18, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package logrotate implements size- and age-bounded rotation for the append-only JSONL logs ctm writes under ~/.config/ctm/logs/.

Design choices:

  • Rotated files use a unix-nanosecond suffix plus ".gz": e.g. "<path>.1745000000000000000.gz". This yields stable chronological ordering without cascading renames and makes age-based pruning a mtime lookup rather than a filename parse.
  • Rotation runs synchronously in the hook path. At the default 50 MiB threshold, gzip is well under 1s on a modern CPU and is triggered at most once per threshold crossing, not per line.
  • A sibling ".rotate.lock" coordinates concurrent writers so only one rotates at a time; a late caller finds the file already small and no-ops.
  • The active file's mode is preserved; rotated .gz siblings inherit the same mode. Callers that want 0600 on the active file will automatically get 0600 on .gz siblings too.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func MaybeRotate

func MaybeRotate(path string, p Policy) error

MaybeRotate rotates path if it exceeds p.MaxSize. It renames the file to "<path>.<unix-nano>", gzips the result in place, and truncates the original to a fresh empty file. After rotation it runs Prune so the caller does not have to.

Contract:

  • Missing file → no-op (nothing to rotate).
  • p.MaxSize == 0 → no-op (size-based rotation disabled; caller may still invoke Prune separately for age-only retention).
  • Concurrent calls serialize on a sibling "<path>.rotate.lock"; the second caller re-stats post-lock and no-ops if the file is back under the threshold.

Writers that keep an open fd to path across a rotation continue appending to the rotated file (same inode). We accept that small trailing-edge data loss (a handful of lines landing in the just- rotated file instead of the fresh active) as the cost of not coordinating fds across processes; the data is still on disk, just in the .gz rather than the active log.

func Open

func Open(path string) (io.ReadCloser, error)

Open returns a reader for a log source. If path ends in ".gz" the reader transparently decompresses; otherwise it's a plain file reader. The returned ReadCloser owns both the file and the gzip reader (if any); a single Close cleans up both.

func Prune

func Prune(path string, p Policy) error

Prune removes rotated siblings of path that violate the retention caps in p: older than MaxAge, or beyond MaxFiles (oldest discarded first). The active log file itself is never touched by Prune.

Zero-valued caps disable the corresponding check.

func Sources

func Sources(path string) ([]string, error)

Sources returns every readable log source belonging to path, in chronological order (oldest first). Rotated .gz siblings come first; the active path is last. Entries that cannot be stat()ed are dropped.

Missing active file is not an error — Sources only returns rotated siblings in that case.

Types

type Policy

type Policy struct {
	MaxSize  int64         // rotate when the active file exceeds this (bytes)
	MaxAge   time.Duration // prune rotated siblings older than this
	MaxFiles int           // cap rotated siblings (keep the newest N)
}

Policy governs rotation and retention. Zero-valued fields disable the corresponding check (e.g. MaxSize=0 disables size-based rotation).

func DefaultPolicy

func DefaultPolicy() Policy

DefaultPolicy returns the ctm default: rotate at 50 MiB, keep at most 30 days of history or 10 files, whichever is smaller.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL