liblog

package
v0.40.5 Latest Latest
Warning

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

Go to latest
Published: Aug 15, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package liblog provides a date-organised, size-bounded log directory: the writer a long-lived host points its structured logs at when nobody is watching a terminal.

It exists because an append-only log file is a slow leak. `contenox serve` is meant to run for days, and a process whose only log destination grows without bound eventually fills the disk it shares with the database — a failure that arrives long after the change that caused it, on the machine least likely to be watched.

Files are named `<name>-<YYYY-MM-DD>.log`, and a day that outgrows its size bound continues in `<name>-<YYYY-MM-DD>.2.log`, `.3.log`, and so on. Asking "what did this host do on Tuesday?" is then a question about filenames rather than about how many times the file happened to roll.

Why there is no background goroutine

Every decision this package makes happens on the write path, under the lock the writer needs anyway:

  • the day check and the size check are an integer compare and a cached date-string compare, far cheaper than the file write they precede;
  • the expensive part — scanning the directory to retire old files — runs only when a new file is actually created, which is at most once per day plus once per size part.

So retention is event-driven rather than swept on a timer: no goroutine to supervise, no second mutex, and nothing to leak if the host is killed. A counter ("organise every N appends") would be cheaper still, but it cannot be right at a day boundary — an idle host that logs once after midnight would file that line under yesterday until the counter happened to trip.

A Writer is safe for concurrent use, because slog handlers write from whichever goroutine logged.

Index

Constants

View Source
const DefaultMaxAge = 14 * 24 * time.Hour

DefaultMaxAge retires a log by date regardless of how few files exist, so a quiet host does not keep last quarter's logs simply because it never wrote enough to hit the file count.

View Source
const DefaultMaxBytes int64 = 10 << 20 // 10 MiB

DefaultMaxBytes is a single day-part's size ceiling when a caller expresses no preference: large enough that a normal day of INFO-level operation fits in one part, small enough to open in an editor.

View Source
const DefaultMaxFiles = 14

DefaultMaxFiles is how many log files survive by default, counted across every date and part. At the default size that is a bounded ~140 MiB.

View Source
const Unlimited = -1

Unlimited disables a retention bound.

Variables

This section is empty.

Functions

func FormatSize

func FormatSize(n int64) string

FormatSize renders a byte count the way ParseSize would accept it back, so a status screen and the config value that set it agree.

func ParseSize

func ParseSize(s string) (int64, error)

ParseSize reads a human-written size — "10MB", "512kb", "1 GiB", "2048" — into bytes. A bare number is bytes.

KB and KiB both mean 1024, and so on down the ladder. That is technically wrong about SI, and deliberate: the value is a disk budget typed by an operator who means "ten megabytes of log", and resolving KB to 1000 here would make `log-max-size 10MB` quietly produce files 5% smaller than the number they typed, for a distinction nobody was drawing.

Types

type Config

type Config struct {
	// Dir is the directory log files live in. Created if absent.
	Dir string
	// Name is the base name: "serve" yields serve-2026-08-15.log.
	Name string
	// MaxBytes bounds one part. Non-positive means [DefaultMaxBytes].
	MaxBytes int64
	// MaxFiles bounds how many files are retained across all dates.
	// Non-positive means [DefaultMaxFiles]; use [Unlimited] for no bound.
	MaxFiles int
	// MaxAge retires files older than this by their date stamp. Zero means
	// [DefaultMaxAge]; negative means no age bound.
	MaxAge time.Duration
	// Now is the clock, injectable so tests can cross a midnight boundary
	// without waiting for one. Nil means [time.Now].
	Now func() time.Time
}

Config describes a log directory. The zero value is not usable; every field except Now has a documented fallback applied by Open.

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

Writer is an io.WriteCloser over a date-organised log directory.

func Open

func Open(cfg Config) (*Writer, error)

Open prepares the log directory and opens today's current part for appending. A restart continues the latest existing part rather than starting a new one, so a host that is restarted repeatedly does not shard its day into a file per launch.

func (*Writer) Close

func (w *Writer) Close() error

Close closes the current file. It is safe to call more than once.

func (*Writer) Dir

func (w *Writer) Dir() string

Dir, MaxBytes, MaxFiles and MaxAge report the settings in force, so a status screen can state them rather than restating defaults and risking a lie.

func (*Writer) MaxAge

func (w *Writer) MaxAge() time.Duration

func (*Writer) MaxBytes

func (w *Writer) MaxBytes() int64

func (*Writer) MaxFiles

func (w *Writer) MaxFiles() int

func (*Writer) Path

func (w *Writer) Path() string

Path reports the file currently being written, so a status screen can name the exact file rather than a pattern.

func (*Writer) Reconfigure

func (w *Writer) Reconfigure(maxBytes int64, maxFiles int, maxAge time.Duration)

Reconfigure updates the retention settings of a live log.

It exists because a host must be able to log before it can read its own configuration: the log opens on defaults during boot, and the stored settings are applied once the database is readable. Only bounds change — the directory and name are fixed at Open, so no in-flight write can be redirected out from under a reader.

func (*Writer) Size

func (w *Writer) Size() int64

Size reports the current part's size.

func (*Writer) Write

func (w *Writer) Write(p []byte) (int, error)

Write appends p, starting a new file first when the date has changed or when p would carry the current part past its ceiling.

A single write larger than the ceiling is still written whole: a log line is the unit of meaning here, and splitting one across two files to honour a size bound would corrupt the record to protect a number.

Jump to

Keyboard shortcuts

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