Help us shape the future of Go! Take the 2026 Go Developer Survey

logs

package
v0.16.0 Latest Latest
Warning

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

Go to latest
Published: Oct 25, 2026 License: MIT Imports: 15 Imported by: 0

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

View Source
const (
	SourceHBBS  = "hbbs"
	SourceHBBR  = "hbbr"
	SourcePanel = "panel"
	SourceOther = "other"
)

Known sources. Lines from other producers are filed under SourceOther so metric labels stay bounded.

View Source
const DefaultSubscriptionBuffer = 1024

DefaultSubscriptionBuffer is the per-subscriber queue length.

View Source
const MaxCapacity = 100_000

MaxCapacity bounds the per-source capacity.

View Source
const MaxLineBytes = 8 << 10

MaxLineBytes bounds a stored line; longer lines are cut.

View Source
const Redacted = "[REDACTED]"

Redacted replaces secret values.

Variables

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

func NewBuffer(capacity int) *Buffer

NewBuffer creates a buffer keeping up to capacity lines per source (0 disables it; values above MaxCapacity are clamped).

func (*Buffer) AddSecrets

func (b *Buffer) AddSecrets(secrets ...string)

AddSecrets makes the redactor replace these literal values (keys, passwords) in every later line. Short values (< 8 bytes) are ignored.

func (*Buffer) Capacity

func (b *Buffer) Capacity() int

Capacity is the per-source line limit.

func (*Buffer) Enabled

func (b *Buffer) Enabled() bool

Enabled reports whether the buffer stores anything.

func (*Buffer) Query

func (b *Buffer) Query(f Filter) Page

Query returns the newest entries matching f, oldest first.

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

func (b *Buffer) Subscribers() int

Subscribers returns the number of live subscriptions.

func (*Buffer) Write

func (b *Buffer) Write(source, line string)

Write ingests one raw line of source (without the trailing newline). It never blocks on consumers.

func (*Buffer) Writer

func (b *Buffer) Writer(source string) io.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

func ParseHBB(line string) (Entry, bool)

ParseHBB parses one hbbs/hbbr log line. The message is the text after the source location (which stays in Raw).

func ParseSlogJSON

func ParseSlogJSON(line string) (Entry, bool)

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

func ParseSlogText(line string) (Entry, bool)

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.

const (
	LevelDebug Level = iota
	LevelInfo
	LevelWarn
	LevelError
)

Levels (trace folds into debug; fatal/panic/critical into error).

func ParseLevel

func ParseLevel(s string) (Level, bool)

ParseLevel maps a level name (any case, common aliases) to a Level.

func (Level) String

func (l Level) String() string

type Page

type Page struct {
	Items []Entry
	More  bool
}

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

func NewRedactor(secrets ...string) *Redactor

NewRedactor returns a redactor that also replaces the given literals.

func (*Redactor) Redact

func (r *Redactor) Redact(s string) string

Redact returns s with secrets replaced by [REDACTED].

func (*Redactor) With

func (r *Redactor) With(secrets ...string) *Redactor

With returns a copy that also replaces secrets.

type SourceInfo

type SourceInfo struct {
	Name  string
	Lines int
}

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) C

func (s *Subscription) C() <-chan Entry

C delivers the entries.

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.

func (*Tailer) Run

func (t *Tailer) Run(ctx context.Context)

Run follows the directory until ctx ends.

Jump to

Keyboard shortcuts

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