forge_core_utils/
sentry.rs1use std::sync::OnceLock;
2
3use sentry_tracing::{EventFilter, SentryLayer};
4use tracing::Level;
5
6const SENTRY_DSN: &str = "https://1065a1d276a581316999a07d5dffee26@o4509603705192449.ingest.de.sentry.io/4509605576441937";
7
8static INIT_GUARD: OnceLock<sentry::ClientInitGuard> = OnceLock::new();
9
10#[derive(Clone, Copy, Debug)]
11pub enum SentrySource {
12 Backend,
13 Mcp,
14}
15
16impl SentrySource {
17 fn tag(self) -> &'static str {
18 match self {
19 SentrySource::Backend => "backend",
20 SentrySource::Mcp => "mcp",
21 }
22 }
23}
24
25fn environment() -> &'static str {
26 if cfg!(debug_assertions) {
27 "dev"
28 } else {
29 "production"
30 }
31}
32
33pub fn init_once(source: SentrySource) {
34 INIT_GUARD.get_or_init(|| {
35 sentry::init((
36 SENTRY_DSN,
37 sentry::ClientOptions {
38 release: sentry::release_name!(),
39 environment: Some(environment().into()),
40 ..Default::default()
41 },
42 ))
43 });
44
45 sentry::configure_scope(|scope| {
46 scope.set_tag("source", source.tag());
47 });
48}
49
50pub fn configure_user_scope(user_id: &str, username: Option<&str>, email: Option<&str>) {
51 let mut sentry_user = sentry::User {
52 id: Some(user_id.to_string()),
53 ..Default::default()
54 };
55
56 if let Some(username) = username {
57 sentry_user.username = Some(username.to_string());
58 }
59
60 if let Some(email) = email {
61 sentry_user.email = Some(email.to_string());
62 }
63
64 sentry::configure_scope(|scope| {
65 scope.set_user(Some(sentry_user));
66 });
67}
68
69pub fn sentry_layer<S>() -> SentryLayer<S>
70where
71 S: tracing::Subscriber,
72 S: for<'a> tracing_subscriber::registry::LookupSpan<'a>,
73{
74 SentryLayer::default()
75 .span_filter(|meta| {
76 matches!(
77 *meta.level(),
78 Level::DEBUG | Level::INFO | Level::WARN | Level::ERROR
79 )
80 })
81 .event_filter(|meta| match *meta.level() {
82 Level::ERROR => EventFilter::Event,
83 Level::DEBUG | Level::INFO | Level::WARN => EventFilter::Breadcrumb,
84 Level::TRACE => EventFilter::Ignore,
85 })
86}