use anyhow::Result;
use kerf::{Config, EventType, Kerf, Level, Match, MatcherSet, StatsConfig};
use std::fs::{File, OpenOptions};
use std::io::{self, Write};
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use tokio::sync::mpsc;
struct LogMessage {
destination: String,
message: Arc<kerf::Event>,
}
struct StatsLogger {
file_loggers: std::collections::HashMap<String, Arc<Mutex<File>>>,
log_counts: std::collections::HashMap<String, usize>,
}
impl StatsLogger {
fn new(log_dir: PathBuf) -> io::Result<Self> {
std::fs::create_dir_all(&log_dir)?;
let mut file_loggers = std::collections::HashMap::new();
let mut log_counts = std::collections::HashMap::new();
for dest in ["captured", "silenced", "dropped"] {
let file_path = log_dir.join(format!("{dest}.log"));
let file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&file_path)?;
file_loggers.insert(dest.to_string(), Arc::new(Mutex::new(file)));
log_counts.insert(dest.to_string(), 0);
}
log_counts.insert("stdout".to_string(), 0);
Ok(Self {
file_loggers,
log_counts,
})
}
fn log_message(&mut self, destination: &str, event: Arc<kerf::Event>) -> io::Result<()> {
let formatted = format!(
"[{}] {} - {}\n",
event.level,
event.module_path.as_deref().unwrap_or("unknown"),
event.message
);
match destination {
"stdout" => {
print!("CAPTURED: {formatted}");
*self.log_counts.entry("stdout".to_string()).or_insert(0) += 1;
}
_ => {
if let Some(file_lock) = self.file_loggers.get(destination) {
let mut file = file_lock.lock().unwrap();
file.write_all(formatted.as_bytes())?;
file.flush()?;
*self.log_counts.entry(destination.to_string()).or_insert(0) += 1;
} else {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("No logger for destination: {destination}"),
));
}
}
}
Ok(())
}
fn print_summary(&self) {
println!("\n=== LOGGER SUMMARY ===");
for (dest, count) in &self.log_counts {
println!("{dest}: {count} events");
}
println!("=======================\n");
}
}
#[tokio::main]
async fn main() -> Result<()> {
let log_dir = tempfile::tempdir()?.keep();
println!("=== KERF STATISTICS DEMONSTRATION ===");
println!("Log directory: {}", log_dir.display());
println!("======================================\n");
let stats_config = StatsConfig {
track_by_location: true,
track_by_module: true,
track_by_level: true,
max_locations: 1000,
max_modules: 100,
};
let mut capture_matcher = MatcherSet::empty();
capture_matcher.add_matcher(Match::info().module_pattern("demo::*")); capture_matcher.add_matcher(Match::warn().all_modules()); capture_matcher.add_matcher(Match::error().all_modules());
let mut silence_matcher = MatcherSet::empty();
silence_matcher.add_matcher(Match::debug().module_pattern("demo::*")); silence_matcher.add_matcher(Match::debug().exclude().module_pattern("demo::noisy"));
let config = Config::empty()
.with_tab("main_capture", capture_matcher)
.with_tab("debug_with_silence", silence_matcher)
.with_stats(stats_config);
let tracer = Kerf::init(config)?;
let (tx, mut rx) = mpsc::unbounded_channel::<LogMessage>();
let logger = Arc::new(Mutex::new(StatsLogger::new(log_dir.clone())?));
let logger_clone = Arc::clone(&logger);
tokio::spawn(async move {
while let Some(LogMessage {
destination,
message,
}) = rx.recv().await
{
let mut logger = logger_clone.lock().unwrap();
if let Err(e) = logger.log_message(&destination, message) {
eprintln!("Error logging to {destination}: {e}");
}
}
});
let tx_captured = tx.clone();
tracer
.set_callback(move |event, _tab_names| {
let _ = tx_captured.send(LogMessage {
destination: "stdout".to_string(),
message: Arc::clone(&event),
});
let _ = tx_captured.send(LogMessage {
destination: "captured".to_string(),
message: Arc::clone(&event),
});
})?
.await??;
let tx_silenced = tx.clone();
tracer
.set_silenced_callback(move |event, silencers| {
println!(
"SILENCED by {:?}: [{}] {} - {}",
silencers,
event.level,
event.module_path.as_deref().unwrap_or("unknown"),
event.message
);
let _ = tx_silenced.send(LogMessage {
destination: "silenced".to_string(),
message: Arc::clone(&event),
});
})?
.await??;
let tx_dropped = tx.clone();
tracer
.set_dropped_callback(move |event| {
println!(
"DROPPED: [{}] {} - {}",
event.level,
event.module_path.as_deref().unwrap_or("unknown"),
event.message
);
let _ = tx_dropped.send(LogMessage {
destination: "dropped".to_string(),
message: Arc::clone(&event),
});
})?
.await??;
println!("Starting event generation...\n");
tokio::spawn(async move {
demo::core::generate_events().await;
demo::database::generate_events().await;
demo::network::generate_events().await;
demo::noisy::generate_events().await;
external::service::generate_events().await;
external::utils::generate_events().await;
for i in 0..5 {
tracing::info!(target: "demo::mixed", "Mixed info event {}", i);
tracing::warn!(target: "demo::mixed", "Mixed warning event {}", i);
if i % 2 == 0 {
tracing::error!(target: "demo::mixed", "Mixed error event {}", i);
}
if i % 3 == 0 {
tracing::debug!(target: "demo::mixed", "Mixed debug event {}", i);
tracing::trace!(target: "demo::mixed", "Mixed trace event {}", i);
}
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
println!("\nEvent generation complete!");
});
tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
print_comprehensive_stats(&tracer).await?;
let logger = logger.lock().unwrap();
logger.print_summary();
println!("Stats demonstration complete!");
println!("Check log files in: {}", log_dir.display());
Ok(())
}
async fn print_comprehensive_stats(tracer: &Kerf) -> Result<()> {
if let Ok(stats_rx) = tracer.get_stats() {
if let Ok(Some(snapshot)) = stats_rx.await {
println!("\n=== COMPREHENSIVE STATISTICS REPORT ===");
println!("Total tracked entries: {}", snapshot.total_entries);
println!("\n--- EVENT TYPE SUMMARY ---");
let mut captured_total = 0;
let mut silenced_total = 0;
let mut dropped_total = 0;
for ((_level, event_type), count) in &snapshot.level_event_counts {
match event_type {
EventType::Captured => captured_total += count,
EventType::Silenced => silenced_total += count,
EventType::Dropped => dropped_total += count,
}
}
println!("📈 Captured: {captured_total}");
println!("🔇 Silenced: {silenced_total}");
println!("🗑️ Dropped: {dropped_total}");
println!(
"📊 Total: {}",
captured_total + silenced_total + dropped_total
);
println!("\n--- LEVEL BREAKDOWN ---");
for level in [
Level::ERROR,
Level::WARN,
Level::INFO,
Level::DEBUG,
Level::TRACE,
] {
let captured = snapshot
.level_event_counts
.get(&(level, EventType::Captured))
.unwrap_or(&0);
let silenced = snapshot
.level_event_counts
.get(&(level, EventType::Silenced))
.unwrap_or(&0);
let dropped = snapshot
.level_event_counts
.get(&(level, EventType::Dropped))
.unwrap_or(&0);
let total = captured + silenced + dropped;
if total > 0 {
println!(
"{level:5}: {captured:3} captured, {silenced:3} silenced, {dropped:3} dropped (total: {total})"
);
}
}
println!("\n--- MODULE STATISTICS ---");
let mut modules: Vec<_> = snapshot.module_stats.iter().collect();
modules.sort_by_key(|(name, _)| name.to_string());
for (module, stats) in modules {
let captured = stats.get_total_for_type(EventType::Captured);
let silenced = stats.get_total_for_type(EventType::Silenced);
let dropped = stats.get_total_for_type(EventType::Dropped);
println!(
"{module:20}: {captured:3} captured, {silenced:3} silenced, {dropped:3} dropped"
);
}
println!("\n--- TOP ACTIVE LOCATIONS ---");
let mut locations: Vec<_> = snapshot.location_stats.iter().collect();
locations.sort_by(|(_, a), (_, b)| {
let a_total = a.get_total_for_type(EventType::Captured)
+ a.get_total_for_type(EventType::Silenced)
+ a.get_total_for_type(EventType::Dropped);
let b_total = b.get_total_for_type(EventType::Captured)
+ b.get_total_for_type(EventType::Silenced)
+ b.get_total_for_type(EventType::Dropped);
b_total.cmp(&a_total)
});
for (location, stats) in locations.iter().take(10) {
let captured = stats.get_total_for_type(EventType::Captured);
let silenced = stats.get_total_for_type(EventType::Silenced);
let dropped = stats.get_total_for_type(EventType::Dropped);
let total = captured + silenced + dropped;
if total > 0 {
println!("{location:30}: {total:3} total events");
}
}
println!("\n--- RAW STATS SUMMARY ---");
println!("Raw stat entries: {}", snapshot.raw_stats.len());
println!("Total counters: {}", snapshot.total_counters.len());
println!("\n--- CONFIGURATION ---");
println!("Track by location: {}", snapshot.config.track_by_location);
println!("Track by module: {}", snapshot.config.track_by_module);
println!("Track by level: {}", snapshot.config.track_by_level);
println!("Max locations: {}", snapshot.config.max_locations);
println!("Max modules: {}", snapshot.config.max_modules);
println!("Locations tracked: {}", snapshot.location_count);
println!("Modules tracked: {}", snapshot.module_count);
println!("=====================================\n");
}
}
Ok(())
}
mod demo {
pub mod core {
use tracing::{debug, error, info, trace, warn};
pub async fn generate_events() {
info!("Core module initializing");
debug!("Core debug: loading configuration");
trace!("Core trace: detailed initialization steps");
warn!("Core warning: deprecated feature used");
error!("Core error: failed to load optional plugin");
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
}
}
pub mod database {
use tracing::{debug, error, info, warn};
pub async fn generate_events() {
info!("Database connection established");
debug!("Database debug: connection pool size = 10");
warn!("Database warning: slow query detected");
error!("Database error: connection timeout");
info!("Database transaction completed");
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
}
}
pub mod network {
use tracing::{debug, error, info, trace, warn};
pub async fn generate_events() {
info!("Network module starting");
debug!("Network debug: binding to port 8080");
trace!("Network trace: socket configuration");
warn!("Network warning: high latency detected");
error!("Network error: failed to bind port 443");
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
}
}
pub mod noisy {
use tracing::{debug, info, trace};
pub async fn generate_events() {
info!("Noisy module info (should be captured)");
debug!("Noisy module debug (should be silenced)");
debug!("Another noisy debug (should be silenced)");
trace!("Noisy trace (should be dropped - too low level)");
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
}
}
}
mod external {
pub mod service {
use tracing::{debug, error, info, trace, warn};
pub async fn generate_events() {
info!("External service info (should be dropped)");
debug!("External service debug (should be dropped)");
trace!("External service trace (should be dropped)");
warn!("External service warning (should be captured)");
error!("External service error (should be captured)");
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
}
}
pub mod utils {
use tracing::{debug, info, trace, warn};
pub async fn generate_events() {
info!("External utils info (should be dropped)");
debug!("External utils debug (should be dropped)");
trace!("External utils trace (should be dropped)");
warn!("External utils warning (should be captured)");
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
}
}
}