Skip to content

refactor: introduce slog for operational logging, keep ui.UI for presentation #312

@bussyjd

Description

@bussyjd

Context

The codebase currently has two output channels:

  • ui.UI (356 calls) — CLI presentation layer with colors, spinners, prompts, JSON mode, TTY detection (34 methods)
  • log.* (99 calls) — server binaries (x402-verifier, x402-buyer) using stdlib log

These work well for their respective contexts. The gap appears when internal helpers (e.g., fixVolumeOwnership) need to report operational warnings — they currently require threading *ui.UI through the entire call chain even though they have no presentation concerns.

Problem

There's a semantic mismatch between two types of output:

Type Example Right channel
Presentation "Wallet generated ✓", spinner, prompt ui.UI
Operational "chown failed for /data/x: permission denied" Should be slog

Today both go through ui.UI, which means:

  • Presentation layer leaks into deep internals
  • Every function in the chain needs *ui.UI even if it only warns on failure
  • Server binaries can't share internal packages that use ui.UI

Proposal

Introduce log/slog (stdlib since Go 1.21, project requires 1.25+) as the operational logging backbone:

CLI (cmd/obol/main.go)

// Pretty handler for human-friendly terminal output
slog.SetDefault(slog.New(newPrettyHandler(os.Stderr, verbose)))

Server binaries (cmd/x402-verifier/, cmd/x402-buyer/)

// Structured JSON handler for container logs
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, nil)))

Internal helpers

// No *ui.UI threading needed — just call slog
func fixVolumeOwnership(cfg *config.Config, hostPath string) {
    if err := cmd.Run(); err != nil {
        slog.Warn("chown failed", "path", hostPath, "err", err)
    }
}

Keep ui.UI for

  • u.Success(), u.Info() — user-facing status messages
  • u.RunWithSpinner() — progress indicators
  • u.Confirm(), u.Select() — interactive prompts
  • u.JSON() — structured output mode

When to do this

This becomes important when:

Not urgent today — the ui.UI threading approach works. This is a future improvement.

Scope

  • Add internal/logging/ with pretty handler (CLI) and JSON handler (servers)
  • Migrate log.Printf in server binaries → slog.*
  • Migrate operational warnings in internals (e.g., fixVolumeOwnership) → slog.Warn
  • Keep all 356+ u.* calls for presentation output
  • Document "when to use slog vs ui.UI" in CLAUDE.md

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions