agent: lumotia — Phase B.8 bridge storage events into tracing subscriber

Phase B.8 audit of commits 65abfa2 (Obs-3, span propagation across
spawn boundaries), 8becb1a (audit-trail empty commit for Obs-4/5
absorbed into afbd33d), and d1391b3 (Obs-1/2, drop lumotia_live literal
target). Existing coverage:
  * commands::live::tests::no_lumotia_live_literal_target_in_live_rs
    pins Obs-1/2 — no literal lumotia_live target survives.
  * src-tauri/tests/tracing_appender_smoke.rs::init_tracing_creates_log_file
    pins Obs-4/5 — install_subscriber writes to a rolling lumotia.log.
  * Obs-3 (span propagation) is not directly tested. Verifying that
    `tokio::spawn` / `thread::spawn` children carry the parent span would
    require custom subscriber infrastructure; the commit message
    acknowledges the 4 instrumented sites as canonical correlation
    points + "everything else fans out from them" + "Storage/audio/
    hotkey/MCP crates left uninstrumented in this commit — future
    sweep". Honour the SAFETY-style annotation; do not chase a synthetic
    subscriber test.

One real residual found.

DEFAULT_STDERR_FILTER and DEFAULT_FILE_FILTER in src-tauri/src/lib.rs
both list `lumotia_storage=info` (stderr) / `lumotia_storage=debug`
(file). Operator intent: storage events surface in stderr AND in the
rolling lumotia.log forensic stream that diagnostic-report bundles
attach. The reality: every storage event vanishes.

The storage crate uses `log` crate macros (log::warn! / log::info!),
not tracing. src-tauri/src/lib.rs installs a tracing subscriber but
does NOT install a `tracing-log::LogTracer` bridge, so log-crate events
never reach any tracing layer. There is no other log subscriber wired
either, so the events are silently dropped.

Concrete signals missing from diagnostic reports:
  * Migration progress (info, lines 603 + 639 in migrations.rs) —
    fires on every schema bump on every first-run after upgrade. Used
    to confirm "did the user's migration succeed?".
  * Audio-cleanup warnings (warn) from delete_transcript (database.rs
    line 369) and purge_deleted_transcripts (line 434) — the two
    log lines Rev-3 specifically added so a forensic report could
    confirm whether disk cleanup completed cleanly.

Same forensic blindness Obs-4/5 fixed for the rest of the codebase,
just for the storage subset.

Fix:
  * crates/storage/Cargo.toml: replace `log = "0.4"` with
    `tracing = "0.1"`. Every other crate in the workspace already uses
    `tracing = "0.1"`; storage was the outlier.
  * Replace the 4 `log::*!(target: "lumotia_storage", …)` calls with
    `tracing::*!(target: "lumotia_storage", …)`. Targets unchanged.
  * Reformat the two migration log lines as structured tracing events
    (version + description fields rather than printf-style positional
    interpolation) so they're filterable by EnvFilter directives and
    machine-readable in the forensic stream.

No behaviour change to storage call semantics. Pure logging-pipeline
rewire.

Verification:
  * cargo test -p lumotia-storage --lib
      → 70/70 pass (unchanged — none of the tests depended on the log
      crate).
  * cargo fmt --check → clean.
  * cargo clippy --workspace --all-targets -- -D warnings → clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 19:48:13 +01:00
parent f252c1b50e
commit 813f024cdb
3 changed files with 21 additions and 6 deletions

View File

@@ -21,8 +21,14 @@ tokio = { version = "1", features = ["rt", "sync", "macros"] }
# Serialisation (DailyCompletionCount exposed to frontend via Tauri commands)
serde = { version = "1", features = ["derive"] }
# Logging
log = "0.4"
# Structured logging via `tracing` so storage events bridge into the
# subscriber installed by src-tauri/src/lib.rs::install_subscriber and
# land in both stderr and the rolling lumotia.log forensic stream. The
# storage crate was on the `log` crate up to Phase B.8; without a
# log→tracing bridge (e.g. tracing-log::LogTracer) those events
# vanished even though the EnvFilter directive `lumotia_storage=info`
# advertised them as visible.
tracing = "0.1"
# Structured error derivation for lumotia_storage::Error.
thiserror = "1"

View File

@@ -366,7 +366,7 @@ pub async fn delete_transcript(pool: &SqlitePool, id: &str) -> Result<()> {
if let Some(path) = audio_path.as_deref() {
if let Err(err) = tokio::fs::remove_file(path).await {
if err.kind() != std::io::ErrorKind::NotFound {
log::warn!(
tracing::warn!(
target: "lumotia_storage",
"delete_transcript: failed to remove audio file at {path}: {err}"
);
@@ -431,7 +431,7 @@ pub async fn purge_deleted_transcripts(pool: &SqlitePool, older_than_days: i64)
for path in &audio_paths {
if let Err(err) = tokio::fs::remove_file(path).await {
if err.kind() != std::io::ErrorKind::NotFound {
log::warn!(
tracing::warn!(
target: "lumotia_storage",
"purge_deleted_transcripts: failed to remove audio file at {path}: {err}"
);

View File

@@ -600,7 +600,12 @@ async fn run_migrations_slice(pool: &SqlitePool, migrations: &[(i64, &str, &str)
for (version, description, sql) in migrations {
if *version > current {
log::info!("Running migration {}: {}", version, description);
tracing::info!(
target: "lumotia_storage",
version,
description,
"running migration",
);
let mut tx = pool.begin().await.map_err(|source| Error::Migration {
version: Some(*version),
@@ -636,7 +641,11 @@ async fn run_migrations_slice(pool: &SqlitePool, migrations: &[(i64, &str, &str)
source,
})?;
log::info!("Migration {} complete", version);
tracing::info!(
target: "lumotia_storage",
version,
"migration complete",
);
}
}