#![warn(
clippy::cargo,
clippy::missing_docs_in_private_items,
clippy::nursery,
clippy::pedantic,
missing_docs
)]
#![cfg_attr(
feature = "nightly",
feature(doc_cfg),
feature(external_doc),
doc(include = "../README.md")
)]
#![cfg_attr(not(feature = "nightly"), doc = "")]
mod before_send;
mod breadcrumb;
mod event;
mod ffi;
mod logger;
mod object;
mod options;
mod panic;
#[cfg(feature = "test")]
pub mod test;
mod transport;
mod user;
mod value;
pub use before_send::BeforeSend;
use before_send::{Data as BeforeSendData, BEFORE_SEND};
pub use breadcrumb::Breadcrumb;
pub use event::{Event, Interface, Uuid};
use ffi::{CPath, CToR, RToC};
#[cfg(feature = "transport-custom")]
pub use http;
use logger::{Data as LoggerData, LOGGER};
pub use logger::{Logger, Message};
pub use object::Map;
use object::Object;
use options::Ownership;
pub use options::{Options, Shutdown};
pub use panic::set_hook;
use std::{
convert::Infallible,
fmt::{Display, Formatter, Result as FmtResult},
os::raw::c_char,
ptr,
};
use thiserror::Error;
use transport::State as TransportState;
#[cfg(feature = "transport-custom")]
pub use transport::{Dsn, Error as TransportError, Parts, Request};
pub use transport::{
Envelope, RawEnvelope, Shutdown as TransportShutdown, Transport, API_VERSION, ENVELOPE_MIME,
SDK_USER_AGENT,
};
pub use user::User;
pub use value::Value;
#[derive(Debug, Error, PartialEq)]
pub enum Error {
#[error("re-initializing the backend failed")]
ReinstallBackend,
#[error("sample rate outside of allowed range")]
SampleRateRange,
#[error("failed to initialize Sentry")]
Init,
#[error("failed to remove value from list by index")]
ListRemove,
#[error("failed to remove value from map")]
MapRemove,
#[error("failed to convert to given type")]
TryConvert(Value),
#[error("list of fingerprints is too long")]
Fingerprints,
#[cfg(feature = "transport-custom")]
#[cfg_attr(feature = "nightly", doc(cfg(feature = "transport-custom")))]
#[error("failed at custom transport")]
Transport(#[from] TransportError),
}
impl From<Infallible> for Error {
fn from(from: Infallible) -> Self {
match from {}
}
}
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub enum Level {
Debug,
Info,
Warning,
Error,
Fatal,
}
impl Display for Level {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let text = match self {
Self::Debug => "Debug",
Self::Info => "Info",
Self::Warning => "Warning",
Self::Error => "Error",
Self::Fatal => "Fatal",
};
write!(f, "{}", text)
}
}
impl Level {
const fn into_raw(self) -> i32 {
match self {
Self::Debug => sys::Level::Debug as _,
Self::Info => sys::Level::Info as _,
Self::Warning => sys::Level::Warning as _,
Self::Error => sys::Level::Error as _,
Self::Fatal => sys::Level::Fatal as _,
}
}
fn from_raw(level: i32) -> Self {
match level {
level if level == sys::Level::Debug as _ => Self::Debug,
level if level == sys::Level::Info as _ => Self::Info,
level if level == sys::Level::Warning as _ => Self::Warning,
level if level == sys::Level::Error as _ => Self::Error,
level if level == sys::Level::Fatal as _ => Self::Fatal,
_ => unreachable!("failed to convert `i32` to `Level`"),
}
}
}
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub enum Consent {
Unknown,
Revoked,
Given,
}
impl Consent {
const fn from_raw(level: sys::UserConsent) -> Self {
match level {
sys::UserConsent::Unknown => Self::Unknown,
sys::UserConsent::Revoked => Self::Revoked,
sys::UserConsent::Given => Self::Given,
}
}
}
pub fn shutdown() {
unsafe { sys::close() };
BEFORE_SEND
.lock()
.expect("failed to deallocate `BEFORE_SEND`")
.take();
LOGGER.lock().expect("failed to deallocate `LOGGER`").take();
}
#[must_use]
pub fn modules_list() -> Vec<String> {
unsafe { Value::from_raw(sys::get_modules_list()) }
.into_list()
.map(Vec::into_iter)
.map(|list| {
list.map(|value| {
value
.into_map()
.map(|mut map| map.remove("code_file").expect("module has no name"))
.and_then(Value::into_string)
})
})
.and_then(Iterator::collect)
.expect("module list has an unexpected layout")
}
pub fn clear_modulecache() {
unsafe { sys::clear_modulecache() }
}
pub fn reinstall_backend() -> Result<(), Error> {
if unsafe { sys::reinstall_backend() } == 0 {
Ok(())
} else {
Err(Error::ReinstallBackend)
}
}
pub fn set_user_consent(consent: Consent) {
match consent {
Consent::Unknown => unsafe { sys::user_consent_reset() },
Consent::Revoked => unsafe { sys::user_consent_revoke() },
Consent::Given => unsafe { sys::user_consent_give() },
}
}
#[must_use]
pub fn user_consent() -> Consent {
Consent::from_raw(unsafe { sys::user_consent_get() })
}
pub fn remove_user() {
unsafe { sys::remove_user() }
}
pub fn set_tag<S1: Into<String>, S2: Into<String>>(key: S1, value: S2) {
let key = key.into().into_cstring();
let value = value.into().into_cstring();
unsafe { sys::set_tag(key.as_ptr(), value.as_ptr()) }
}
pub fn remove_tag<S: Into<String>>(key: S) {
let key = key.into().into_cstring();
unsafe { sys::remove_tag(key.as_ptr()) }
}
pub fn set_extra<S: Into<String>, V: Into<Value>>(key: S, value: V) {
let key = key.into().into_cstring();
let value = value.into().into_raw();
unsafe { sys::set_extra(key.as_ptr(), value) }
}
pub fn remove_extra<S: Into<String>>(key: S) {
let key = key.into().into_cstring();
unsafe { sys::remove_extra(key.as_ptr()) }
}
pub fn set_context<S: Into<String>, M: Map + Into<Value>>(key: S, value: M) {
let key = key.into().into_cstring();
let value = value.into().into_raw();
unsafe { sys::set_context(key.as_ptr(), value) }
}
pub fn remove_context<S: Into<String>>(key: S) {
let key = key.into().into_cstring();
unsafe { sys::remove_context(key.as_ptr()) }
}
pub fn set_fingerprint<I: IntoIterator<Item = S>, S: Into<String>>(
fingerprints: I,
) -> Result<(), Error> {
let fingerprints: Vec<_> = fingerprints
.into_iter()
.map(Into::into)
.map(RToC::into_cstring)
.collect();
if fingerprints.len() > 32 {
Err(Error::Fingerprints)
} else if fingerprints.is_empty() {
Ok(())
} else {
let mut raw_fingerprints = [ptr::null(); 32];
for (fingerprint, raw_fingerprint) in fingerprints.iter().zip(raw_fingerprints.iter_mut()) {
*raw_fingerprint = fingerprint.as_ptr();
}
unsafe {
sys::set_fingerprint(
raw_fingerprints[0],
raw_fingerprints[1],
raw_fingerprints[2],
raw_fingerprints[3],
raw_fingerprints[4],
raw_fingerprints[5],
raw_fingerprints[6],
raw_fingerprints[7],
raw_fingerprints[8],
raw_fingerprints[9],
raw_fingerprints[10],
raw_fingerprints[11],
raw_fingerprints[12],
raw_fingerprints[13],
raw_fingerprints[14],
raw_fingerprints[15],
raw_fingerprints[16],
raw_fingerprints[17],
raw_fingerprints[18],
raw_fingerprints[19],
raw_fingerprints[20],
raw_fingerprints[21],
raw_fingerprints[22],
raw_fingerprints[23],
raw_fingerprints[24],
raw_fingerprints[25],
raw_fingerprints[26],
raw_fingerprints[27],
raw_fingerprints[28],
raw_fingerprints[29],
raw_fingerprints[30],
raw_fingerprints[31],
ptr::null::<c_char>(),
)
};
Ok(())
}
}
pub fn remove_fingerprint() {
unsafe { sys::remove_fingerprint() }
}
pub fn set_transaction<S: Into<String>>(transaction: S) {
let transaction = transaction.into().into_cstring();
unsafe { sys::set_transaction(transaction.as_ptr()) }
}
pub fn remove_transaction() {
unsafe { sys::remove_transaction() }
}
pub fn set_level(level: Level) {
unsafe { sys::set_level(level.into_raw()) }
}
pub fn start_session() {
unsafe { sys::start_session() }
}
pub fn end_session() {
unsafe { sys::end_session() }
}
#[test]
fn error() -> Result<(), Error> {
Ok::<_, Infallible>(())?;
Ok(())
}
#[test]
fn level() {
assert_eq!(-1, Level::Debug.into_raw());
assert_eq!(0, Level::Info.into_raw());
assert_eq!(1, Level::Warning.into_raw());
assert_eq!(2, Level::Error.into_raw());
assert_eq!(3, Level::Fatal.into_raw());
assert_eq!(Level::Debug, Level::from_raw(-1));
assert_eq!(Level::Info, Level::from_raw(0));
assert_eq!(Level::Warning, Level::from_raw(1));
assert_eq!(Level::Error, Level::from_raw(2));
assert_eq!(Level::Fatal, Level::from_raw(3));
}
#[cfg(test)]
#[rusty_fork::fork_test(timeout_ms = 60000)]
fn consent() -> anyhow::Result<()> {
assert_eq!(Consent::Unknown, crate::user_consent());
crate::set_user_consent(Consent::Unknown);
assert_eq!(Consent::Unknown, crate::user_consent());
crate::set_user_consent(Consent::Revoked);
assert_eq!(Consent::Unknown, crate::user_consent());
crate::set_user_consent(Consent::Given);
assert_eq!(Consent::Unknown, crate::user_consent());
let _shutdown = Options::new().init()?;
crate::set_user_consent(Consent::Given);
assert_eq!(Consent::Given, crate::user_consent());
crate::set_user_consent(Consent::Revoked);
assert_eq!(Consent::Revoked, crate::user_consent());
crate::set_user_consent(Consent::Unknown);
assert_eq!(Consent::Unknown, crate::user_consent());
Ok(())
}
#[test]
fn fingerprint() -> anyhow::Result<()> {
for len in 1..33 {
let mut fingerprints = Vec::with_capacity(len);
for fingerprint in 0..len {
fingerprints.push(fingerprint.to_string());
}
crate::set_fingerprint(fingerprints)?;
}
Ok(())
}
#[test]
#[should_panic]
fn fingerprint_invalid() {
let mut fingerprints = Vec::with_capacity(33);
for fingerprint in 0..33 {
fingerprints.push(fingerprint.to_string());
}
crate::set_fingerprint(fingerprints).unwrap()
}
#[cfg(test)]
#[rusty_fork::fork_test(timeout_ms = 60000)]
fn threaded_stress() -> anyhow::Result<()> {
use std::thread;
fn spawns(tests: Vec<fn(i32)>) {
let mut spawns = Vec::with_capacity(tests.len());
for test in tests {
let handle = thread::spawn(move || {
let mut handles = Vec::with_capacity(100);
for index in 0..100 {
handles.push(thread::spawn(move || test(index)))
}
handles
});
spawns.push(handle)
}
for spawn in spawns {
for handle in spawn.join().unwrap() {
handle.join().unwrap()
}
}
}
test::set_hook();
let mut options = Options::new();
options.set_require_user_consent(true);
let shutdown = options.init()?;
spawns(vec![
|_| {
let _modules = crate::modules_list();
},
|_| crate::clear_modulecache(),
|index| {
crate::set_user_consent(match index % 3 {
0 => Consent::Unknown,
1 => Consent::Given,
2 => Consent::Revoked,
_ => unreachable!(),
})
},
|_| {
let _ = crate::user_consent();
},
|index| {
let mut user = User::new();
user.insert("id", index);
user.set();
},
|_| crate::remove_user(),
|index| crate::set_tag(index.to_string(), index.to_string()),
|index| crate::remove_tag(index.to_string()),
|index| crate::set_extra(index.to_string(), index),
|index| crate::remove_extra(index.to_string()),
|index| crate::set_context(index.to_string(), vec![(index.to_string(), index)]),
|index| crate::remove_context(index.to_string()),
|index| crate::set_fingerprint(vec![index.to_string()]).unwrap(),
|_| crate::remove_fingerprint(),
|index| crate::set_transaction(index.to_string()),
|_| crate::remove_transaction(),
|index| {
crate::set_level(match index % 5 {
0 => Level::Debug,
1 => Level::Info,
2 => Level::Warning,
3 => Level::Error,
4 => Level::Fatal,
_ => unreachable!(),
})
},
|_| crate::start_session(),
|_| crate::end_session(),
]);
shutdown.shutdown();
test::verify_panics();
Ok(())
}