Documentation
¶
Overview ¶
Package logbuffer persists graywolf's slog records to a bounded circular buffer in a standalone SQLite database (graywolf-logs.db).
It is wired in cmd/graywolf/main.go as a slog.Handler that wraps the existing console TextHandler: every record continues to print to the console at the operator's selected level (INFO, or DEBUG with -debug), and is also persisted at DEBUG level so a later "graywolf flare" submission can attach recent history regardless of how the operator configured the console.
The DB lives in a separate file from the main configstore to avoid write contention on the main app DB; on Raspberry Pi / SD-card-rooted systems the file is placed on tmpfs to avoid SD-card wear.
Design: .context/2026-04-25-graywolf-flare-system-design.md
§ "Subsystem 1 — Circular Log Persistence".
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BackingDeviceFor ¶
BackingDeviceFor exposes backingDeviceForPath under an exported name.
func IsRamdiskPath ¶
IsRamdiskPath reports whether p was placed under one of the tmpfs candidates ResolvePath tries (i.e. /run/graywolf or /dev/shm/graywolf). Used by cmd/graywolf to detect a "wanted ramdisk but fell back to disk" outcome and surface the spec-required WARN. Kept here so the candidate list stays the single source of truth.
func IsRaspberryPiHost ¶
func IsRaspberryPiHost() bool
IsRaspberryPiHost is the production-default wrapper around isRaspberryPi that reads /sys/firmware/devicetree/base/model. Exposed so cmd/graywolf can drive the path picker without re-implementing the detection.
func IsSDCardDevice ¶
IsSDCardDevice exposes isSDCardDevice under an exported name.
func ResolvePath ¶
func ResolvePath(opts ResolveOptions) (string, error)
ResolvePath returns the absolute path where graywolf-logs.db should live. The returned directory is NOT guaranteed to exist — Open() creates it.
Types ¶
type Config ¶
Config tunes the Handler.
RingSize is the maximum number of rows retained. <=0 disables persistence entirely (Handler still forwards to the inner handler).
MaintenanceEvery controls how often eviction runs (every Nth Handle call). 0 means "never trigger from Handle" (used by tests that drive eviction manually). Wired to 200 in production by cmd/graywolf/main.go.
type DB ¶
type DB struct {
Path string
// contains filtered or unexported fields
}
DB is the standalone graywolf-logs.db handle. It deliberately exposes no application-level methods beyond Close — the handler reaches into gorm directly so we don't carry a duplicate insert API.
func Open ¶
Open opens (or creates) the log buffer database at path. The parent directory is created if missing; the SQLite file is chmod-ed 0600 since log content can include hostnames, paths, and other operator-private detail.
func (*DB) Query ¶ added in v0.13.16
func (d *DB) Query(opts QueryOptions) ([]Record, error)
Query returns persisted log rows in ascending id (chronological) order. It selects the most-recent rows matching the filters (newest first) then reverses so callers can append to a chronological view.
The read shares the single pooled connection with the slog writer (db.go pins MaxOpenConns(1)); WAL mode keeps this correct, and the per-poll read volume is tiny, so contention is not a concern.
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler is a slog.Handler that forwards every record to an inner handler (typically the console TextHandler) and tees it to a logbuffer DB. Capture is always at DEBUG regardless of the inner handler's threshold so a future flare submission has full detail.
func (*Handler) Enabled ¶
Enabled returns true for every level >= Debug. The inner handler is asked separately inside Handle so the console keeps its configured threshold.
func (*Handler) Handle ¶
Handle forwards the record to the inner handler (subject to the inner handler's own Enabled check) and persists it to the DB at DEBUG-and-above.
type QueryOptions ¶ added in v0.13.16
type QueryOptions struct {
// Since, when non-zero, restricts results to rows at or after this time.
Since time.Time
// Limit caps the number of (most-recent) rows returned. Values <= 0
// fall back to defaultQueryLimit.
Limit int
// Levels, when non-empty, restricts results to these slog level
// strings ("DEBUG","INFO","WARN","ERROR"). Empty means all levels.
Levels []string
}
QueryOptions filters a Query. The zero value returns the most recent rows up to the default limit.
type Record ¶ added in v0.13.16
type Record struct {
Time time.Time
Level string
Component string
Message string
Attrs map[string]any
}
Record is one persisted log row, decoded for read consumers.
type ResolveOptions ¶
type ResolveOptions struct {
// ConfigDBPath is the path the operator gave for graywolf.db. Used as
// the disk-backed fallback location (logs land in the same directory
// under graywolf-logs.db).
ConfigDBPath string
// PreferRamdisk forces ramdisk preference even on non-SD systems.
// Wired from --logbuffer-ramdisk.
PreferRamdisk bool
// IsRaspberryPi mirrors isRaspberryPi() — kept as a separate field so
// callers can override for tests and so the structured form documents
// the inputs explicitly.
IsRaspberryPi bool
// BackingIsSDCard is true when the directory holding the config DB is
// backed by an mmcblk/mtdblock device.
BackingIsSDCard bool
// WritableProbe is invoked for each candidate ramdisk directory. The
// first directory the probe accepts wins. In production this is
// defaultWritableProbe (creates and removes a temp file).
WritableProbe func(dir string) error
}
ResolveOptions are the inputs to the path picker. All fields are either CLI-flag-derived or detector outputs so the picker itself stays pure and testable.