#![cfg(feature = "test")]
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use sentry::protocol::{
Attachment, Context, DynamicSamplingContext, EnvelopeHeaders, EnvelopeItem,
};
use sentry::types::{Dsn, Uuid};
#[test]
fn test_basic_capture_message() {
let mut last_event_id = None::<Uuid>;
let events = sentry::test::with_captured_events(|| {
sentry::configure_scope(|scope| {
scope.set_tag("worker", "worker1");
});
sentry::capture_message("Hello World!", sentry::Level::Warning);
last_event_id = sentry::last_event_id();
});
assert_eq!(events.len(), 1);
let event = events.into_iter().next().unwrap();
assert_eq!(event.message.unwrap(), "Hello World!");
assert_eq!(event.level, sentry::Level::Warning);
assert_eq!(
event.tags.into_iter().collect::<Vec<(String, String)>>(),
vec![("worker".to_string(), "worker1".to_string())]
);
assert_eq!(Some(event.event_id), last_event_id);
}
#[test]
fn test_event_trace_context_from_propagation_context() {
let mut last_event_id = None::<Uuid>;
let mut span = None;
let events = sentry::test::with_captured_events(|| {
sentry::configure_scope(|scope| {
span = scope.get_span();
});
sentry::capture_message("Hello World!", sentry::Level::Warning);
last_event_id = sentry::last_event_id();
});
assert_eq!(events.len(), 1);
let event = events.into_iter().next().unwrap();
let trace_context = event.contexts.get("trace");
assert!(span.is_none());
assert!(matches!(trace_context, Some(Context::Trace(_))));
}
#[test]
fn test_breadcrumbs() {
let events = sentry::test::with_captured_events(|| {
sentry::add_breadcrumb(|| sentry::Breadcrumb {
ty: "log".into(),
message: Some("Old breadcrumb to be removed".into()),
..Default::default()
});
sentry::configure_scope(|scope| scope.clear_breadcrumbs());
sentry::add_breadcrumb(|| sentry::Breadcrumb {
ty: "log".into(),
message: Some("First breadcrumb".into()),
..Default::default()
});
sentry::add_breadcrumb(sentry::Breadcrumb {
ty: "log".into(),
message: Some("Second breadcrumb".into()),
..Default::default()
});
sentry::add_breadcrumb(|| {
vec![
sentry::Breadcrumb {
ty: "log".into(),
message: Some("Third breadcrumb".into()),
..Default::default()
},
sentry::Breadcrumb {
ty: "log".into(),
message: Some("Fourth breadcrumb".into()),
..Default::default()
},
]
});
sentry::add_breadcrumb(|| None);
sentry::capture_message("Hello World!", sentry::Level::Warning);
});
assert_eq!(events.len(), 1);
let event = events.into_iter().next().unwrap();
let messages: Vec<_> = event
.breadcrumbs
.iter()
.map(|x| (x.message.as_deref().unwrap(), x.ty.as_str()))
.collect();
assert_eq!(
messages,
vec![
("First breadcrumb", "log"),
("Second breadcrumb", "log"),
("Third breadcrumb", "log"),
("Fourth breadcrumb", "log"),
]
);
}
#[test]
fn test_factory() {
struct TestTransport(Arc<AtomicUsize>);
impl sentry::Transport for TestTransport {
fn send_envelope(&self, envelope: sentry::Envelope) {
let event = envelope.event().unwrap();
assert_eq!(event.message.as_ref().unwrap(), "test");
self.0.fetch_add(1, Ordering::SeqCst);
}
}
let events = Arc::new(AtomicUsize::new(0));
let events_for_options = events.clone();
let options = sentry::ClientOptions {
dsn: "http://foo@example.com/42".parse().ok(),
transport: Some(Arc::new(
move |opts: &sentry::ClientOptions| -> Arc<dyn sentry::Transport> {
assert_eq!(opts.dsn.as_ref().unwrap().host(), "example.com");
Arc::new(TestTransport(events_for_options.clone()))
},
)),
..Default::default()
};
sentry::Hub::run(
Arc::new(sentry::Hub::new(
Some(Arc::new(options.into())),
Arc::new(Default::default()),
)),
|| {
sentry::capture_message("test", sentry::Level::Error);
},
);
assert_eq!(events.load(Ordering::SeqCst), 1);
}
#[test]
fn test_reentrant_configure_scope() {
let events = sentry::test::with_captured_events(|| {
sentry::configure_scope(|scope1| {
scope1.set_tag("which_scope", "scope1");
sentry::configure_scope(|scope2| {
scope2.set_tag("which_scope", "scope2");
});
});
sentry::capture_message("look ma, no deadlock!", sentry::Level::Info);
});
assert_eq!(events.len(), 1);
assert_eq!(events[0].tags["which_scope"], "scope1");
}
#[test]
fn test_attached_stacktrace() {
let logger = sentry_log::SentryLogger::new();
log::set_boxed_logger(Box::new(logger))
.map(|()| log::set_max_level(log::LevelFilter::Info))
.unwrap();
let options = sentry::apply_defaults(sentry::ClientOptions {
attach_stacktrace: true,
..Default::default()
});
let events = sentry::test::with_captured_events_options(
|| {
let error = "thisisnotanumber".parse::<u32>().unwrap_err();
sentry::capture_error(&error);
sentry::capture_message("some kind of message", sentry::Level::Info);
log::error!("Shit's on fire yo");
},
options,
);
assert_eq!(events.len(), 3);
let stacktraces = events
.into_iter()
.flat_map(|ev| ev.threads.into_iter().filter_map(|thrd| thrd.stacktrace));
assert_eq!(stacktraces.count(), 3);
}
#[test]
fn test_attachment_sent_from_scope() {
let envelopes = sentry::test::with_captured_envelopes(|| {
sentry::with_scope(
|scope| {
scope.add_attachment(Attachment {
buffer: vec![1, 2, 3, 4, 5, 6, 7, 8, 9],
filename: "test-file.bin".to_string(),
..Default::default()
})
},
|| sentry::capture_message("test", sentry::Level::Error),
);
});
assert_eq!(envelopes.len(), 1);
let items = envelopes[0].items().collect::<Vec<_>>();
assert_eq!(items.len(), 2);
assert!(matches!(items[1],
EnvelopeItem::Attachment(attachment)
if attachment.filename == *"test-file.bin"
&& attachment.buffer == vec![1, 2, 3, 4, 5, 6, 7, 8, 9]
));
}
#[cfg(feature = "panic")]
#[test]
fn test_panic_scope_pop() {
let options = sentry::ClientOptions::new()
.add_integration(sentry::integrations::panic::PanicIntegration::new());
let events = sentry::test::with_captured_events_options(
|| {
let hub = sentry::Hub::current();
let scope1 = hub.push_scope();
let scope2 = hub.push_scope();
let panic = std::panic::catch_unwind(|| {
drop(scope1);
});
assert!(panic.is_err());
let panic = std::panic::catch_unwind(|| {
drop(scope2);
});
assert!(panic.is_err());
},
options,
);
assert_eq!(events.len(), 2);
assert_eq!(&events[0].exception[0].ty, "panic");
assert_eq!(
events[0].exception[0].value,
Some("Popped scope guard out of order".into())
);
assert_eq!(&events[1].exception[0].ty, "panic");
assert_eq!(
events[1].exception[0].value,
Some("Popped scope guard out of order".into())
);
}
#[cfg(feature = "logs")]
#[test]
fn test_basic_capture_log() {
use std::time::SystemTime;
use sentry::{protocol::Log, protocol::LogAttribute, protocol::Map, Hub};
let options = sentry::ClientOptions {
enable_logs: true,
..Default::default()
};
let envelopes = sentry::test::with_captured_envelopes_options(
|| {
let mut attributes: Map<String, LogAttribute> = Map::new();
attributes.insert("test".into(), "a string".into());
let log = Log {
level: sentry::protocol::LogLevel::Warn,
body: "this is a test".into(),
trace_id: None,
timestamp: SystemTime::now(),
severity_number: None,
attributes,
};
Hub::current().capture_log(log);
},
options,
);
assert_eq!(envelopes.len(), 1);
let envelope = envelopes.first().expect("expected envelope");
let item = envelope.items().next().expect("expected envelope item");
match item {
EnvelopeItem::ItemContainer(container) => match container {
sentry::protocol::ItemContainer::Logs(logs) => {
let log = logs.iter().next().expect("expected log");
assert_eq!(sentry::protocol::LogLevel::Warn, log.level);
assert_eq!("this is a test", log.body);
assert!(log.trace_id.is_some());
assert!(log.severity_number.is_none());
assert!(log.attributes.contains_key("sentry.sdk.name"));
assert!(log.attributes.contains_key("sentry.sdk.version"));
assert!(log.attributes.contains_key("test"));
}
_ => panic!("expected logs"),
},
_ => panic!("expected item container"),
}
}
#[cfg(feature = "logs")]
#[test]
fn test_basic_capture_log_macro_message() {
use sentry_core::logger_info;
let options = sentry::ClientOptions {
enable_logs: true,
..Default::default()
};
let envelopes = sentry::test::with_captured_envelopes_options(
|| {
logger_info!("Hello, world!");
},
options,
);
assert_eq!(envelopes.len(), 1);
let envelope = envelopes.first().expect("expected envelope");
let item = envelope.items().next().expect("expected envelope item");
match item {
EnvelopeItem::ItemContainer(container) => match container {
sentry::protocol::ItemContainer::Logs(logs) => {
let log = logs.iter().next().expect("expected log");
assert_eq!(sentry_core::protocol::LogLevel::Info, log.level);
assert_eq!("Hello, world!", log.body);
assert!(log.trace_id.is_some());
assert!(log.severity_number.is_none());
assert!(log.attributes.contains_key("sentry.sdk.name"));
assert!(log.attributes.contains_key("sentry.sdk.version"));
}
_ => panic!("expected logs"),
},
_ => panic!("expected item container"),
}
}
#[cfg(feature = "logs")]
#[test]
fn test_basic_capture_log_macro_message_formatted() {
use sentry::protocol::LogAttribute;
use sentry_core::logger_warn;
let options = sentry::ClientOptions {
enable_logs: true,
..Default::default()
};
let envelopes = sentry::test::with_captured_envelopes_options(
|| {
let failed_requests = ["request1", "request2", "request3"];
logger_warn!(
"Critical system errors detected for user {}, total failures: {}",
"test_user",
failed_requests.len()
);
},
options,
);
assert_eq!(envelopes.len(), 1);
let envelope = envelopes.first().expect("expected envelope");
let item = envelope.items().next().expect("expected envelope item");
match item {
EnvelopeItem::ItemContainer(container) => match container {
sentry::protocol::ItemContainer::Logs(logs) => {
let log = logs.iter().next().expect("expected log");
assert_eq!(sentry_core::protocol::LogLevel::Warn, log.level);
assert_eq!(
"Critical system errors detected for user test_user, total failures: 3",
log.body
);
assert_eq!(
LogAttribute::from(
"Critical system errors detected for user {}, total failures: {}"
),
log.attributes
.get("sentry.message.template")
.unwrap()
.clone()
);
assert_eq!(
LogAttribute::from("test_user"),
log.attributes
.get("sentry.message.parameter.0")
.unwrap()
.clone()
);
assert_eq!(
LogAttribute::from(3),
log.attributes
.get("sentry.message.parameter.1")
.unwrap()
.clone()
);
assert!(log.trace_id.is_some());
assert!(log.severity_number.is_none());
assert!(log.attributes.contains_key("sentry.sdk.name"));
assert!(log.attributes.contains_key("sentry.sdk.version"));
}
_ => panic!("expected logs"),
},
_ => panic!("expected item container"),
}
}
#[cfg(feature = "logs")]
#[test]
fn test_basic_capture_log_macro_message_with_attributes() {
use sentry::protocol::LogAttribute;
use sentry_core::logger_error;
let options = sentry::ClientOptions {
enable_logs: true,
..Default::default()
};
let envelopes = sentry::test::with_captured_envelopes_options(
|| {
logger_error!(
user.id = "12345",
user.active = true,
request.duration = 150,
success = false,
"Failed to process request"
);
},
options,
);
assert_eq!(envelopes.len(), 1);
let envelope = envelopes.first().expect("expected envelope");
let item = envelope.items().next().expect("expected envelope item");
match item {
EnvelopeItem::ItemContainer(container) => match container {
sentry::protocol::ItemContainer::Logs(logs) => {
let log = logs.iter().next().expect("expected log");
assert_eq!(sentry_core::protocol::LogLevel::Error, log.level);
assert_eq!("Failed to process request", log.body);
assert_eq!(None, log.attributes.get("sentry.message.template"));
assert!(log.trace_id.is_some());
assert!(log.severity_number.is_none());
assert!(log.attributes.contains_key("sentry.sdk.name"));
assert!(log.attributes.contains_key("sentry.sdk.version"));
assert_eq!(
LogAttribute::from("12345"),
log.attributes.get("user.id").unwrap().clone()
);
assert_eq!(
LogAttribute::from(true),
log.attributes.get("user.active").unwrap().clone()
);
assert_eq!(
LogAttribute::from(150u64),
log.attributes.get("request.duration").unwrap().clone()
);
assert_eq!(
LogAttribute::from(false),
log.attributes.get("success").unwrap().clone()
);
}
_ => panic!("expected logs"),
},
_ => panic!("expected item container"),
}
}
#[cfg(feature = "logs")]
#[test]
fn test_basic_capture_log_macro_message_formatted_with_attributes() {
use sentry::protocol::LogAttribute;
use sentry_core::logger_debug;
let options = sentry::ClientOptions {
enable_logs: true,
..Default::default()
};
let envelopes = sentry::test::with_captured_envelopes_options(
|| {
logger_debug!(
hello = "test",
operation.name = "database_query",
operation.success = true,
operation.time_ms = 42,
world = 10,
"Database query {} completed in {} ms with {} results",
"users_by_region",
42,
15
);
},
options,
);
assert_eq!(envelopes.len(), 1);
let envelope = envelopes.first().expect("expected envelope");
let item = envelope.items().next().expect("expected envelope item");
match item {
EnvelopeItem::ItemContainer(container) => match container {
sentry::protocol::ItemContainer::Logs(logs) => {
let log = logs.iter().next().expect("expected log");
assert_eq!(sentry_core::protocol::LogLevel::Debug, log.level);
assert_eq!(
"Database query users_by_region completed in 42 ms with 15 results",
log.body
);
assert!(log.trace_id.is_some());
assert!(log.severity_number.is_none());
assert_eq!(
LogAttribute::from("Database query {} completed in {} ms with {} results",),
log.attributes
.get("sentry.message.template")
.unwrap()
.clone()
);
assert!(log.attributes.contains_key("sentry.sdk.name"));
assert!(log.attributes.contains_key("sentry.sdk.version"));
assert_eq!(
LogAttribute::from("test"),
log.attributes.get("hello").unwrap().clone()
);
assert_eq!(
LogAttribute::from("database_query"),
log.attributes.get("operation.name").unwrap().clone()
);
assert_eq!(
LogAttribute::from(true),
log.attributes.get("operation.success").unwrap().clone()
);
assert_eq!(
LogAttribute::from(42u64),
log.attributes.get("operation.time_ms").unwrap().clone()
);
assert_eq!(
LogAttribute::from(10),
log.attributes.get("world").unwrap().clone()
);
assert_eq!(
LogAttribute::from("Database query {} completed in {} ms with {} results"),
log.attributes
.get("sentry.message.template")
.unwrap()
.clone()
);
assert_eq!(
LogAttribute::from("users_by_region"),
log.attributes
.get("sentry.message.parameter.0")
.unwrap()
.clone()
);
assert_eq!(
LogAttribute::from(42),
log.attributes
.get("sentry.message.parameter.1")
.unwrap()
.clone()
);
assert_eq!(
LogAttribute::from(15),
log.attributes
.get("sentry.message.parameter.2")
.unwrap()
.clone()
);
}
_ => panic!("expected logs"),
},
_ => panic!("expected item container"),
}
}
#[test]
fn test_transaction_envelope_dsc_headers() {
let mut trace_id: Option<sentry::protocol::TraceId> = None;
let dsn: Option<Dsn> = "http://foo@example.com/42".parse().ok();
let envelopes = sentry::test::with_captured_envelopes_options(
|| {
let transaction_ctx = sentry::TransactionContext::new("name transaction", "op");
trace_id = Some(transaction_ctx.trace_id());
let transaction = sentry::start_transaction(transaction_ctx);
sentry::configure_scope(|scope| scope.set_span(Some(transaction.clone().into())));
transaction.finish();
},
sentry::ClientOptions {
dsn: dsn.clone(),
traces_sample_rate: 1.0,
..Default::default()
},
);
assert!(trace_id.is_some());
let trace_id = trace_id.unwrap();
assert_eq!(envelopes.len(), 1);
let envelope = envelopes.into_iter().next().unwrap();
assert!(envelope.uuid().is_some());
let uuid = envelope.uuid().copied().unwrap();
let expected = EnvelopeHeaders::new().with_event_id(uuid).with_trace(
DynamicSamplingContext::new()
.with_trace_id(trace_id)
.with_public_key(dsn.unwrap().public_key().to_owned())
.with_sample_rate(1.0)
.with_sampled(true),
);
assert_eq!(envelope.headers(), &expected);
}