logger

package
v0.29.0 Latest Latest
Warning

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

Go to latest
Published: Jul 24, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package logger provides aiwf's opt-in, default-off diagnostic-log surface: a thin wrapper around log/slog whose configuration comes from three AIWF_LOG* env vars, then aiwf.yaml's logging: block, then a no-op default (ADR-0017).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(cfg Config, w io.Writer) *slog.Logger

New returns a *slog.Logger reflecting cfg. When cfg.Enabled is false — the default (ADR-0017 Decision #2) — the returned logger is backed by slog.DiscardHandler, whose Enabled method reports false for every level: emitting a record does no I/O and no allocation beyond the call itself, and w is never touched.

When cfg.Enabled is true, records at or above cfg.Level are written to w in cfg.Format ("text" or "json"). w's destination (the default XDG-state-home file vs. an explicit override) and its concurrent- append safety are resolved by the caller — this constructor only wraps whatever writer it is given.

func NewRunID

func NewRunID() string

NewRunID returns a fresh, per-invocation correlation id: 16 hex characters from 8 cryptographically random bytes. Not an RFC-4122 UUID — nothing here needs that format, only enough entropy that two concurrent aiwf invocations never collide (ADR-0017 Decision #7), so a stdlib-only crypto/rand read is enough without a new dependency.

func OpenDestination

func OpenDestination(cfg Config, now time.Time, getenv func(string) string) (io.WriteCloser, error)

OpenDestination resolves cfg.Destination into an io.WriteCloser: "stderr" opens os.Stderr, an explicit absolute path opens that file for append, and "" (the default) opens the daily $XDG_STATE_HOME/aiwf/logs/aiwf-YYYY-MM-DD.log file (falling back to ~/.local/state/aiwf/logs when XDG_STATE_HOME is unset), creating its directory and sweeping entries older than 30 days only on this call.

Returns (nil, nil) when cfg.Enabled is false: OpenDestination never touches disk for a disabled config, matching ADR-0017's default-off constraint regardless of what a caller passes.

now and getenv are injected so callers (and tests) control the clock and environment — internal/logger sits below the layering tier that may read the ambient wall clock directly (no-time-now-in-core).

func ResolveConfigWithSources

func ResolveConfigWithSources(getenv func(string) string, yamlCfg YAMLConfig) (Config, Sources, error)

ResolveConfigWithSources is ResolveConfig plus the per-field Sources breakdown. See ResolveConfig's doc comment for the precedence rule and error conditions; this is the same resolution, not a second implementation of it.

func WithVerb

func WithVerb(l *slog.Logger, verb, entity, actor, runID string) *slog.Logger

WithVerb binds verb/entity/actor/run_id onto l (ADR-0017 Decision #7), scrubbing any macOS (/Users/<name>/) or Linux (/home/<name>/) home-directory fragment from verb/entity/actor first. The scrub operates on string content, not provenance, so it catches a leak regardless of source — including a value assembled from os.Args that a caller passed through verb/entity/actor unfiltered. runID (typically logger.NewRunID()) is bound unscrubbed: it is a random hex id, never operator-supplied text, so it can never carry a home-path fragment.

Types

type Config

type Config struct {
	// Enabled is false when neither AIWF_LOG nor aiwf.yaml's
	// logging.level was set — the default-off state (ADR-0017
	// Decision #2). Level/Format/Destination are the zero value
	// when Enabled is false.
	Enabled bool
	Level   slog.Level
	// Format is "text" or "json".
	Format string
	// Destination is "" (use the default XDG-state-home path),
	// "stderr", or an absolute path.
	Destination string
}

Config is the fully-resolved diagnostic-logging configuration for one aiwf invocation.

func ResolveConfig

func ResolveConfig(getenv func(string) string, yamlCfg YAMLConfig) (Config, error)

ResolveConfig applies ADR-0017's env-beats-yaml-beats-default precedence, independently per setting: AIWF_LOG/AIWF_LOG_FORMAT/ AIWF_LOG_FILE each beat the corresponding aiwf.yaml logging: key, which beats the default. getenv is injected so callers (and tests) control the environment read; yamlCfg is the zero value when aiwf.yaml has no logging: block.

Logging is enabled only when a level is supplied from either source — setting only format or destination without a level never opts in, matching ADR-0017 Decision #2's default-off state.

Returns an error when a supplied level or format value (from either source) is not one of the closed sets ADR-0017 defines.

A thin wrapper over ResolveConfigWithSources for callers (most of them) that only need the merged result, not which tier supplied it.

type FieldSource

type FieldSource string

FieldSource names which precedence tier supplied a resolved field's value.

const (
	// SourceEnv means the corresponding AIWF_LOG* env var supplied the value.
	SourceEnv FieldSource = "env"
	// SourceYAML means aiwf.yaml's logging: block supplied the value.
	SourceYAML FieldSource = "yaml"
	// SourceDefault means neither source was set for this field.
	SourceDefault FieldSource = "default"
)

type Sources

type Sources struct {
	Level       FieldSource
	Format      FieldSource
	Destination FieldSource
}

Sources reports, per field, which precedence tier supplied the value ResolveConfigWithSources actually used — for an operator- facing surface (`aiwf doctor`) that explains why a given value won, not just what the merged result is. The zero value (empty FieldSource on every field) is what a disabled Config carries: with no level from either source, format/destination are never resolved at all, so labeling them "default" would misleadingly imply they were consulted.

type YAMLConfig

type YAMLConfig struct {
	Level       string `yaml:"level"`
	Format      string `yaml:"format"`
	Destination string `yaml:"destination"`
}

YAMLConfig is the argument shape ResolveConfig accepts for aiwf.yaml's optional top-level logging: block (ADR-0017 Decision #3). All three fields are optional; a zero value means "absent." internal/config.Logging (config.Logging.ToYAMLConfig()) is what actually decodes a real aiwf.yaml file today — internal/logger can't import internal/config (see internal/config.Logging's doc comment), so this is a separately declared, structurally identical type callers convert into.

Jump to

Keyboard shortcuts

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