Skip to content

Logging strategy: unify output and execution logging #76

Closed

Description

@David-Noble-at-work

Summary

The codebase has multiple logging/output mechanisms that need a unified strategy.

Current State

1. CLI Output Functions (internal/cli/output.go)

User-facing messages to stderr, respects --silent:

cli.Note("message")     // [program] [+] message
cli.Warn("message")     // [program] [△] message
cli.Error("message")    // [program] [✖] message
cli.Success("message")  // [program] [✔] message
cli.Failure("message")  // [program] [✖] message + returns error

2. Execution Logger (ctx.Logger io.Writer)

Operations log to an injected writer during graph execution:

fmt.Fprintf(ctx.Logger, "[dry-run] %s install %s\n", pm.Name(), pkg)
fmt.Fprintf(ctx.Logger, "[package] %s install %s\n", pm.Name(), pkg)
fmt.Fprintf(ctx.Logger, "[service] launchctl start %s\n", service)

Files: internal/execution/ops_package.go, internal/execution/ops_service.go

3. Starlark Bindings Output (b.output io.Writer)

Starlark phases log to an output writer:

fmt.Fprintf(b.output, "  [package] Installing %s\n", name)
fmt.Fprintf(b.output, "  [shell] %s\n", command)
fmt.Fprintf(b.output, "  [note] %s\n", msg)
fmt.Fprintf(b.output, "  [warn] %s\n", msg)

File: internal/starlark/bindings.go

4. Audit Logs (s.audit io.Writer)

Security-sensitive operations logged as JSON:

fmt.Fprintln(s.audit, result.JSON())

Files: internal/shell/shell.go, internal/pwsh/pwsh.go

Problems

  1. No log levels - Can't filter by debug/info/warn/error
  2. Inconsistent format - [dry-run], [package], [service], [shell] prefixes vary
  3. No structured option - Can't output JSON logs for machine parsing
  4. Starlark duplicates cli - [note], [warn], [ERROR] in starlark vs cli.Note/Warn/Error
  5. Execution logs bypass cli - ctx.Logger writes don't respect --silent
  6. No verbosity control - -v flag doesn't affect execution logs

Strategy Options

Option A: Minimal - Standardize Format Only

  • Keep current architecture
  • Standardize all log prefixes to [category] format
  • Ensure execution logger respects --silent
  • Lowest effort, doesn't solve log levels

Option B: Log Levels via Interface

  • Define a Logger interface with Debug/Info/Warn/Error methods
  • Replace ctx.Logger io.Writer with ctx.Logger Logger
  • CLI commands set log level based on -v and --silent
  • Medium effort, solves verbosity control

Option C: Unified Logger Abstraction

  • Create a central Logger that wraps cli.Note/Warn/Error/Success
  • All logging goes through this abstraction
  • Supports log levels, respects --silent, consistent format
  • Higher effort, cleanest architecture

Option D: Structured Logging (slog)

  • Use Go 1.21+ log/slog package
  • Supports both text and JSON output
  • Built-in log levels
  • Can emit structured logs for observability
  • Highest effort, best for long-term

Recommendation

Option B (Log Levels via Interface) balances effort vs benefit:

  • Solves verbosity control (-v, --silent)
  • Doesn't require rewriting all logging calls
  • Can evolve to Option C or D later

Files to Consider

  • internal/cli/output.go - existing CLI output functions
  • internal/execution/operation.go - Context with Logger
  • internal/execution/ops_package.go - package operation logs
  • internal/execution/ops_service.go - service operation logs
  • internal/starlark/bindings.go - starlark output

Acceptance Criteria

  • Decide on logging strategy
  • Implement chosen approach
  • All execution logs respect --silent
  • Verbose mode (-v) shows more detail
  • Format is consistent across all log sources

Activity

  1. David-Noble-at-work commented on Feb 6, 2026

    @David-Noble-at-work
    ContributorAuthor

    Additional Finding: Config/CLI Verbosity Mismatch

    The config schema defines a shared verbosity option that is never read by the code.

    Config Schema (devlore-config.json)

    "verbosity": {
      "enum": ["quiet", "normal", "verbose"],
      "description": "Output verbosity level (shared across lore and writ)"
    }

    What Code Actually Reads

    viper.GetBool("writ.verbose")  // CLI flag, not config
    viper.GetBool("lore.verbose")  // CLI flag, not config

    What's Missing

    • verbosity: quiet should set cli.SetSilent(true)
    • verbosity: verbose should enable verbose mode
    • Config option is completely disconnected from behavior

    Recommendation

    Connect the config verbosity option to the CLI behavior in initConfig():

    switch viper.GetString("verbosity") {
    case "quiet":
        cli.SetSilent(true)
    case "verbose":
        // enable verbose logging
    }

    This is an error in judgment - we defined a config option but never implemented it.

  2. David-Noble-at-work commented on Mar 11, 2026

    @David-Noble-at-work
    ContributorAuthor

    Closing — logging is now unified through cli.Note/Warn/Error/Success. The separate ctx.Logger, b.output, and s.audit mechanisms described in this issue no longer exist. Remaining work (log levels, --verbosity control, structured logging) is blocked by #77 and can be tracked there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions