use anyhow::Result;
use kerf::{ArcEvent, Config, Kerf, Match, MatcherSet};
use std::{collections::HashMap, sync::Arc};
use tokio::sync::{Mutex, mpsc};
use tracing::{Level, debug, error, info, warn};
enum LogCommand {
AddMessage {
tab_names: Vec<String>,
event: ArcEvent,
},
ClearTab(String),
}
struct LogTab {
messages: Vec<ArcEvent>,
formatted_messages: Vec<String>, }
struct StdoutTracerDemo {
tabs: HashMap<String, LogTab>,
tab_order: Vec<String>,
}
#[tokio::main]
async fn main() -> Result<()> {
let config = Config::from_tabs([
("main", Match::debug().all_modules()),
(
"database",
Match::debug().include().module_pattern("*database*"),
),
("errors", Match::error().all_modules()),
("warnings", Match::warn().all_modules()),
]);
let tracer = Kerf::init(config)?;
let (tx, mut rx) = mpsc::unbounded_channel::<LogCommand>();
let logger = Arc::new(Mutex::new(StdoutTracerDemo::new()));
{
let mut l = logger.lock().await;
l.add_tab("main");
l.add_tab("database");
l.add_tab("errors");
l.add_tab("warnings");
l.add_tab("silenced"); l.add_tab("dropped"); }
let logger_clone = Arc::clone(&logger);
tokio::spawn(async move {
while let Some(cmd) = rx.recv().await {
let mut logger = logger_clone.lock().await;
match cmd {
LogCommand::AddMessage { tab_names, event } => {
for tab in tab_names {
logger.add_message(&tab, event.clone());
}
}
LogCommand::ClearTab(tab) => {
logger.clear_tab(&tab);
}
}
drop(logger);
}
});
let tx_clone = tx.clone();
tracer
.set_callback(move |event, tab_names| {
if let Err(e) = tx_clone.send(LogCommand::AddMessage {
tab_names: tab_names.iter().map(|s| s.to_string()).collect(),
event: Arc::clone(&event),
}) {
eprintln!("Failed to send log event: {e}");
}
})?
.await??;
let tx_clone = tx.clone();
tracer
.set_silenced_callback(move |event, _silencers| {
if let Err(e) = tx_clone.send(LogCommand::AddMessage {
tab_names: vec!["silenced".to_string()],
event,
}) {
eprintln!("Failed to send silenced event: {e}");
}
})?
.await??;
let tx_clone = tx.clone();
tracer
.set_dropped_callback(move |event| {
if let Err(e) = tx_clone.send(LogCommand::AddMessage {
tab_names: vec!["dropped".to_string()],
event,
}) {
eprintln!("Failed to send dropped event: {e}");
}
})?
.await??;
database::do_event();
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
let do_output = || async {
{
let logger = logger.lock().await;
logger.render().await;
}
display_stats(&tracer).await;
};
do_output().await;
update_tab_level(&tracer, &tx, "database", Level::TRACE, true).await?;
do_output().await;
update_tab_level(&tracer, &tx, "database", Level::ERROR, true).await?;
do_output().await;
update_tab_level(&tracer, &tx, "database", Level::TRACE, false).await?;
update_tab_level(&tracer, &tx, "main", Level::WARN, true).await?;
do_output().await;
update_tab_level(&tracer, &tx, "database", Level::WARN, true).await?;
update_tab_level(&tracer, &tx, "main", Level::DEBUG, true).await?;
update_tab_level(&tracer, &tx, "errors", Level::ERROR, true).await?;
do_output().await;
println!("\n=== FINAL TRACER STATS ===");
display_stats(&tracer).await;
Ok(())
}
impl StdoutTracerDemo {
fn new() -> Self {
Self {
tabs: HashMap::new(),
tab_order: Vec::new(),
}
}
fn add_tab(&mut self, tab: &str) {
let tab_name = tab.to_string();
self.tabs.insert(
tab_name.clone(),
LogTab {
messages: Vec::new(),
formatted_messages: Vec::new(),
},
);
if !self.tab_order.contains(&tab_name) {
self.tab_order.push(tab_name);
}
}
fn add_message(&mut self, tab: &str, event: ArcEvent) {
if let Some(tab_data) = self.tabs.get_mut(tab) {
let formatted = match tab {
"silenced" => format!("[{}] [SILENCED] {}", event.id, event.format()),
"dropped" => {
format!("[{}] [DROPPED] {}", event.id, event.format())
}
_ => format!("[{}] {}", event.id, event.format()),
};
tab_data.messages.push(event);
tab_data.formatted_messages.push(formatted);
}
}
fn clear_tab(&mut self, tab: &str) {
if let Some(tab_data) = self.tabs.get_mut(tab) {
tab_data.messages.clear();
tab_data.formatted_messages.clear();
}
}
async fn render(&self) {
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
println!("\n========================");
println!("=== LOGGER STATE ===");
println!("========================");
for tab in &self.tab_order {
if let Some(tab_data) = self.tabs.get(tab) {
println!(
"Tab: {} ({} messages)",
tab,
tab_data.formatted_messages.len()
);
for msg in &tab_data.formatted_messages {
println!("{msg}");
}
println!("-----------------------\n");
}
}
}
}
mod database {
use super::*;
use tracing::{Level, span, trace};
pub fn do_event() {
let db_span = span!(Level::INFO, "database");
let _enter = db_span.enter();
trace!("do_event started!");
info!("Database connection established");
trace!("Connection details initialized");
debug!("Database query executed: SELECT * FROM users");
debug!("Query completed in 5ms");
drop(_enter);
trace!("Exited database span");
warn!("System running low on memory");
trace!("Memory warning logged");
error!("Failed to process request");
trace!("Error logged and reported");
}
}
async fn display_stats(_tracer: &Kerf) {
println!("\n=== TRACER METRICS ===");
println!("=====================\n");
}
async fn update_tab_matcher(
tracer: &Kerf,
command_tx: &mpsc::UnboundedSender<LogCommand>,
tab: &str,
filter_set: MatcherSet,
description: &str,
) -> Result<()> {
println!("\n~~~ UPDATING FILTER: {tab} ~~~");
println!("{description}");
command_tx.send(LogCommand::ClearTab(tab.to_string()))?;
tracer.update_tab(tab, filter_set)?.await??;
database::do_event();
println!("Filter updated successfully!\n");
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
Ok(())
}
async fn update_tab_level(
tracer: &Kerf,
command_tx: &mpsc::UnboundedSender<LogCommand>,
tab: &str,
level: Level,
include: bool,
) -> Result<()> {
let module_patterns = match tab {
"database" => vec!["*database*".to_string()],
_ => vec!["*".to_string()], };
let filter_set = if include {
MatcherSet::from_matcher(Match::new(level).module_patterns(&module_patterns))
} else {
MatcherSet::from_matchers([
Match::trace().all_modules(),
Match::new(level)
.exclude()
.module_patterns(&module_patterns),
])
};
let inclusion = if include { "INCLUDE" } else { "EXCLUDE" };
let description =
format!("Updating {tab} tab to {inclusion} level {level} {module_patterns:?}",);
update_tab_matcher(tracer, command_tx, tab, filter_set, &description).await
}