From a7f86b26e4a1a64f6e7ddef0777e1eb81b0e4e2e Mon Sep 17 00:00:00 2001 From: Taylor Eernisse Date: Fri, 13 Feb 2026 22:31:30 -0500 Subject: [PATCH] refactor(core): compact human log format, quieter lock lifecycle, nonzero_summary helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three quality-of-life improvements to reduce log noise and improve readability: 1. logging.rs: Add CompactHumanFormat for stderr tracing output. Replaces the default format with a minimal 'HH:MM:SS LEVEL message key=value' layout — no span context, no full timestamps, no target module. The JSON file log layer is unaffected. This makes watching 'lore sync' output much cleaner. 2. lock.rs: Downgrade AppLock acquire/release messages from info! to debug!. Lock lifecycle events (acquired new, acquired existing, released) are operational bookkeeping that clutters normal output. They remain visible at -vv verbosity for troubleshooting. 3. ingestion/mod.rs: Add nonzero_summary() utility that formats named counters as a compact middle-dot-separated string, suppressing zero values. Produces output like '42 fetched · 3 labels · 12 notes' instead of verbose key=value structured fields. Returns 'nothing to update' when all values are zero. Co-Authored-By: Claude Opus 4.6 --- src/core/lock.rs | 8 ++++---- src/core/logging.rs | 38 ++++++++++++++++++++++++++++++++++++++ src/ingestion/mod.rs | 16 ++++++++++++++++ 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/core/lock.rs b/src/core/lock.rs index 056dad6..b666325 100644 --- a/src/core/lock.rs +++ b/src/core/lock.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::thread; use std::time::Duration; -use tracing::{debug, error, info, warn}; +use tracing::{debug, error, warn}; use uuid::Uuid; use super::db::create_connection; @@ -75,7 +75,7 @@ impl AppLock { "INSERT INTO app_locks (name, owner, acquired_at, heartbeat_at) VALUES (?, ?, ?, ?)", (&self.name, &self.owner, now, now), )?; - info!(owner = %self.owner, "Lock acquired (new)"); + debug!(owner = %self.owner, "Lock acquired (new)"); } Some((existing_owner, acquired_at, heartbeat_at)) => { let is_stale = now - heartbeat_at > self.stale_lock_ms; @@ -85,7 +85,7 @@ impl AppLock { "UPDATE app_locks SET owner = ?, acquired_at = ?, heartbeat_at = ? WHERE name = ?", (&self.owner, now, now, &self.name), )?; - info!( + debug!( owner = %self.owner, previous_owner = %existing_owner, was_stale = is_stale, @@ -125,7 +125,7 @@ impl AppLock { "DELETE FROM app_locks WHERE name = ? AND owner = ?", (&self.name, &self.owner), ) { - Ok(_) => info!(owner = %self.owner, "Lock released"), + Ok(_) => debug!(owner = %self.owner, "Lock released"), Err(e) => error!( owner = %self.owner, error = %e, diff --git a/src/core/logging.rs b/src/core/logging.rs index 0759430..812a835 100644 --- a/src/core/logging.rs +++ b/src/core/logging.rs @@ -1,7 +1,45 @@ +use std::fmt; use std::fs; use std::path::Path; use tracing_subscriber::EnvFilter; +use tracing_subscriber::fmt::format::{FormatEvent, FormatFields}; +use tracing_subscriber::registry::LookupSpan; + +/// Compact stderr formatter: `HH:MM:SS LEVEL message key=value` +/// +/// No span context, no full timestamps, no target — just the essentials. +/// The JSON file log is unaffected (it uses its own layer). +pub struct CompactHumanFormat; + +impl FormatEvent for CompactHumanFormat +where + S: tracing::Subscriber + for<'a> LookupSpan<'a>, + N: for<'a> FormatFields<'a> + 'static, +{ + fn format_event( + &self, + ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>, + mut writer: tracing_subscriber::fmt::format::Writer<'_>, + event: &tracing::Event<'_>, + ) -> fmt::Result { + let now = chrono::Local::now(); + let time = now.format("%H:%M:%S"); + + let level = *event.metadata().level(); + let styled = match level { + tracing::Level::ERROR => console::style("ERROR").red().bold(), + tracing::Level::WARN => console::style(" WARN").yellow(), + tracing::Level::INFO => console::style(" INFO").green(), + tracing::Level::DEBUG => console::style("DEBUG").dim(), + tracing::Level::TRACE => console::style("TRACE").dim(), + }; + + write!(writer, "{time} {styled} ")?; + ctx.format_fields(writer.by_ref(), event)?; + writeln!(writer) + } +} pub fn build_stderr_filter(verbose: u8, quiet: bool) -> EnvFilter { if std::env::var("RUST_LOG").is_ok() { diff --git a/src/ingestion/mod.rs b/src/ingestion/mod.rs index a0c96c5..aa64675 100644 --- a/src/ingestion/mod.rs +++ b/src/ingestion/mod.rs @@ -14,6 +14,22 @@ pub use merge_requests::{ ingest_merge_requests, }; pub use mr_discussions::{IngestMrDiscussionsResult, ingest_mr_discussions}; +/// Format a set of named counters as a compact human-readable summary, +/// filtering out zero values and joining with middle-dot separators. +/// Returns `"nothing to update"` when all values are zero. +pub(crate) fn nonzero_summary(pairs: &[(&str, usize)]) -> String { + let parts: Vec = pairs + .iter() + .filter(|(_, v)| *v > 0) + .map(|(k, v)| format!("{v} {k}")) + .collect(); + if parts.is_empty() { + "nothing to update".to_string() + } else { + parts.join(" \u{b7} ") + } +} + pub use orchestrator::{ DrainResult, IngestMrProjectResult, IngestProjectResult, ProgressCallback, ProgressEvent, ingest_project_issues, ingest_project_issues_with_progress, ingest_project_merge_requests,