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,