pub mod fingerprint;
pub mod layer;
pub mod store;
pub mod types;
pub use layer::BugCaptureLayer;
pub use store::{DEFAULT_CAPTURE_CAPACITY, ErrorStore};
pub use types::CapturedError;
#[doc(hidden)]
pub static BUG_CAPTURE_ENV_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
pub const TRUSTY_NO_BUG_CAPTURE_ENV: &str = "TRUSTY_NO_BUG_CAPTURE";
#[must_use]
pub fn bug_capture_layer(
app_name: &str,
capacity: usize,
crate_version: impl Into<String>,
) -> (BugCaptureLayer, ErrorStore) {
let store = ErrorStore::open(app_name, capacity);
let layer = BugCaptureLayer::new(store.clone(), crate_version);
(layer, store)
}
#[must_use]
pub fn init_capture_layer(
app_name: &str,
capacity: usize,
crate_version: impl Into<String>,
) -> (BugCaptureLayer, ErrorStore) {
bug_capture_layer(app_name, capacity, crate_version)
}
#[cfg(test)]
mod tests {
use super::*;
use tracing_subscriber::layer::SubscriberExt as _;
#[test]
fn bug_capture_layer_constructor() {
let _guard = BUG_CAPTURE_ENV_TEST_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
let store_path = None; let store = ErrorStore::with_path(store_path, 10);
let layer = BugCaptureLayer::new(store.clone(), "0.1.0");
let subscriber = tracing_subscriber::registry().with(layer);
tracing::subscriber::with_default(subscriber, || {
tracing::error!("constructor test error");
});
let records = store.recent_errors(10);
assert_eq!(records.len(), 1);
assert_eq!(records[0].message, "constructor test error");
assert_eq!(records[0].crate_version, "0.1.0");
}
#[test]
fn captured_error_serde_round_trip() {
let original = CapturedError {
timestamp_secs: 1_700_000_000,
crate_target: "trusty_search::indexer".to_string(),
crate_version: "1.0.0".to_string(),
message: "index open failed".to_string(),
fields: "path=/tmp/idx".to_string(),
file: Some("src/indexer.rs".to_string()),
line: Some(42),
os: "macos".to_string(),
arch: "aarch64".to_string(),
fingerprint: "a".repeat(64),
};
let json = serde_json::to_string(&original).unwrap();
let back: CapturedError = serde_json::from_str(&json).unwrap();
assert_eq!(back, original);
}
#[test]
fn captured_error_summary_format() {
let rec = CapturedError {
timestamp_secs: 0,
crate_target: "trusty_memory".to_string(),
crate_version: "0.5.0".to_string(),
message: "palace not found".to_string(),
fields: String::new(),
file: Some("src/palace.rs".to_string()),
line: Some(99),
os: "linux".to_string(),
arch: "x86_64".to_string(),
fingerprint: "b".repeat(64),
};
let summary = rec.summary();
assert!(summary.contains("trusty_memory"), "{summary}");
assert!(summary.contains("palace not found"), "{summary}");
assert!(summary.contains("src/palace.rs"), "{summary}");
assert!(summary.contains("99"), "{summary}");
}
#[test]
fn init_capture_layer_returns_store() {
let _guard = BUG_CAPTURE_ENV_TEST_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
let store = ErrorStore::with_path(None, 5);
let layer = BugCaptureLayer::new(store.clone(), "0.2.0");
let subscriber = tracing_subscriber::registry().with(layer);
tracing::subscriber::with_default(subscriber, || {
tracing::error!("init capture test");
});
assert_eq!(store.len(), 1);
}
}