pub use sentry::{ClientInitGuard, ClientOptions, IntoDsn, User};
use af_core::prelude::*;
use std::collections::BTreeMap;
pub type Fingerprint = Cow<'static, [Cow<'static, str>]>;
pub fn init(options: impl Into<ClientOptions>) -> ClientInitGuard {
let options = options.into();
sentry::init(options)
}
#[macro_export]
macro_rules! error {
($type_name:expr, $format:literal, $($args:tt)+) => {
$crate::Error::new($type_name).with_description(format_args!($format, $($args)+))
};
($type_name:expr, $($args:tt)+) => {
$crate::Error::new($type_name).with_description($($args)+)
};
($($args:tt)+) => {
$crate::Error::new($($args)*)
};
}
pub fn is_enabled() -> bool {
sentry::Hub::with(|hub| hub.client().map(|c| c.is_enabled()).unwrap_or_default())
}
#[derive(Debug)]
pub struct Error {
pub description: String,
pub detail: String,
pub fingerprint: Fingerprint,
pub type_name: String,
pub tags: BTreeMap<String, String>,
pub user: User,
pub uuid: Uuid,
}
const DEFAULT_FINGERPRINT: Fingerprint =
Cow::Borrowed(&[Cow::Borrowed("{{ type }}"), Cow::Borrowed("{{ tags.environment }}")]);
impl Error {
pub fn new(type_name: impl Into<String>) -> Self {
Self {
description: default(),
detail: default(),
fingerprint: DEFAULT_FINGERPRINT,
type_name: type_name.into(),
tags: default(),
user: default(),
uuid: Uuid::new(),
}
}
pub fn set_description(&mut self, description: impl ToString) {
let mut description = description.to_string();
if self.detail.is_empty() {
self.detail = description.clone();
}
if let Some(i) = description.find('\n') {
description.truncate(i);
description.truncate(description.trim_end().len());
}
if description.ends_with(':') {
description.pop();
description.push('.');
}
if description.len() > 256 {
description.truncate(255);
description.push('…');
}
self.description = description;
}
pub fn set_detail(&mut self, detail: impl ToString) {
self.detail = detail.to_string();
}
pub fn set_tag(&mut self, name: impl Into<String>, value: impl ToString) {
self.tags.insert(name.into(), value.to_string());
}
pub fn with_description(mut self, description: impl ToString) -> Self {
self.set_description(description);
self
}
pub fn with_detail(mut self, detail: impl ToString) -> Self {
self.set_detail(detail);
self
}
pub fn with_fingerprint(mut self, fingerprint: Fingerprint) -> Self {
self.fingerprint = fingerprint;
self
}
pub fn with_tag(mut self, name: impl Into<String>, value: impl ToString) -> Self {
self.set_tag(name, value);
self
}
pub fn with_user(mut self, user: User) -> Self {
self.user = user;
self
}
pub fn with_user_id(mut self, id: impl ToString) -> Self {
self.user.id = Some(id.to_string());
self
}
pub fn report(mut self) -> Uuid {
let uuid = self.report_mut();
mem::forget(self);
uuid
}
fn report_mut(&mut self) -> Uuid {
let mut event = sentry::protocol::Event::new();
if !self.detail.is_empty() {
event.message = Some(mem::take(&mut self.detail));
}
event.exception.values.push(sentry::protocol::Exception {
ty: mem::take(&mut self.type_name),
value: Some(mem::take(&mut self.description)),
..default()
});
event.fingerprint = mem::replace(&mut self.fingerprint, DEFAULT_FINGERPRINT);
mem::swap(&mut event.tags, &mut self.tags);
if let Some(env) = event.tags.remove("environment") {
event.environment = Some(env.into());
}
event.user = Some(mem::take(&mut self.user));
sentry::capture_event(event).into()
}
}
impl Drop for Error {
fn drop(&mut self) {
self.report_mut();
}
}