ObolNetwork / ObolNetwork/obol-stack

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

Open
#312 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
11
Forks
1
PR merge metrics
No merged PRs in 30d

Description

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:

  • The ServiceOffer controller (#296) lands and shares internal packages with CLI code
  • Server binaries need to share code from internal/ that currently requires *ui.UI
  • Observability requirements grow (structured log aggregation, field-based filtering)

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with cmd/obol/main.go, cmd/x402-verifier/, cmd/x402-buyer/, and the proposed internal/logging/ package. Review existing log.Printf calls, operational warnings such as fixVolumeOwnership, and the ui.UI usage before defining handler setup. Done means server logs use slog, internal operational warnings no longer require *ui.UI, presentation calls remain unchanged, and the slog-versus-ui.UI guidance is documented in CLAUDE.md.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
observability
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.