#[cfg(test)]
mod test_tracer {
use anyhow::Result;
use kerf::{
ArcEvent, Config, Event, EventType, Kerf, Level, Match, MatcherSet, StatsConfig, Tab,
};
use std::{collections::HashMap, sync::Arc, time::Duration};
use tokio::sync::Mutex;
fn create_test_event(
id: u64,
level: Level,
event: &str,
module_path: Option<&str>,
file: Option<&str>,
line: Option<u32>,
span_name: Option<&str>,
) -> ArcEvent {
let mut event = Event {
id,
timestamp: chrono::Local::now(),
level,
target: "test_target".to_string(),
name: "test_event".to_string(),
module_path: module_path.map(|s| s.to_string()),
file: file.map(|s| s.to_string()),
line,
message: event.to_string(),
fields: HashMap::new(),
span_name: span_name.map(|s| s.to_string()),
span_hierarchy: span_name.map(|s| s.to_string()), };
event
.fields
.insert("test_field".to_string(), "test_value".to_string());
Arc::new(event)
}
#[allow(clippy::too_many_arguments)]
fn create_test_event_with_target(
id: u64,
level: Level,
event: &str,
module_path: Option<&str>,
target: &str,
file: Option<&str>,
line: Option<u32>,
span_name: Option<&str>,
) -> ArcEvent {
let mut event = Event {
id,
timestamp: chrono::Local::now(),
level,
target: target.to_string(),
name: "test_event".to_string(),
module_path: module_path.map(|s| s.to_string()),
file: file.map(|s| s.to_string()),
line,
message: event.to_string(),
fields: HashMap::new(),
span_name: span_name.map(|s| s.to_string()),
span_hierarchy: span_name.map(|s| s.to_string()),
};
event
.fields
.insert("test_field".to_string(), "test_value".to_string());
Arc::new(event)
}
async fn send_event(tracer: &Kerf, event: ArcEvent) {
let _ = tracer._get_sender_for_testing().send(event);
tokio::time::sleep(Duration::from_millis(10)).await;
}
async fn get_stats_counts(tracer: &Kerf) -> (u64, u64, u64) {
if let Ok(stats_rx) = tracer.get_stats() {
if let Ok(Some(snapshot)) = stats_rx.await {
let mut captured = 0;
let mut silenced = 0;
let mut dropped = 0;
for ((_level, event_type), count) in &snapshot.level_event_counts {
match event_type {
EventType::Captured => captured += count,
EventType::Silenced => silenced += count,
EventType::Dropped => dropped += count,
}
}
return (captured, silenced, dropped);
}
}
(0, 0, 0)
}
#[tokio::test]
async fn test_error_handling() -> Result<()> {
let tracer = Kerf::new_with_config(Config::default_main());
let remove_result = tracer.remove_tab("nonexistent")?.await?;
assert!(
remove_result.is_err(),
"Removing non-existent tab should return error"
);
if let Err(e) = remove_result {
assert!(
e.to_string().contains("not found"),
"Error event should mention tab not found"
);
}
let update_result = tracer
.update_tab("nonexistent", MatcherSet::empty())?
.await?;
assert!(
update_result.is_err(),
"Updating non-existent tab should return error"
);
if let Err(e) = update_result {
assert!(
e.to_string().contains("not found"),
"Error event should mention tab not found"
);
}
Ok(())
}
#[tokio::test]
async fn test_tab_management() -> Result<()> {
let mut matcher_set1 = MatcherSet::empty();
matcher_set1.add_matcher(Match::info().module_pattern("module_a*"));
let mut matcher_set2 = MatcherSet::empty();
matcher_set2.add_matcher(Match::debug().module_pattern("module_b*"));
let config = Config {
tabs: vec![
Tab::new("tab_a".to_string()).with_matcher_set(matcher_set1.clone()),
Tab::new("tab_b".to_string()).with_matcher_set(matcher_set2.clone()),
],
stats_config: Some(StatsConfig::default()),
};
let tracer = Kerf::new_with_config(config);
let tab_a_messages = Arc::new(Mutex::new(Vec::<String>::new()));
let tab_b_messages = Arc::new(Mutex::new(Vec::<String>::new()));
let sub_a_clone = tab_a_messages.clone();
let sub_b_clone = tab_b_messages.clone();
tracer
.set_callback(move |event, tabs| {
println!("Callback: {} - {:?}", event.message, tabs);
for &tab in tabs {
let msg_text = event.message.clone();
if tab.eq("tab_a") {
let sub_a = sub_a_clone.clone();
tokio::spawn(async move {
let mut lock = sub_a.lock().await;
lock.push(msg_text.clone());
});
} else if tab.eq("tab_b") {
let sub_b = sub_b_clone.clone();
tokio::spawn(async move {
let mut lock = sub_b.lock().await;
lock.push(msg_text.clone());
});
}
}
})?
.await??;
let event_a = create_test_event(
1,
Level::INFO,
"Module A event",
Some("module_a"),
Some("test.rs"),
Some(42),
None,
);
let event_b = create_test_event(
2,
Level::DEBUG,
"Module B event",
Some("module_b"),
Some("test.rs"),
Some(43),
None,
);
let event_tx = tracer._get_sender_for_testing();
let _ = event_tx.send(event_a.clone());
let _ = event_tx.send(event_b.clone());
tokio::time::sleep(Duration::from_millis(200)).await;
{
let sub_a_messages = tab_a_messages.lock().await;
let sub_b_messages = tab_b_messages.lock().await;
println!("A messages: {:?}", *sub_a_messages);
println!("B messages: {:?}", *sub_b_messages);
assert_eq!(
sub_a_messages.len(),
1,
"Subscriber A should have received 1 event"
);
assert_eq!(
sub_b_messages.len(),
1,
"Subscriber B should have received 1 event"
);
}
tracer.remove_tab("tab_a")?.await??;
tracer.remove_tab("tab_b")?.await??;
tracer.clear_stats()?.await??;
tracer.add_tab("tab_a", matcher_set1.clone())?.await??;
tracer.add_tab("tab_b", matcher_set2.clone())?.await??;
let mut updated_matcher_set = matcher_set1.clone();
updated_matcher_set.add_matcher(Match::debug().module_pattern("module_b*"));
tracer.update_tab("tab_a", updated_matcher_set)?.await??;
let tab_a_messages = Arc::new(Mutex::new(Vec::<String>::new()));
let tab_b_messages = Arc::new(Mutex::new(Vec::<String>::new()));
let sub_a_clone = tab_a_messages.clone();
let sub_b_clone = tab_b_messages.clone();
tracer
.set_callback(move |event, tabs| {
println!("Callback 2: {} - {:?}", event.message, tabs);
for &tab in tabs {
let msg_text = event.message.clone();
if tab.eq("tab_a") {
let sub_a = sub_a_clone.clone();
tokio::spawn(async move {
let mut lock = sub_a.lock().await;
lock.push(msg_text.clone());
});
} else if tab.eq("tab_b") {
let sub_b = sub_b_clone.clone();
tokio::spawn(async move {
let mut lock = sub_b.lock().await;
lock.push(msg_text.clone());
});
}
}
})?
.await??;
let event_b2 = create_test_event(
3,
Level::DEBUG,
"Another Module B event",
Some("module_b"),
Some("test.rs"),
Some(44),
None,
);
let _ = tracer._get_sender_for_testing().send(event_b2.clone());
tokio::time::sleep(Duration::from_millis(200)).await;
{
let sub_a_messages = tab_a_messages.lock().await;
let sub_b_messages = tab_b_messages.lock().await;
println!("After update - A messages: {:?}", *sub_a_messages);
println!("After update - B messages: {:?}", *sub_b_messages);
assert_eq!(
sub_a_messages.len(),
1,
"Subscriber A should have received 1 event after update"
);
assert_eq!(
sub_b_messages.len(),
1,
"Subscriber B should have received 1 event"
);
}
tracer.remove_tab("tab_b")?.await??;
let event_b3 = create_test_event(
4,
Level::DEBUG,
"Third Module B event",
Some("module_b"),
Some("test.rs"),
Some(45),
None,
);
let _ = tracer._get_sender_for_testing().send(event_b3.clone());
tokio::time::sleep(Duration::from_millis(200)).await;
{
let sub_a_messages = tab_a_messages.lock().await;
let sub_b_messages = tab_b_messages.lock().await;
println!("After removal - A messages: {:?}", *sub_a_messages);
println!("After removal - B messages: {:?}", *sub_b_messages);
assert_eq!(
sub_a_messages.len(),
2,
"Subscriber A should have received 2 messages"
);
assert_eq!(
sub_b_messages.len(),
1,
"Subscriber B should still have 1 event after removal"
);
}
Ok(())
}
#[tokio::test]
async fn test_callback_functionality() -> Result<()> {
let mut matcher_set1 = MatcherSet::empty();
matcher_set1.add_matcher(Match::info().module_pattern("module_a*"));
let mut matcher_set2 = MatcherSet::empty();
matcher_set2.add_matcher(
Match::error() .module_pattern("module_a*"), );
let config = Config {
tabs: vec![
Tab::new("lax_tab".to_string()).with_matcher_set(matcher_set1),
Tab::new("strict_tab".to_string()).with_matcher_set(matcher_set2),
],
stats_config: Some(StatsConfig::default()),
};
let tracer = Kerf::new_with_config(config);
#[derive(Debug)]
struct CallbackData {
event: String,
tabs: Vec<String>,
}
let callback_data = Arc::new(Mutex::new(Vec::<CallbackData>::new()));
let data_clone = callback_data.clone();
tracer
.set_callback(move |event, tabs| {
let data = data_clone.clone();
let msg = event.message.clone();
let sub_list: Vec<String> = tabs.iter().map(|&s| s.to_string()).collect();
tokio::spawn(async move {
let mut lock = data.lock().await;
lock.push(CallbackData {
event: msg,
tabs: sub_list,
});
});
})?
.await??;
let info_event = create_test_event(
1,
Level::INFO,
"Info event",
Some("module_a"),
Some("test.rs"),
Some(42),
None,
);
let error_event = create_test_event(
2,
Level::ERROR,
"Error event",
Some("module_a"),
Some("test.rs"),
Some(43),
None,
);
send_event(&tracer, info_event).await;
send_event(&tracer, error_event).await;
tokio::time::sleep(Duration::from_millis(100)).await;
{
let data = callback_data.lock().await;
assert_eq!(data.len(), 2, "Should have exactly 2 callback invocations");
let info_callback = data.iter().find(|d| d.event == "Info event");
assert!(
info_callback.is_some(),
"Should have called back for info event"
);
if let Some(info_cb) = info_callback {
assert_eq!(
info_cb.tabs.len(),
1,
"Info event should be captured by exactly 1 tab"
);
assert!(
info_cb.tabs.contains(&"lax_tab".to_string()),
"Info event should be captured by lax_tab"
);
}
let error_callback = data.iter().find(|d| d.event == "Error event");
assert!(
error_callback.is_some(),
"Should have called back for error event"
);
if let Some(error_cb) = error_callback {
assert_eq!(
error_cb.tabs.len(),
2,
"Error event should be captured by exactly 2 tabs"
);
assert!(
error_cb.tabs.contains(&"lax_tab".to_string()),
"Error event should be captured by lax_tab"
);
assert!(
error_cb.tabs.contains(&"strict_tab".to_string()),
"Error event should be captured by strict_tab"
);
}
}
let new_callback_data = Arc::new(Mutex::new(Vec::<CallbackData>::new()));
let new_data_clone = new_callback_data.clone();
tracer
.set_callback(move |event, tabs| {
let data = new_data_clone.clone();
let msg = event.message.clone();
let sub_list: Vec<String> = tabs.iter().map(|&s| s.to_string()).collect();
tokio::spawn(async move {
let mut lock = data.lock().await;
lock.push(CallbackData {
event: msg,
tabs: sub_list,
});
});
})?
.await??;
let new_event = create_test_event(
3,
Level::INFO,
"New event",
Some("module_a"),
Some("test.rs"),
Some(44),
None,
);
send_event(&tracer, new_event).await;
tokio::time::sleep(Duration::from_millis(100)).await;
{
let data = new_callback_data.lock().await;
let new_msg_callback = data.iter().find(|d| d.event == "New event");
assert!(
new_msg_callback.is_some(),
"Should have called back for new event"
);
if let Some(new_cb) = new_msg_callback {
assert_eq!(
new_cb.tabs.len(),
1,
"New event should be captured by exactly 1 tab"
);
assert!(
new_cb.tabs.contains(&"lax_tab".to_string()),
"New event should be captured by lax_tab"
);
}
}
Ok(())
}
#[tokio::test]
async fn test_specialized_callbacks() -> Result<()> {
let mut normal_matcher = MatcherSet::empty();
normal_matcher.add_matcher(Match::info().module_pattern("test_*"));
let mut silencing_matcher = MatcherSet::empty();
silencing_matcher.add_matcher(Match::info().exclude().module_pattern("test_silenced*"));
silencing_matcher.add_matcher(Match::info().module_pattern("test_*"));
let config = Config {
tabs: vec![
Tab::new("normal_sub".to_string()).with_matcher_set(normal_matcher),
Tab::new("silencing_sub".to_string()).with_matcher_set(silencing_matcher),
],
stats_config: Some(StatsConfig::default()),
};
let tracer = Kerf::new_with_config(config);
let captured_events = Arc::new(Mutex::new(Vec::<(String, Vec<String>)>::new()));
let silenced_events = Arc::new(Mutex::new(Vec::<(String, Vec<String>)>::new()));
let dropped_events = Arc::new(Mutex::new(Vec::<String>::new()));
let captured_clone = captured_events.clone();
let silenced_clone = silenced_events.clone();
let dropped_clone = dropped_events.clone();
tracer
.set_callback(move |event, tabs| {
let events = captured_clone.clone();
let msg = event.message.clone();
let subs: Vec<String> = tabs.iter().map(|&s| s.to_string()).collect();
tokio::spawn(async move {
let mut lock = events.lock().await;
lock.push((msg, subs));
});
})?
.await??;
tracer
.set_silenced_callback(move |event, silencers| {
println!("SILENCED CB: {} for {:?}", event.message, silencers);
let events = silenced_clone.clone();
let msg = event.message.clone();
let subs: Vec<String> = silencers.iter().map(|&s| s.to_string()).collect();
tokio::spawn(async move {
let mut lock = events.lock().await;
lock.push((msg, subs));
});
})?
.await??;
tracer
.set_dropped_callback(move |event| {
let events = dropped_clone.clone();
let msg = event.message.clone();
tokio::spawn(async move {
let mut lock = events.lock().await;
lock.push(msg);
});
})?
.await??;
let normal_event = create_test_event(
1,
Level::INFO,
"Normal event",
Some("test_normal"),
Some("test.rs"),
Some(10),
None,
);
let silenced_event = create_test_event(
2,
Level::INFO,
"Silenced event",
Some("test_silenced"),
Some("test.rs"),
Some(20),
None,
);
let dropped_event = create_test_event(
3,
Level::INFO,
"Dropped event",
Some("other_module"),
Some("test.rs"),
Some(30),
None,
);
println!("Sending normal event...");
tracer
._get_sender_for_testing()
.send(normal_event)
.expect("Failed to send");
tokio::time::sleep(Duration::from_millis(100)).await;
println!("Sending dropped event...");
tracer
._get_sender_for_testing()
.send(dropped_event)
.expect("Failed to send");
tokio::time::sleep(Duration::from_millis(100)).await;
tracer.remove_tab("normal_sub")?.await??;
println!("Sending silenced event after removing normal_sub...");
tracer
._get_sender_for_testing()
.send(silenced_event)
.expect("Failed to send");
tokio::time::sleep(Duration::from_millis(500)).await;
let captured = captured_events.lock().await;
let silenced = silenced_events.lock().await;
let dropped = dropped_events.lock().await;
println!("CAPTURED: {:?}", *captured);
println!("SILENCED: {:?}", *silenced);
println!("DROPPED: {:?}", *dropped);
let (captured_count, silenced_count, dropped_count) = get_stats_counts(&tracer).await;
println!(
"STATS - Captured: {captured_count}, Silenced: {silenced_count}, Dropped: {dropped_count}"
);
assert_eq!(captured.len(), 1, "Should have 1 captured event");
if !captured.is_empty() {
let (msg, tabs) = &captured[0];
assert_eq!(msg, "Normal event", "Should be the normal event");
assert_eq!(tabs.len(), 2, "Both tabs should capture it");
assert!(
tabs.contains(&"normal_sub".to_string()),
"normal_sub should capture it"
);
assert!(
tabs.contains(&"silencing_sub".to_string()),
"silencing_sub should capture it"
);
}
assert_eq!(silenced.len(), 1, "Should have 1 silenced event");
if !silenced.is_empty() {
let (msg, silencers) = &silenced[0];
assert_eq!(msg, "Silenced event", "Should be the silenced event");
assert_eq!(silencers.len(), 1, "Should be silenced by one tab");
assert!(
silencers.contains(&"silencing_sub".to_string()),
"Should be silenced by silencing_sub"
);
}
assert_eq!(dropped.len(), 1, "Should have 1 dropped event");
assert!(
dropped.contains(&"Dropped event".to_string()),
"Should contain the dropped event"
);
assert_eq!(captured_count, 1, "Tracer should report 1 captured event");
assert_eq!(silenced_count, 1, "Tracer should report 1 silenced event");
assert_eq!(dropped_count, 1, "Tracer should report 1 dropped event");
Ok(())
}
#[tokio::test]
async fn test_silenced_events() -> Result<()> {
let mut silencing_matcher = MatcherSet::empty();
silencing_matcher.add_matcher(Match::info().module_pattern("test_*"));
silencing_matcher.add_matcher(
Match::info()
.exclude() .module_pattern("test_silenced*"),
);
let config = Config {
tabs: vec![Tab::new("silencing_sub".to_string()).with_matcher_set(silencing_matcher)],
stats_config: Some(StatsConfig::default()),
};
let tracer = Kerf::new_with_config(config);
let silenced_events =
Arc::new(tokio::sync::Mutex::new(Vec::<(String, Vec<String>)>::new()));
let silenced_clone = silenced_events.clone();
tracer
.set_silenced_callback(move |event, silencers| {
println!(
"SILENCED CALLBACK INVOKED: event={}, silencers={:?}",
event.message, silencers
);
let events = silenced_clone.clone();
let msg = event.message.clone();
let silencer_names: Vec<String> =
silencers.iter().map(|&s| s.to_string()).collect();
println!("SILENCED EVENT DETECTED: {msg}");
tokio::spawn(async move {
let mut lock = events.lock().await;
lock.push((msg, silencer_names));
println!("SILENCED EVENT STORED");
});
})?
.await??;
let silenced_event = create_test_event(
1,
Level::INFO,
"Test silenced event",
Some("test_silenced"),
Some("test.rs"),
Some(20),
None,
);
println!("SENDING SILENCED EVENT");
tracer
._get_sender_for_testing()
.send(silenced_event.clone())
.expect("Failed to send");
println!("WAITING FOR PROCESSING");
tokio::time::sleep(Duration::from_millis(500)).await;
let silenced = silenced_events.lock().await;
println!("SILENCED EVENTS: {:?}", *silenced);
let (_, silenced_count, _) = get_stats_counts(&tracer).await;
println!("TRACER SILENCED COUNT: {silenced_count}");
assert!(
silenced_count > 0,
"Tracer should report at least one silenced event"
);
assert_eq!(
silenced.len(),
1,
"Should have exactly 1 silenced event in the callback results"
);
if !silenced.is_empty() {
let (msg, silencers) = &silenced[0];
assert_eq!(
msg, "Test silenced event",
"Should be the test silenced event"
);
assert_eq!(silencers.len(), 1, "Should be silenced by one tab");
assert!(
silencers.contains(&"silencing_sub".to_string()),
"Should be silenced by silencing_sub"
);
}
Ok(())
}
#[tokio::test]
async fn test_basic_message_capture() -> Result<()> {
let mut matcher_set = MatcherSet::empty();
matcher_set.add_matcher(Match::debug().module_pattern("test_module*"));
let config = Config {
tabs: vec![Tab::new("test_tab".to_string()).with_matcher_set(matcher_set)],
stats_config: Some(StatsConfig::default()),
};
let tracer = Kerf::new_with_config(config);
let captured_messages = Arc::new(Mutex::new(Vec::<ArcEvent>::new()));
let captured_clone = captured_messages.clone();
tracer
.set_callback(move |event, tabs| {
if tabs.contains(&"test_tab") {
let captured = captured_clone.clone();
tokio::spawn(async move {
let mut lock = captured.lock().await;
lock.push(Arc::clone(&event));
});
}
})?
.await??;
let event1 = create_test_event(
1,
Level::INFO,
"Test event 1",
Some("test_module"),
Some("test.rs"),
Some(42),
None,
);
let event2 = create_test_event(
2,
Level::WARN,
"Test event 2",
Some("test_module_other"),
Some("test.rs"),
Some(43),
Some("test_span"),
);
let event3 = create_test_event(
3,
Level::ERROR,
"Test event 3",
Some("other_module"),
Some("test.rs"),
Some(44),
None,
);
send_event(&tracer, event1).await;
send_event(&tracer, event2).await;
send_event(&tracer, event3).await;
tokio::time::sleep(Duration::from_millis(100)).await;
let locked_captured = captured_messages.lock().await;
assert_eq!(
locked_captured.len(),
2,
"Should have captured exactly 2 messages"
);
let (_, _, dropped_count) = get_stats_counts(&tracer).await;
assert_eq!(dropped_count, 1, "Should have 1 dropped event");
Ok(())
}
#[tokio::test]
async fn test_matcher_behavior() -> Result<()> {
let mut matcher_set = MatcherSet::empty();
matcher_set.add_matcher(Match::trace().module_pattern("test_module*"));
matcher_set.add_matcher(
Match::trace()
.exclude()
.module_pattern("test_module_internal*"),
);
let config = Config {
tabs: vec![Tab::new("test_tab".to_string()).with_matcher_set(matcher_set)],
stats_config: Some(StatsConfig::default()),
};
let tracer = Kerf::new_with_config(config);
let captured_messages = Arc::new(Mutex::new(Vec::<String>::new()));
let silenced_messages = Arc::new(Mutex::new(Vec::<String>::new()));
let captured_clone = captured_messages.clone();
let silenced_clone = silenced_messages.clone();
tracer
.set_callback(move |event, tabs| {
if tabs.contains(&"test_tab") {
let captured = captured_clone.clone();
let msg = event.message.clone();
tokio::spawn(async move {
let mut lock = captured.lock().await;
lock.push(msg);
});
}
})?
.await??;
tracer
.set_silenced_callback(move |event, _| {
let captured = silenced_clone.clone();
let msg = event.message.clone();
tokio::spawn(async move {
let mut lock = captured.lock().await;
lock.push(msg);
});
})?
.await??;
let should_capture = create_test_event(
1,
Level::INFO,
"Should be captured",
Some("test_module"),
Some("test.rs"),
Some(42),
None,
);
let should_silence = create_test_event(
2,
Level::INFO,
"Should be silenced",
Some("test_module_internal"),
Some("test.rs"),
Some(43),
None,
);
let another_capture = create_test_event(
3,
Level::DEBUG,
"Should be captured too",
Some("test_module_other"),
Some("test.rs"),
Some(44),
None,
);
let event_tx = tracer._get_sender_for_testing();
let _ = event_tx.send(should_capture.clone());
let _ = event_tx.send(should_silence.clone());
let _ = event_tx.send(another_capture.clone());
tokio::time::sleep(Duration::from_millis(200)).await;
{
let captured = captured_messages.lock().await;
let silenced = silenced_messages.lock().await;
println!("Captured messages: {:?}", *captured);
println!("Silenced messages: {:?}", *silenced);
assert_eq!(captured.len(), 2, "Should have captured 2 messages");
assert_eq!(silenced.len(), 1, "Should have silenced 1 event");
assert!(
captured.contains(&"Should be captured".to_string()),
"First event should be captured"
);
assert!(
captured.contains(&"Should be captured too".to_string()),
"Third event should be captured"
);
assert!(
silenced.contains(&"Should be silenced".to_string()),
"Second event should be silenced"
);
}
let (_, silenced_count, _) = get_stats_counts(&tracer).await;
assert_eq!(silenced_count, 1, "Tracer should report 1 silenced event");
tracer.clear_stats()?.await??;
let (_, silenced_after_clear, dropped_after_clear) = get_stats_counts(&tracer).await;
assert_eq!(
silenced_after_clear, 0,
"Silenced count should be 0 after clear"
);
assert_eq!(
dropped_after_clear, 0,
"Dropped count should be 0 after clear"
);
Ok(())
}
#[tokio::test]
async fn test_matcher_level_behavior() -> Result<()> {
let mut matcher_set = MatcherSet::empty();
matcher_set.add_matcher(
Match::info() .module_pattern("test_module*"),
);
let config = Config {
tabs: vec![Tab::new("level_test_tab".to_string()).with_matcher_set(matcher_set)],
stats_config: Some(StatsConfig::default()),
};
let tracer = Kerf::new_with_config(config);
let first_phase_events = Arc::new(Mutex::new(Vec::<(String, Level)>::new()));
let first_clone = first_phase_events.clone();
tracer
.set_callback(move |event, _tabs| {
let captured = first_clone.clone();
let msg = event.message.clone();
let level = event.level;
tokio::spawn(async move {
let mut lock = captured.lock().await;
lock.push((msg, level));
});
})?
.await??;
let levels = [
(Level::TRACE, "TRACE level event 1"),
(Level::DEBUG, "DEBUG level event 1"),
(Level::INFO, "INFO level event 1"),
(Level::WARN, "WARN level event 1"),
(Level::ERROR, "ERROR level event 1"),
];
for (i, (level, msg)) in levels.iter().enumerate() {
let event = create_test_event(
i as u64,
*level,
msg,
Some("test_module"),
Some("test.rs"),
Some(i as u32),
None,
);
let _ = tracer._get_sender_for_testing().send(event);
}
tokio::time::sleep(Duration::from_millis(200)).await;
{
let events = first_phase_events.lock().await;
assert_eq!(events.len(), 3, "Should have captured exactly 3 events");
let has_info = events.iter().any(|(msg, _)| msg == "INFO level event 1");
let has_warn = events.iter().any(|(msg, _)| msg == "WARN level event 1");
let has_error = events.iter().any(|(msg, _)| msg == "ERROR level event 1");
let has_debug = events.iter().any(|(msg, _)| msg == "DEBUG level event 1");
let has_trace = events.iter().any(|(msg, _)| msg == "TRACE level event 1");
assert!(has_info, "Should have captured INFO event");
assert!(has_warn, "Should have captured WARN event");
assert!(has_error, "Should have captured ERROR event");
assert!(!has_debug, "Should NOT have captured DEBUG event");
assert!(!has_trace, "Should NOT have captured TRACE event");
}
tracer.clear_stats()?.await??;
let mut updated_matcher = MatcherSet::empty();
updated_matcher.add_matcher(
Match::debug() .module_pattern("test_module*"),
);
let second_phase_events = Arc::new(Mutex::new(Vec::<(String, Level)>::new()));
let second_clone = second_phase_events.clone();
tracer
.set_callback(move |event, _tabs| {
let captured = second_clone.clone();
let msg = event.message.clone();
let level = event.level;
tokio::spawn(async move {
let mut lock = captured.lock().await;
lock.push((msg, level));
});
})?
.await??;
tracer
.update_tab("level_test_tab", updated_matcher)?
.await??;
let levels2 = [
(Level::TRACE, "TRACE level event 2"),
(Level::DEBUG, "DEBUG level event 2"),
(Level::INFO, "INFO level event 2"),
(Level::WARN, "WARN level event 2"),
(Level::ERROR, "ERROR level event 2"),
];
for (i, (level, msg)) in levels2.iter().enumerate() {
let event = create_test_event(
(i + 100) as u64, *level,
msg,
Some("test_module"),
Some("test.rs"),
Some((i + 100) as u32),
None,
);
let _ = tracer._get_sender_for_testing().send(event);
}
tokio::time::sleep(Duration::from_millis(200)).await;
{
let events = second_phase_events.lock().await;
println!("Second phase events: {events:?}");
assert_eq!(events.len(), 4, "Should have captured exactly 4 events");
let has_info = events.iter().any(|(msg, _)| msg == "INFO level event 2");
let has_warn = events.iter().any(|(msg, _)| msg == "WARN level event 2");
let has_error = events.iter().any(|(msg, _)| msg == "ERROR level event 2");
let has_debug = events.iter().any(|(msg, _)| msg == "DEBUG level event 2");
let has_trace = events.iter().any(|(msg, _)| msg == "TRACE level event 2");
assert!(has_info, "Should have captured INFO event");
assert!(has_warn, "Should have captured WARN event");
assert!(has_error, "Should have captured ERROR event");
assert!(has_debug, "Should have captured DEBUG event");
assert!(!has_trace, "Should NOT have captured TRACE event");
}
Ok(())
}
#[tokio::test]
async fn test_tracer_under_heavy_load() -> Result<()> {
let mut matcher_set = MatcherSet::empty();
matcher_set.add_matcher(Match::debug().module_pattern("test_module*"));
let config = Config {
tabs: vec![Tab::new("test_tab".to_string()).with_matcher_set(matcher_set)],
stats_config: Some(StatsConfig::default()),
};
let tracer = Kerf::new_with_config(config);
let captured_count = Arc::new(Mutex::new(0));
let count_clone = captured_count.clone();
let (completion_tx, completion_rx) = tokio::sync::oneshot::channel::<()>();
let completion_tx = Arc::new(Mutex::new(Some(completion_tx)));
let completion_tx_clone = completion_tx.clone();
tracer
.set_callback(move |event, _tabs| {
let counter = count_clone.clone();
let completion_sender = completion_tx_clone.clone();
tokio::spawn(async move {
let mut lock = counter.lock().await;
*lock += 1;
if event.id == u64::MAX {
let mut sender = completion_sender.lock().await;
if let Some(tx) = sender.take() {
let _ = tx.send(());
}
}
});
})?
.await??;
const EVENT_COUNT: u64 = 1000;
let tx = tracer._get_sender_for_testing();
for i in 0..EVENT_COUNT {
let event = create_test_event(
i,
Level::DEBUG,
&format!("Test event {i}"),
Some("test_module"),
Some("test.rs"),
Some(i as u32),
None,
);
let _ = tx.send(event);
}
let sentinel_event = create_test_event(
u64::MAX, Level::DEBUG,
"SENTINEL EVENT - END OF TEST",
Some("test_module"),
Some("test.rs"),
Some(0),
None,
);
let _ = tx.send(sentinel_event);
match tokio::time::timeout(Duration::from_secs(15), completion_rx).await {
Ok(_) => {
println!("Received completion signal, all events processed");
}
Err(_) => {
println!("Timed out waiting for completion signal");
}
}
{
let count = captured_count.lock().await;
let actual_count = *count - 1;
println!("Processed {actual_count} regular events (plus 1 sentinel)");
assert_eq!(
actual_count, EVENT_COUNT as usize,
"Should have captured all {EVENT_COUNT} events"
);
}
let (captured_count, _, _) = get_stats_counts(&tracer).await;
assert_eq!(
captured_count - 1,
EVENT_COUNT,
"Tracer should report having captured all events"
);
Ok(())
}
#[tokio::test]
async fn test_span_based_matchering() -> Result<()> {
let mut span_matcher = MatcherSet::empty();
span_matcher.add_matcher(
Match::info().all_modules().span_pattern("important*"), );
let mut exclude_span_matcher = MatcherSet::empty();
exclude_span_matcher.add_matcher(Match::info().all_modules());
exclude_span_matcher.add_matcher(
Match::info().exclude().span_pattern("ignore*"), );
let config = Config {
tabs: vec![
Tab::new("span_tab".to_string()).with_matcher_set(span_matcher),
Tab::new("exclude_tab".to_string()).with_matcher_set(exclude_span_matcher),
],
stats_config: Some(StatsConfig::default()),
};
let tracer = Kerf::new_with_config(config);
let captured_by_span = Arc::new(Mutex::new(Vec::<(String, String)>::new()));
let captured_by_exclude = Arc::new(Mutex::new(Vec::<(String, String)>::new()));
let span_clone = captured_by_span.clone();
let exclude_clone = captured_by_exclude.clone();
tracer
.set_callback(move |event, tabs| {
let span_name = event.span_name.clone().unwrap_or_default();
let msg = event.message.clone();
for &tab in tabs {
if tab.eq("span_tab") {
let span_captured = span_clone.clone();
let msg_clone = msg.clone();
let span_name_clone = span_name.clone();
tokio::spawn(async move {
let mut lock = span_captured.lock().await;
lock.push((msg_clone.clone(), span_name_clone.clone()));
});
} else if tab.eq("exclude_tab") {
let exclude_captured = exclude_clone.clone();
let msg_clone = msg.clone();
let span_name_clone = span_name.clone();
tokio::spawn(async move {
let mut lock = exclude_captured.lock().await;
lock.push((msg_clone.clone(), span_name_clone.clone()));
});
}
}
})?
.await??;
let important_event = create_test_event(
1,
Level::INFO,
"Important event",
Some("test_module"),
Some("test.rs"),
Some(42),
Some("important_span"),
);
let normal_event = create_test_event(
2,
Level::INFO,
"Normal event",
Some("test_module"),
Some("test.rs"),
Some(43),
Some("normal_span"),
);
let ignore_event = create_test_event(
3,
Level::INFO,
"Ignore event",
Some("test_module"),
Some("test.rs"),
Some(44),
Some("ignore_span"),
);
let no_span_event = create_test_event(
4,
Level::INFO,
"No span event",
Some("test_module"),
Some("test.rs"),
Some(45),
None,
);
let tx = tracer._get_sender_for_testing();
let _ = tx.send(important_event);
let _ = tx.send(normal_event);
let _ = tx.send(ignore_event);
let _ = tx.send(no_span_event);
tokio::time::sleep(Duration::from_millis(200)).await;
{
let span_events = captured_by_span.lock().await;
let exclude_events = captured_by_exclude.lock().await;
println!("Span tab events: {:?}", *span_events);
println!("Exclude tab events: {:?}", *exclude_events);
assert_eq!(span_events.len(), 1, "Span tab should only capture 1 event");
assert!(
span_events
.iter()
.any(|(msg, span)| msg == "Important event" && span == "important_span"),
"Span tab should capture the important event"
);
assert_eq!(
exclude_events.len(),
3,
"Exclude tab should capture 3 events"
);
assert!(
exclude_events
.iter()
.any(|(msg, _)| msg == "Important event"),
"Exclude tab should include important event"
);
assert!(
exclude_events.iter().any(|(msg, _)| msg == "Normal event"),
"Exclude tab should include normal event"
);
assert!(
exclude_events.iter().any(|(msg, _)| msg == "No span event"),
"Exclude tab should include no-span event"
);
assert!(
!exclude_events.iter().any(|(msg, _)| msg == "Ignore event"),
"Exclude tab should NOT include ignore event"
);
}
Ok(())
}
#[tokio::test]
async fn test_target_pattern_matchering() -> Result<()> {
let mut target_matcher = MatcherSet::empty();
target_matcher.add_matcher(
Match::info().all_modules().target_pattern("api*"), );
let mut exclude_target_matcher = MatcherSet::empty();
exclude_target_matcher.add_matcher(Match::info().all_modules());
exclude_target_matcher.add_matcher(
Match::info().exclude().target_pattern("internal*"), );
let config = Config {
tabs: vec![
Tab::new("target_tab".to_string()).with_matcher_set(target_matcher),
Tab::new("exclude_target_tab".to_string()).with_matcher_set(exclude_target_matcher),
],
stats_config: Some(StatsConfig::default()),
};
let tracer = Kerf::new_with_config(config);
let api_event = create_test_event_with_target(
1,
Level::INFO,
"API event",
Some("test_module"),
"api_service",
Some("test.rs"),
Some(10),
None,
);
let web_event = create_test_event_with_target(
2,
Level::INFO,
"Web event",
Some("test_module"),
"web_service",
Some("test.rs"),
Some(11),
None,
);
let internal_event = create_test_event_with_target(
3,
Level::INFO,
"Internal event",
Some("test_module"),
"internal_service",
Some("test.rs"),
Some(12),
None,
);
let target_captured = Arc::new(Mutex::new(Vec::<String>::new()));
let exclude_captured = Arc::new(Mutex::new(Vec::<String>::new()));
let target_clone = target_captured.clone();
let exclude_clone = exclude_captured.clone();
tracer
.set_callback(move |event, tabs| {
let msg = event.message.clone();
for &tab in tabs {
if tab.eq("target_tab") {
let captured = target_clone.clone();
let msg_clone = msg.clone();
tokio::spawn(async move {
let mut lock = captured.lock().await;
lock.push(msg_clone);
});
} else if tab.eq("exclude_target_tab") {
let captured = exclude_clone.clone();
let msg_clone = msg.clone();
tokio::spawn(async move {
let mut lock = captured.lock().await;
lock.push(msg_clone);
});
}
}
})?
.await??;
let tx = tracer._get_sender_for_testing();
let _ = tx.send(api_event);
let _ = tx.send(web_event);
let _ = tx.send(internal_event);
tokio::time::sleep(Duration::from_millis(200)).await;
{
let target_events = target_captured.lock().await;
let exclude_events = exclude_captured.lock().await;
println!("Target tab events: {:?}", *target_events);
println!("Exclude target tab events: {:?}", *exclude_events);
assert_eq!(
target_events.len(),
1,
"Target tab should only capture 1 event"
);
assert!(
target_events.contains(&"API event".to_string()),
"Target tab should capture the API event"
);
assert_eq!(
exclude_events.len(),
2,
"Exclude target tab should capture 2 events"
);
assert!(
exclude_events.contains(&"API event".to_string()),
"Exclude target tab should include api event"
);
assert!(
exclude_events.contains(&"Web event".to_string()),
"Exclude target tab should include web event"
);
assert!(
!exclude_events.contains(&"Internal event".to_string()),
"Exclude target tab should NOT include internal event"
);
}
Ok(())
}
#[tokio::test]
async fn test_combined_target_and_module_matchering() -> Result<()> {
let mut combined_matcher = MatcherSet::empty();
combined_matcher.add_matcher(
Match::info()
.module_pattern("service_*")
.target_pattern("api_*"),
);
let config = Config {
tabs: vec![Tab::new("combined_tab".to_string()).with_matcher_set(combined_matcher)],
stats_config: Some(StatsConfig::default()),
};
let tracer = Kerf::new_with_config(config);
let match_both = create_test_event_with_target(
1,
Level::INFO,
"Match both module and target",
Some("service_module"),
"api_service",
Some("test.rs"),
Some(10),
None,
);
let match_module_only = create_test_event_with_target(
2,
Level::INFO,
"Match module only",
Some("service_module"),
"web_service",
Some("test.rs"),
Some(11),
None,
);
let match_target_only = create_test_event_with_target(
3,
Level::INFO,
"Match target only",
Some("utility_module"),
"api_service",
Some("test.rs"),
Some(12),
None,
);
let match_neither = create_test_event_with_target(
4,
Level::INFO,
"Match neither",
Some("utility_module"),
"web_service",
Some("test.rs"),
Some(13),
None,
);
let captured_events = Arc::new(Mutex::new(Vec::<String>::new()));
let captured_clone = captured_events.clone();
tracer
.set_callback(move |event, _| {
let captured = captured_clone.clone();
let msg = event.message.clone();
tokio::spawn(async move {
let mut lock = captured.lock().await;
lock.push(msg);
});
})?
.await??;
let tx = tracer._get_sender_for_testing();
let _ = tx.send(match_both);
let _ = tx.send(match_module_only);
let _ = tx.send(match_target_only);
let _ = tx.send(match_neither);
tokio::time::sleep(Duration::from_millis(200)).await;
{
let events = captured_events.lock().await;
println!("Combined filter events: {:?}", *events);
assert_eq!(
events.len(),
1,
"Combined filter should only capture 1 event"
);
assert!(
events.contains(&"Match both module and target".to_string()),
"Should only capture the event matching both module and target"
);
assert!(
!events.contains(&"Match module only".to_string()),
"Should not capture event matching only module"
);
assert!(
!events.contains(&"Match neither".to_string()),
"Should not capture event matching neither"
);
}
Ok(())
}
#[tokio::test]
async fn test_multiple_target_patterns() -> Result<()> {
let mut multi_target_matcher = MatcherSet::empty();
multi_target_matcher.add_matcher(
Match::info()
.all_modules()
.target_patterns(vec!["api_*", "web_*"]), );
let config = Config {
tabs: vec![
Tab::new("multi_target_tab".to_string()).with_matcher_set(multi_target_matcher),
],
stats_config: Some(StatsConfig::default()),
};
let tracer = Kerf::new_with_config(config);
let api_event = create_test_event_with_target(
1,
Level::INFO,
"API event",
Some("test_module"),
"api_service",
Some("test.rs"),
Some(10),
None,
);
let web_event = create_test_event_with_target(
2,
Level::INFO,
"Web event",
Some("test_module"),
"web_service",
Some("test.rs"),
Some(11),
None,
);
let db_event = create_test_event_with_target(
3,
Level::INFO,
"DB event",
Some("test_module"),
"db_service",
Some("test.rs"),
Some(12),
None,
);
let captured_events = Arc::new(Mutex::new(Vec::<String>::new()));
let captured_clone = captured_events.clone();
tracer
.set_callback(move |event, _| {
let captured = captured_clone.clone();
let msg = event.message.clone();
tokio::spawn(async move {
let mut lock = captured.lock().await;
lock.push(msg);
});
})?
.await??;
let tx = tracer._get_sender_for_testing();
let _ = tx.send(api_event);
let _ = tx.send(web_event);
let _ = tx.send(db_event);
tokio::time::sleep(Duration::from_millis(200)).await;
{
let events = captured_events.lock().await;
println!("Multi-target filter events: {:?}", *events);
assert_eq!(
events.len(),
2,
"Multi-target filter should capture exactly 2 events"
);
assert!(
events.contains(&"API event".to_string()),
"Should capture the API event"
);
assert!(
events.contains(&"Web event".to_string()),
"Should capture the Web event"
);
assert!(
!events.contains(&"DB event".to_string()),
"Should not capture the DB event"
);
}
Ok(())
}
#[tokio::test]
async fn test_target_pattern_wildcard() -> Result<()> {
let mut wildcard_matcher = MatcherSet::empty();
wildcard_matcher.add_matcher(
Match::info().all_modules(), );
let config = Config {
tabs: vec![Tab::new("wildcard_tab".to_string()).with_matcher_set(wildcard_matcher)],
stats_config: Some(StatsConfig::default()),
};
let tracer = Kerf::new_with_config(config);
let events = [
create_test_event_with_target(
1,
Level::INFO,
"API event",
Some("test_module"),
"api_service",
Some("test.rs"),
Some(10),
None,
),
create_test_event_with_target(
2,
Level::INFO,
"Web event",
Some("test_module"),
"web_service",
Some("test.rs"),
Some(11),
None,
),
create_test_event_with_target(
3,
Level::INFO,
"DB event",
Some("test_module"),
"db_service",
Some("test.rs"),
Some(12),
None,
),
create_test_event_with_target(
4,
Level::INFO,
"Empty target",
Some("test_module"),
"",
Some("test.rs"),
Some(13),
None,
),
];
let captured_events = Arc::new(Mutex::new(Vec::<String>::new()));
let captured_clone = captured_events.clone();
tracer
.set_callback(move |event, _| {
let captured = captured_clone.clone();
let msg = event.message.clone();
tokio::spawn(async move {
let mut lock = captured.lock().await;
lock.push(msg);
});
})?
.await??;
let tx = tracer._get_sender_for_testing();
for event in events.iter() {
let _ = tx.send(event.clone());
}
tokio::time::sleep(Duration::from_millis(200)).await;
{
let events = captured_events.lock().await;
println!("Wildcard target filter events: {:?}", *events);
assert_eq!(
events.len(),
4,
"Wildcard target filter should capture all events"
);
assert!(
events.contains(&"API event".to_string()),
"Should capture the API event"
);
assert!(
events.contains(&"Web event".to_string()),
"Should capture the Web event"
);
assert!(
events.contains(&"DB event".to_string()),
"Should capture the DB event"
);
assert!(
events.contains(&"Empty target".to_string()),
"Should capture event with empty target"
);
}
Ok(())
}
#[tokio::test]
async fn test_complex_target_patterns() -> Result<()> {
let mut complex_matcher = MatcherSet::empty();
complex_matcher.add_matcher(
Match::info().all_modules().target_pattern("*_service_v1"), );
let config = Config {
tabs: vec![
Tab::new("complex_target_tab".to_string()).with_matcher_set(complex_matcher),
],
stats_config: Some(StatsConfig::default()),
};
let tracer = Kerf::new_with_config(config);
let events = [
create_test_event_with_target(
1,
Level::INFO,
"API v1 event",
Some("test_module"),
"api_service_v1",
Some("test.rs"),
Some(10),
None,
),
create_test_event_with_target(
2,
Level::INFO,
"API v2 event",
Some("test_module"),
"api_service_v2",
Some("test.rs"),
Some(11),
None,
),
create_test_event_with_target(
3,
Level::INFO,
"Web v1 event",
Some("test_module"),
"web_service_v1",
Some("test.rs"),
Some(12),
None,
),
create_test_event_with_target(
4,
Level::INFO,
"DB event",
Some("test_module"),
"db_service",
Some("test.rs"),
Some(13),
None,
),
];
let captured_events = Arc::new(Mutex::new(Vec::<String>::new()));
let captured_clone = captured_events.clone();
tracer
.set_callback(move |event, _| {
let captured = captured_clone.clone();
let msg = event.message.clone();
tokio::spawn(async move {
let mut lock = captured.lock().await;
lock.push(msg);
});
})?
.await??;
let tx = tracer._get_sender_for_testing();
for event in events.iter() {
let _ = tx.send(event.clone());
}
tokio::time::sleep(Duration::from_millis(200)).await;
{
let events = captured_events.lock().await;
println!("Complex target filter events: {:?}", *events);
assert_eq!(
events.len(),
2,
"Complex target filter should capture exactly 2 events"
);
assert!(
events.contains(&"API v1 event".to_string()),
"Should capture the API v1 event"
);
assert!(
events.contains(&"Web v1 event".to_string()),
"Should capture the Web v1 event"
);
assert!(
!events.contains(&"API v2 event".to_string()),
"Should not capture the API v2 event"
);
assert!(
!events.contains(&"DB event".to_string()),
"Should not capture the DB event"
);
}
Ok(())
}
#[tokio::test]
async fn test_combined_all_matcher_types() -> Result<()> {
let mut combined_matcher = MatcherSet::empty();
combined_matcher.add_matcher(
Match::info()
.module_pattern("service_*")
.file_pattern("*.rs")
.span_pattern("operation_*")
.target_pattern("api_*"),
);
let config = Config {
tabs: vec![Tab::new("all_matchers_tab".to_string()).with_matcher_set(combined_matcher)],
stats_config: Some(StatsConfig::default()),
};
let tracer = Kerf::new_with_config(config);
let perfect_match = create_test_event_with_target(
1,
Level::INFO,
"Perfect match",
Some("service_module"),
"api_service",
Some("test.rs"),
Some(10),
Some("operation_get"),
);
let miss_span = create_test_event_with_target(
2,
Level::INFO,
"Missing span match",
Some("service_module"),
"api_service",
Some("test.rs"),
Some(11),
Some("other_span"),
);
let miss_target = create_test_event_with_target(
3,
Level::INFO,
"Missing target match",
Some("service_module"),
"web_service",
Some("test.rs"),
Some(12),
Some("operation_post"),
);
let miss_module = create_test_event_with_target(
4,
Level::INFO,
"Missing module match",
Some("utility_module"),
"api_service",
Some("test.rs"),
Some(13),
Some("operation_delete"),
);
let miss_file = create_test_event_with_target(
5,
Level::INFO,
"Missing file match",
Some("service_module"),
"api_service",
Some("test.txt"),
Some(14),
Some("operation_put"),
);
let captured_events = Arc::new(Mutex::new(Vec::<String>::new()));
let captured_clone = captured_events.clone();
tracer
.set_callback(move |event, _| {
let captured = captured_clone.clone();
let msg = event.message.clone();
tokio::spawn(async move {
let mut lock = captured.lock().await;
lock.push(msg);
});
})?
.await??;
let tx = tracer._get_sender_for_testing();
let _ = tx.send(perfect_match);
let _ = tx.send(miss_span);
let _ = tx.send(miss_target);
let _ = tx.send(miss_module);
let _ = tx.send(miss_file);
tokio::time::sleep(Duration::from_millis(200)).await;
{
let events = captured_events.lock().await;
println!("Combined all filter types events: {:?}", *events);
assert_eq!(
events.len(),
1,
"Combined filter should capture exactly 1 event"
);
assert!(
events.contains(&"Perfect match".to_string()),
"Should only capture the event matching all criteria"
);
}
Ok(())
}
#[tokio::test]
async fn test_stats_functionality() -> Result<()> {
let mut matcher_set = MatcherSet::empty();
matcher_set.add_matcher(Match::info().module_pattern("test_*"));
let config = Config {
tabs: vec![Tab::new("test_tab".to_string()).with_matcher_set(matcher_set)],
stats_config: Some(StatsConfig {
track_by_location: true,
track_by_module: true,
track_by_level: true,
max_locations: 100,
max_modules: 50,
}),
};
let tracer = Kerf::new_with_config(config);
let captured_events = Arc::new(Mutex::new(Vec::<String>::new()));
let captured_clone = captured_events.clone();
tracer
.set_callback(move |event, _tabs| {
let captured = captured_clone.clone();
let msg = event.message.clone();
tokio::spawn(async move {
let mut lock = captured.lock().await;
lock.push(msg);
});
})?
.await??;
let captured_event = create_test_event(
1,
Level::INFO,
"Captured event",
Some("test_module"),
Some("test.rs"),
Some(42),
None,
);
let dropped_event = create_test_event(
2,
Level::INFO,
"Dropped event",
Some("other_module"),
Some("test.rs"),
Some(43),
None,
);
let tx = tracer._get_sender_for_testing();
let _ = tx.send(captured_event);
let _ = tx.send(dropped_event);
tokio::time::sleep(Duration::from_millis(200)).await;
{
let captured = captured_events.lock().await;
assert_eq!(captured.len(), 1, "Should have captured 1 event");
assert!(captured.contains(&"Captured event".to_string()));
}
if let Ok(stats_rx) = tracer.get_stats() {
if let Ok(Some(snapshot)) = stats_rx.await {
assert!(snapshot.total_entries > 0, "Should have stats entries");
let mut total_captured = 0;
let mut total_dropped = 0;
for ((_level, event_type), count) in &snapshot.level_event_counts {
match event_type {
EventType::Captured => total_captured += count,
EventType::Dropped => total_dropped += count,
_ => {}
}
}
assert_eq!(total_captured, 1, "Should have 1 captured event in stats");
assert_eq!(total_dropped, 1, "Should have 1 dropped event in stats");
assert!(
!snapshot.location_stats.is_empty(),
"Should have location stats"
);
assert!(
!snapshot.module_stats.is_empty(),
"Should have module stats"
);
} else {
panic!("Failed to get stats snapshot");
}
} else {
panic!("Failed to request stats");
}
tracer.clear_stats()?.await??;
if let Ok(stats_rx) = tracer.get_stats() {
if let Ok(Some(snapshot)) = stats_rx.await {
let mut total_events = 0;
for ((_, _), count) in &snapshot.level_event_counts {
total_events += count;
}
assert_eq!(total_events, 0, "Stats should be cleared");
}
}
Ok(())
}
}