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.
116 lines
3.5 KiB
Markdown
116 lines
3.5 KiB
Markdown
# 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`**
|
|
|
|
```rust
|
|
//! 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;`:
|
|
|
|
```rust
|
|
mod cli;
|
|
mod logging;
|
|
```
|
|
|
|
- [ ] **Step 3: Wire the formatter into tracing initialization**
|
|
|
|
In `backend/src/main.rs`, replace line 30:
|
|
|
|
```rust
|
|
// 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**
|
|
|
|
```bash
|
|
git add backend/src/logging.rs backend/src/main.rs
|
|
git commit -m "feat: add source file:line to WARN and ERROR log lines"
|
|
```
|