Documentation
¶
Overview ¶
Package logs keeps recent log lines of hbbs, hbbr and rdpanel in memory for the panel's Server -> Logs page (GET /api/v1/admin/server/logs*).
Lines get in two ways:
- In the s6 image every service's log pipeline (prefix-log) copies each line to the container's stdout and to an s6-log directory <RDPANEL_LOGS_DIR>/<source>/current (rotated, bounded); a Tailer per source follows that file. rdpanel never sits in the services' output path, so a slow or stopped panel cannot block hbbs/hbbr, and the files refill the buffer when rdpanel restarts.
- Without a logs directory (plain `rdpanel serve`), only the panel's own lines are captured, by teeing the slog output (Writer).
Every line is redacted (Redactor) before it is parsed and stored. A Buffer holds one fixed-size ring per source; live subscribers get new entries over bounded queues that drop (and count) instead of blocking.
Index ¶
- Constants
- Variables
- type Buffer
- func (b *Buffer) AddSecrets(secrets ...string)
- func (b *Buffer) Capacity() int
- func (b *Buffer) Enabled() bool
- func (b *Buffer) Query(f Filter) Page
- func (b *Buffer) Register(reg prometheus.Registerer)
- func (b *Buffer) Sources(want ...string) []SourceInfo
- func (b *Buffer) Subscribe(f Filter, buffer int) *Subscription
- func (b *Buffer) Subscribers() int
- func (b *Buffer) Write(source, line string)
- func (b *Buffer) Writer(source string) io.Writer
- type Entry
- type Filter
- type Level
- type Page
- type Redactor
- type SourceInfo
- type Subscription
- type Tailer
Constants ¶
const ( SourceHBBS = "hbbs" SourceHBBR = "hbbr" SourcePanel = "panel" SourceOther = "other" )
Known sources. Lines from other producers are filed under SourceOther so metric labels stay bounded.
const DefaultSubscriptionBuffer = 1024
DefaultSubscriptionBuffer is the per-subscriber queue length.
const MaxCapacity = 100_000
MaxCapacity bounds the per-source capacity.
const MaxLineBytes = 8 << 10
MaxLineBytes bounds a stored line; longer lines are cut.
const Redacted = "[REDACTED]"
Redacted replaces secret values.
Variables ¶
var FileSources = []string{SourceHBBS, SourceHBBR, SourcePanel}
FileSources are the s6 services whose output the image writes to the logs directory (deploy/rootfs/usr/libexec/rdpanel/prefix-log).
Functions ¶
This section is empty.
Types ¶
type Buffer ¶
type Buffer struct {
// contains filtered or unexported fields
}
Buffer is the in-memory store. A nil *Buffer, or one with capacity 0, is disabled: writes are dropped and queries return nothing.
func NewBuffer ¶
NewBuffer creates a buffer keeping up to capacity lines per source (0 disables it; values above MaxCapacity are clamped).
func (*Buffer) AddSecrets ¶
AddSecrets makes the redactor replace these literal values (keys, passwords) in every later line. Short values (< 8 bytes) are ignored.
func (*Buffer) Register ¶
func (b *Buffer) Register(reg prometheus.Registerer)
Register adds the buffer's metrics to reg (nil reg: no-op).
func (*Buffer) Sources ¶
func (b *Buffer) Sources(want ...string) []SourceInfo
Sources lists the sources that have stored lines plus the always-present ones in want, sorted by name.
func (*Buffer) Subscribe ¶
func (b *Buffer) Subscribe(f Filter, buffer int) *Subscription
Subscribe registers a live consumer; buffer <= 0 means DefaultSubscriptionBuffer. It returns nil when the buffer is disabled.
func (*Buffer) Subscribers ¶
Subscribers returns the number of live subscriptions.
func (*Buffer) Write ¶
Write ingests one raw line of source (without the trailing newline). It never blocks on consumers.
func (*Buffer) Writer ¶
Writer returns an io.Writer that splits what is written into lines and stores them under source. Tee the process logger through it (io.MultiWriter(os.Stderr, buf.Writer("panel"))) to capture the panel's own slog output. It never fails and never blocks on consumers. A disabled buffer yields io.Discard.
type Entry ¶
type Entry struct {
// Seq is a process-local, strictly increasing id across all sources.
Seq uint64
Time time.Time // zero when unknown
Source string
Level Level
Message string
Raw string
}
Entry is one stored log line. Raw and Message are already redacted.
func ParseHBB ¶
ParseHBB parses one hbbs/hbbr log line. The message is the text after the source location (which stays in Raw).
func ParseSlogJSON ¶
ParseSlogJSON parses a log/slog JSONHandler line: time, level and msg become the entry's fields, the remaining attributes are appended to the message as key=value in their original order.
func ParseSlogText ¶
ParseSlogText parses a log/slog TextHandler (logfmt) line such as
time=2026-10-16T13:27:07.704+03:00 level=INFO msg="starting rdpanel" version=0.2.0
type Filter ¶
type Filter struct {
// Sources limits the sources (empty: all).
Sources []string
// MinLevel drops entries below it.
MinLevel Level
// Q is a case-insensitive substring of the raw line.
Q string
// Before/After select entries with Seq < Before / Seq > After (0: no bound).
Before, After uint64
// Limit caps the result (newest entries win); 0 means no limit.
Limit int
// contains filtered or unexported fields
}
Filter selects entries.
type Level ¶
type Level int8
Level is a normalised log level; higher is more severe.
Levels (trace folds into debug; fatal/panic/critical into error).
func ParseLevel ¶
ParseLevel maps a level name (any case, common aliases) to a Level.
type Page ¶
Page is a query result: Items oldest first; More reports that older matching entries exist (continue with Before = Items[0].Seq).
type Redactor ¶
type Redactor struct {
// contains filtered or unexported fields
}
Redactor removes secrets from log lines before they are stored: known literal values (the hbbs key pair, the JWT and secrets-at-rest keys, the SMTP password) and anything that looks like a credential: JWTs, bearer tokens, Authorization/Cookie headers, password/secret/token/... key=value pairs and JSON fields, long values of a bare "key", and credentials in URLs.
It errs on the side of hiding too much: the raw service output stays available in `docker logs` for operators with host access.
func NewRedactor ¶
NewRedactor returns a redactor that also replaces the given literals.
type SourceInfo ¶
SourceInfo describes one source's ring.
type Subscription ¶
type Subscription struct {
// contains filtered or unexported fields
}
Subscription receives entries written after Subscribe that match its filter (Before/Limit are ignored). Close it when done.
func (*Subscription) Close ¶
func (s *Subscription) Close()
Close unregisters the subscription; it is idempotent.
func (*Subscription) TakeDropped ¶
func (s *Subscription) TakeDropped() uint64
TakeDropped returns and resets the number of entries dropped for this subscriber since the previous call.
type Tailer ¶
type Tailer struct {
Dir string
Source string
Buf *Buffer
// Interval is the poll period (default 300 ms).
Interval time.Duration
Log *slog.Logger
}
Tailer follows an s6-log directory (<Dir>/current plus rotated @*.s / @*.u archives) and feeds its lines into a Buffer.
On start it replays the newest archive and current (the Buffer keeps only the last lines), so the page survives rdpanel restarts; then it polls current for growth and follows s6-log's rotations (current renamed, a new one created) without losing the tail of the old file.