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
- func ChainReplaceAttr(fns ...func([]string, slog.Attr) slog.Attr) func([]string, slog.Attr) slog.Attr
- func Logger(ctx context.Context) *slog.Logger
- func Migrate(db *sql.DB) error
- func Prune(db *sql.DB, maxAge time.Duration, minKeepLevel slog.Level) (int64, error)
- func ShortenSource(_ []string, a slog.Attr) slog.Attr
- func WithLogger(ctx context.Context, logger *slog.Logger) context.Context
- type IndexHandler
- func (h *IndexHandler) Close()
- func (h *IndexHandler) Enabled(ctx context.Context, level slog.Level) bool
- func (h *IndexHandler) Handle(ctx context.Context, r slog.Record) error
- func (h *IndexHandler) WithAttrs(attrs []slog.Attr) slog.Handler
- func (h *IndexHandler) WithGroup(name string) slog.Handler
- type LogEntry
- type Rotator
Constants ¶
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 ¶
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 ¶
Migrate creates or upgrades the log_entries table and indexes. Call this once after opening the database and before logging begins.
func Prune ¶
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 ¶
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 ¶
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 ¶
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 ¶
Enabled reports whether the handler handles records at the given level.
func (*IndexHandler) Handle ¶
Handle delegates to the wrapped handler and then asynchronously indexes the record into SQLite.
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 ¶
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 ¶
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 ¶
ActiveFile returns the filename (not full path) of the current active log file. This is always [activeLogName] ("thane.log").