Skip to content

feat(obs): migrate from eprintln to tracing for structured logging #513

Description

@BryanFRD

Problem

All logging today is `println!` / `eprintln!` (~120 call sites across the codebase). This works for terminal humans but blocks several capabilities:

  • No log levels. Verbose mode is a boolean; can't say "DEBUG level for monorepo orchestrator only, INFO for git ops".
  • No JSON output for CI ingestion. Users running ferrflow in a release workflow want to ship the run to Datadog / Loki / CloudWatch. Today they get raw text with ANSI color codes mixed in.
  • No structured fields. `println!("Pushed and verified on {remote}/{branch}")` is unparseable downstream; users want to filter on `remote=origin branch=main`.
  • OpenTelemetry integration impossible. Customers asking for OTLP export of release activity can't be served.
  • Test-time stdout assertions are fragile (already burned us with the bench output parsing).

Proposal

Migrate to `tracing` + `tracing-subscriber`.

```rust
// src/main.rs
fn init_logging(verbose: bool, format: LogFormat) {
let filter = if verbose { "ferrflow=debug" } else { "ferrflow=info" };
let registry = tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::new(filter));
match format {
LogFormat::Human => registry.with(tracing_subscriber::fmt::layer().compact()).init(),
LogFormat::Json => registry.with(tracing_subscriber::fmt::layer().json()).init(),
}
}
```

Replace the existing prints incrementally:

  • `eprintln!("warning: …")` → `tracing::warn!(…)`
  • `println!("✓ Pushed …")` → `tracing::info!(remote, branch, "pushed and verified")`
  • The pretty colored output (✓ / ✗ / →) stays in the human `fmt` layer's prefix.

```bash
ferrflow check # human, colors, current behavior
ferrflow check --log-format json # one JSON line per event
RUST_LOG=ferrflow::git=trace ferrflow release # debug just the git layer
```

Bonus once it lands

  • `#[tracing::instrument]` on `run_release_logic`, `TagIndex::build`, `fetch_and_rebase` — automatic span timing for the `--timing` flag (feat(cli): --timing flag for per-stage breakdown #478) for free.
  • Future OTLP export via `tracing-opentelemetry` — one feature flag away.

Scope / migration plan

Do this in stages, not one giant PR:

  1. Add deps + init in main, default human layer matches existing output
  2. Migrate `src/git/` call sites (smallest blast radius)
  3. Migrate `src/monorepo/` (largest)
  4. Migrate `src/forge/` + `src/hooks/`
  5. Add `--log-format json` CLI flag
  6. Optional follow-up: OTLP

Acceptance

  • All `eprintln!` / `println!` migrated to `tracing` macros (except `println!(json)` for `--json` outputs, which is data not logs)
  • `--log-format json` emits one event per line, schema documented in `docs/observability/logs.md`
  • Human output (without flag) is visually identical to today's
  • No regression on `micro-bench` (`tracing` filtering is near-zero cost when filters miss)

Related #478 (--timing flag — gets free from `#[instrument]`).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementImprovement to existing feature

    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