go-ruby-logger

Ruby's stdlib Logger core in pure Go โ€” MRI-compatible, no cgo.

pure Go ยท zero cgo MRI Logger core severity model default formatter level gating rotation policy injected clock / pid / sink MRI byte-exact 100% coverage 6 arches
Documentation GitHub
Documentation (MkDocs Material + mike) License: BSD-3-Clause Go 1.26.4+ Coverage 100%

go-ruby-logger is a pure-Go (no cgo) reimplementation of the deterministic core of Ruby's stdlib Logger โ€” the severity model, the default Formatter, level gating, and the LogDevice rotation policy of MRI 4.0.5. It decides what bytes a log line is, whether a message is gated out by the level, and whether โ€” and to which filename โ€” a log file should rotate, all as pure functions over an injected clock, pid and filesize, with no IO and no Ruby runtime. It was extracted from rbgo's internals into a reusable standalone library; opening the file, writing the bytes and renaming on rotation stay the host's job. It is the logging backend for go-embedded-ruby, bound by rbgo as a native module just like go-ruby-regexp, go-ruby-erb and go-ruby-logger โ€” differential-tested against MRI byte-for-byte, 100% coverage, CI green across 6 arches and 3 OSes.

Severity model ready

DEBUG=0 INFO=1 WARN=2 ERROR=3 FATAL=4 UNKNOWN=5, the SeverityLabel mapping ("DEBUG".."FATAL", then "ANY"), and CoerceSeverity โ€” MRI’s Severity.coerce from an int or a "warn"/"FATAL" string.

Default formatter ready

MRI’s "%.1s, [%s #%d] %5s -- %s: %s\n" with the "%Y-%m-%dT%H:%M:%S.%6N" timestamp, the datetime_format override and the msg2str coercion โ€” pure over an injected clock and pid, so a fixed instant reproduces MRI’s exact bytes.

Level gating ready

Add (alias Log) formats nothing when there is no sink or severity < level; the Debug/Info/Warn/Error/Fatal/Unknown helpers, the <<-style raw Write, and the DebugQ..FatalQ predicates all mirror MRI.

Rotation policy ready

A pure decision, no IO: ShouldRotateBySize + the ShiftAgeSequence rename plan for integer shift_age; NextRotateTime, ShouldRotateByPeriod and the PeriodAgeFile rotated-name (with MRI’s .1...99 collision suffix) for daily/weekly/monthly periods.

Compute core, host IO ready

The library hands back the line bytes and the rename plan; opening the file, writing the bytes, renaming on rotation and reading the wall clock are the host’s job, injected via the Sink, Now and Pid seams.

Differential oracle & coverage ready

The line bytes, datetime_format override, Exception coercion, end-to-end gating, the full severity range and the rotation schedule are each compared byte-for-byte against the system ruby; 100% coverage, gofmt + go vet clean, green across all six 64-bit Go arches and three OSes.

A faithful port of the deterministic core of Ruby's Logger in pure Go, cgo disabled, so it cross-compiles and embeds anywhere. It builds the default-format line bytes (msg2str + the %6N timestamp), decides the severity gate, and computes the rotation schedule and rotated-file names โ€” all pure over an injected clock, pid and filesize. Validated differentially against the system ruby binary. It is a standalone, reusable module extracted from rbgo's internals, and the logging backend for the sibling org github.com/go-embedded-ruby.