You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ai_synth/docs/superpowers/plans/2026-03-23-error-location-l...

3.5 KiB

Error Location Logging — Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Add source file and line number to WARN and ERROR log lines, leaving INFO/DEBUG unchanged.

Architecture: Custom FormatEvent implementation that conditionally appends [file:line] based on log level. One new file (logging.rs), one modified line in main.rs.

Tech Stack: tracing_subscriber::fmt::FormatEvent, tracing::Level

Spec: docs/superpowers/specs/2026-03-23-error-location-logging-design.md


Task 1: Create the custom formatter

Files:

  • Create: backend/src/logging.rs

  • Modify: backend/src/main.rs:6,11,30

  • Step 1: Create backend/src/logging.rs

//! Custom log formatter that includes source file and line for WARN/ERROR events.

use std::fmt;
use tracing::{Event, Level, Subscriber};
use tracing_subscriber::fmt::format::{self, FormatEvent, FormatFields};
use tracing_subscriber::fmt::FmtContext;
use tracing_subscriber::registry::LookupSpan;

/// Event formatter that appends `[file:line]` to WARN and ERROR log lines.
///
/// INFO, DEBUG, and TRACE events use the default format without source location.
pub struct ErrorLocationFormat;

impl<S, N> FormatEvent<S, N> for ErrorLocationFormat
where
    S: Subscriber + for<'a> LookupSpan<'a>,
    N: for<'a> FormatFields<'a> + 'static,
{
    fn format_event(
        &self,
        ctx: &FmtContext<'_, S, N>,
        mut writer: format::Writer<'_>,
        event: &Event<'_>,
    ) -> fmt::Result {
        let metadata = event.metadata();
        let level = metadata.level();
        let timestamp = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.6fZ");

        // Write: timestamp + level + target
        write!(writer, "{} {:>5} {}", timestamp, level, metadata.target())?;

        // Append [file:line] for WARN and ERROR only
        if *level <= Level::WARN {
            if let (Some(file), Some(line)) = (metadata.file(), metadata.line()) {
                write!(writer, " [{}:{}]", file, line)?;
            }
        }

        write!(writer, ": ")?;

        // Write the event fields (message, structured fields)
        ctx.format_fields(writer.by_ref(), event)?;

        writeln!(writer)
    }
}
  • Step 2: Add mod logging; to main.rs

In backend/src/main.rs, add the module declaration alongside mod cli;:

mod cli;
mod logging;
  • Step 3: Wire the formatter into tracing initialization

In backend/src/main.rs, replace line 30:

// Before:
fmt().with_env_filter(filter).init();

// After:
fmt()
    .with_env_filter(filter)
    .event_format(logging::ErrorLocationFormat)
    .init();
  • Step 4: Run tests to verify nothing breaks

Run: cd backend && cargo test --lib Expected: 338 tests pass (the formatter is only used in main.rs binary, not in lib tests)

  • Step 5: Run the binary to verify output format

Run: cd backend && RUST_LOG=debug cargo run -- serve 2>&1 | head -10 Expected: INFO/DEBUG lines have no [file:line], but any WARN/ERROR would show it.

To force an error, run without DATABASE_URL: Run: cd backend && RUST_LOG=debug cargo run -- serve 2>&1 | head -5 Expected: An ERROR line with [file:line] showing the source location.

  • Step 6: Commit
git add backend/src/logging.rs backend/src/main.rs
git commit -m "feat: add source file:line to WARN and ERROR log lines"