use super::SentrySettings;
use crate::ratelimit::StaticQuantaClock;
use governor::{Quota, RateLimiter};
use std::borrow::Cow;
use std::num::NonZeroU32;
use std::sync::Arc;
type Fingerprint = Cow<'static, str>;
const SENTRY_LIMITER_CLEANUP_QUOTA: Quota =
Quota::per_hour(NonZeroU32::new(3).unwrap()).allow_burst(NonZeroU32::new(1).unwrap());
#[deprecated = "Replaced by install_hook_with_settings."]
pub fn install_hook(options: &mut sentry_core::ClientOptions) {
install_hook_with_settings(options, &Default::default());
}
pub fn install_hook_with_settings(
options: &mut sentry_core::ClientOptions,
settings: &SentrySettings,
) {
let rate_limiter = settings.max_events_per_second.map(|rl| {
RateLimiter::dashmap_with_clock(Quota::per_second(rl), StaticQuantaClock::default())
});
let previous = options.before_send.take();
options.before_send = Some(Arc::new(move |mut event| {
if let Some(limiter) = &rate_limiter {
crate::ratelimit!(SENTRY_LIMITER_CLEANUP_QUOTA; limiter.retain_recent());
let fp = extract_fingerprint(&event);
if limiter.check_key(&fp).is_err() {
return None;
}
}
if let Some(prev) = &previous {
event = prev(event)?;
}
super::metrics::sentry::events_total(event.level).inc();
Some(event)
}));
}
fn extract_fingerprint(event: &sentry_core::protocol::Event<'static>) -> Fingerprint {
use sentry_core::protocol::Level;
let explicit_fp = &event.fingerprint;
if !explicit_fp.is_empty() && !is_sentry_default_fingerprint(explicit_fp) {
if let [fp] = explicit_fp.as_ref() {
return fp.clone();
}
return explicit_fp.join("::").into();
}
if let Some(msg) = &event.message {
return msg.clone().into();
}
if let Some(exc) = event.exception.first() {
if let Some(val) = &exc.value {
return val.clone().into();
}
if !exc.ty.is_empty() {
return exc.ty.clone().into();
}
}
Cow::Borrowed(match event.level {
Level::Debug => "level::debug",
Level::Info => "level::info",
Level::Warning => "level::warning",
Level::Error => "level::error",
Level::Fatal => "level::fatal",
})
}
fn is_sentry_default_fingerprint(fp: &[Cow<'_, str>]) -> bool {
if let [fp] = fp {
return matches!(fp.as_ref(), "{{ default }}" | "{{default}}");
}
false
}