logging

package
v0.8.2 Latest Latest
Warning

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

Go to latest
Published: Mar 10, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package logging provides self-managed log file rotation, context-propagated structured logging, and a queryable SQLite index for Thane.

The Rotator implements io.WriteCloser and handles daily log rotation with optional gzip compression of previous days' files. Log files are organized by date:

logs/
  thane.log                    # current, active
  thane-2026-03-09.log.gz     # rotated daily, compressed
  thane-2026-03-08.log.gz
  ...never deleted...

The WithLogger / Logger helpers thread a *slog.Logger through context.Context so that every log line in a request chain automatically carries trace fields (request_id, session, conversation, subsystem, iteration index).

ShortenSource strips the module prefix from source file paths when slog's AddSource option is enabled, keeping log lines compact.

The IndexHandler wraps any slog.Handler and simultaneously indexes every log record into a SQLite database. Promoted fields (request_id, session_id, conversation_id, subsystem, tool, model) are extracted into indexed columns for fast queries; remaining attributes go into a JSON catch-all. Use Prune to manage index retention while preserving the raw log files as the canonical record.

Index

Constants

View Source
const (
	SubsystemAgent     = "agent"
	SubsystemDelegate  = "delegate"
	SubsystemSignal    = "signal"
	SubsystemScheduler = "scheduler"
	SubsystemMetacog   = "metacog"
	SubsystemAPI       = "api"
)

Standard subsystem names for structured log filtering.

Variables

This section is empty.

Functions

func ChainReplaceAttr

func ChainReplaceAttr(fns ...func([]string, slog.Attr) slog.Attr) func([]string, slog.Attr) slog.Attr

ChainReplaceAttr composes multiple slog.HandlerOptions.ReplaceAttr functions into one. Each function is applied in order, so earlier functions can transform the attribute before later ones see it.

func Logger

func Logger(ctx context.Context) *slog.Logger

Logger extracts the *slog.Logger stored by WithLogger. If no logger is present (or nil was stored), it returns slog.Default as a safe fallback so callers never need nil checks.

func Migrate

func Migrate(db *sql.DB) error

Migrate creates or upgrades the log_entries table and indexes. Call this once after opening the database and before logging begins.

func Prune

func Prune(db *sql.DB, maxAge time.Duration, minKeepLevel slog.Level) (int64, error)

Prune deletes log index entries older than maxAge whose level is strictly below minKeepLevel. For example, passing slog.LevelInfo prunes DEBUG and TRACE entries while keeping INFO, WARN, and ERROR. Returns the number of rows deleted.

func ShortenSource

func ShortenSource(_ []string, a slog.Attr) slog.Attr

ShortenSource is a slog.HandlerOptions.ReplaceAttr function that strips the module prefix from source file and function paths when AddSource is enabled. This yields compact output like "internal/agent/loop.go:730" and "internal/agent.(*Loop).Run" instead of fully qualified module paths. Requires -trimpath in the build flags so Go embeds module-relative paths rather than absolute filesystem paths.

func WithLogger

func WithLogger(ctx context.Context, logger *slog.Logger) context.Context

WithLogger returns a copy of ctx carrying logger. Retrieve it with Logger. Typically called at request entry points to inject a logger pre-enriched with trace fields (request_id, subsystem, etc.), then again at iteration boundaries to add the iteration index.

Types

type IndexHandler

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

IndexHandler is an slog.Handler that wraps another handler and simultaneously indexes every log record into a SQLite database. The wrapped handler produces the canonical raw log output (file and/or stdout); the SQLite index enables fast queries by time, level, request ID, subsystem, etc.

Writes to SQLite happen asynchronously via a buffered channel so that logging never blocks on database I/O. The channel is drained by a single background goroutine started by NewIndexHandler. Call IndexHandler.Close to flush pending entries and stop the background goroutine.

func NewIndexHandler

func NewIndexHandler(inner slog.Handler, db *sql.DB, rotator *Rotator) *IndexHandler

NewIndexHandler wraps inner with a SQLite indexing handler. The db must be an open SQLite connection (typically from [database.Open]). If rotator is non-nil, each entry records the raw log filename and line number for back-linking.

The caller must call IndexHandler.Close on shutdown to flush pending entries and release the background goroutine.

func (*IndexHandler) Close

func (h *IndexHandler) Close()

Close flushes pending index entries and stops the background goroutine. It is safe to call multiple times.

func (*IndexHandler) Enabled

func (h *IndexHandler) Enabled(ctx context.Context, level slog.Level) bool

Enabled reports whether the handler handles records at the given level.

func (*IndexHandler) Handle

func (h *IndexHandler) Handle(ctx context.Context, r slog.Record) error

Handle delegates to the wrapped handler and then asynchronously indexes the record into SQLite.

func (*IndexHandler) WithAttrs

func (h *IndexHandler) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs returns a new handler with the given attributes pre-set.

func (*IndexHandler) WithGroup

func (h *IndexHandler) WithGroup(name string) slog.Handler

WithGroup returns a new handler with the given group name.

type LogEntry

type LogEntry struct {
	ID         int64
	Timestamp  time.Time
	Level      string
	Msg        string
	Subsystem  string
	Tool       string
	Model      string
	Attrs      string
	SourceFile string
	SourceLine int
}

LogEntry is an exported representation of a log index row suitable for display in the web dashboard.

func QueryBySession

func QueryBySession(db *sql.DB, sessionID, level, subsystem string, limit int) ([]LogEntry, error)

QueryBySession returns log entries matching the given session ID, ordered by timestamp ascending (chronological). When limit is positive, only the most recent limit entries are returned — the query selects newest-first and then reverses in Go so callers always receive chronological order. Optional filters narrow by level and/or subsystem.

type Rotator

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

Rotator is a file-backed io.WriteCloser that rotates log files at date boundaries. Each day's log is written to thane.log; on rotation the previous day's file is renamed to thane-YYYY-MM-DD.log (and optionally gzip-compressed to thane-YYYY-MM-DD.log.gz).

Rotator is safe for concurrent use.

func Open

func Open(dir string, compress bool) (*Rotator, error)

Open creates a Rotator that writes to dir/thane.log. The directory is created if it does not exist. If compress is true, rotated files are gzip-compressed.

On open, if thane.log already exists and belongs to a previous date (determined by the file's last modification time), it is rotated immediately.

func (*Rotator) ActiveFile

func (r *Rotator) ActiveFile() string

ActiveFile returns the filename (not full path) of the current active log file. This is always [activeLogName] ("thane.log").

func (*Rotator) Close

func (r *Rotator) Close() error

Close closes the active log file.

func (*Rotator) LineCount

func (r *Rotator) LineCount() int

LineCount returns the number of lines written to the current active file since it was opened (or last rotated). Caller must hold no lock; the count is read under the internal mutex.

func (*Rotator) Write

func (r *Rotator) Write(p []byte) (int, error)

Write writes p to the active log file, rotating first if the date has changed since the last write.

Jump to

Keyboard shortcuts

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