diff --git a/crates/deployment/src/lib.rs b/crates/deployment/src/lib.rs index 1116be61..5683e940 100644 --- a/crates/deployment/src/lib.rs +++ b/crates/deployment/src/lib.rs @@ -30,13 +30,12 @@ use services::services::{ git::{GitService, GitServiceError}, image::{ImageError, ImageService}, pr_monitor::PrMonitorService, - sentry::SentryService, worktree_manager::WorktreeError, }; use sqlx::{Error as SqlxError, types::Uuid}; use thiserror::Error; use tokio::sync::RwLock; -use utils::msg_store::MsgStore; +use utils::{msg_store::MsgStore, sentry as sentry_utils}; #[derive(Debug, Error)] pub enum DeploymentError { @@ -82,8 +81,6 @@ pub trait Deployment: Clone + Send + Sync + 'static { fn config(&self) -> &Arc>; - fn sentry(&self) -> &SentryService; - fn db(&self) -> &DBService; fn analytics(&self) -> &Option; @@ -113,8 +110,7 @@ pub trait Deployment: Clone + Send + Sync + 'static { let config = self.config().read().await; let username = config.github.username.as_deref(); let email = config.github.primary_email.as_deref(); - - self.sentry().update_scope(user_id, username, email).await; + sentry_utils::configure_user_scope(user_id, username, email); Ok(()) } diff --git a/crates/local-deployment/Cargo.toml b/crates/local-deployment/Cargo.toml index cf5a220b..e01a1872 100644 --- a/crates/local-deployment/Cargo.toml +++ b/crates/local-deployment/Cargo.toml @@ -31,8 +31,6 @@ regex = "1.11.1" notify-rust = "4.11" notify = "8.2.0" notify-debouncer-full = "0.5.0" -sentry = { version = "0.41.0", features = ["anyhow", "backtrace", "panic", "debug-images"] } -sentry-tracing = { version = "0.41.0", features = ["backtrace"] } reqwest = { version = "0.12", features = ["json"] } futures = "0.3" async-stream = "0.3" diff --git a/crates/local-deployment/src/lib.rs b/crates/local-deployment/src/lib.rs index 5a33a1bd..b4b8bc80 100644 --- a/crates/local-deployment/src/lib.rs +++ b/crates/local-deployment/src/lib.rs @@ -16,21 +16,18 @@ use services::services::{ filesystem::FilesystemService, git::GitService, image::ImageService, - sentry::SentryService, }; use tokio::sync::RwLock; use utils::{assets::config_path, msg_store::MsgStore}; use uuid::Uuid; use crate::container::LocalContainerService; - mod command; pub mod container; #[derive(Clone)] pub struct LocalDeployment { config: Arc>, - sentry: SentryService, user_id: String, db: DBService, analytics: Option, @@ -74,7 +71,6 @@ impl Deployment for LocalDeployment { save_config_to_file(&raw_config, &config_path()).await?; let config = Arc::new(RwLock::new(raw_config)); - let sentry = SentryService::new(); let user_id = generate_user_id(); let analytics = AnalyticsConfig::new().map(AnalyticsService::new); let git = GitService::new(); @@ -131,7 +127,6 @@ impl Deployment for LocalDeployment { Ok(Self { config, - sentry, user_id, db, analytics, @@ -160,10 +155,6 @@ impl Deployment for LocalDeployment { &self.config } - fn sentry(&self) -> &SentryService { - &self.sentry - } - fn db(&self) -> &DBService { &self.db } diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index b662616e..9ba31c93 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -34,8 +34,6 @@ rmcp = { version = "0.5.0", features = ["server", "transport-io"] } schemars = { workspace = true } regex = "1.11.1" toml = "0.8" -sentry = { version = "0.41.0", features = ["anyhow", "backtrace", "panic", "debug-images"] } -sentry-tracing = { version = "0.41.0", features = ["backtrace"] } reqwest = { version = "0.12", features = ["json"] } strip-ansi-escapes = "0.2.1" thiserror = { workspace = true } diff --git a/crates/server/src/bin/mcp_task_server.rs b/crates/server/src/bin/mcp_task_server.rs index 02e332be..81222318 100644 --- a/crates/server/src/bin/mcp_task_server.rs +++ b/crates/server/src/bin/mcp_task_server.rs @@ -1,25 +1,13 @@ use rmcp::{ServiceExt, transport::stdio}; use server::mcp::task_server::TaskServer; use tracing_subscriber::{EnvFilter, prelude::*}; -use utils::{port_file::read_port_file, sentry::sentry_layer}; +use utils::{ + port_file::read_port_file, + sentry::{self as sentry_utils, SentrySource, sentry_layer}, +}; fn main() -> anyhow::Result<()> { - let environment = if cfg!(debug_assertions) { - "dev" - } else { - "production" - }; - let _guard = sentry::init(( - "https://1065a1d276a581316999a07d5dffee26@o4509603705192449.ingest.de.sentry.io/4509605576441937", - sentry::ClientOptions { - release: sentry::release_name!(), - environment: Some(environment.into()), - ..Default::default() - }, - )); - sentry::configure_scope(|scope| { - scope.set_tag("source", "mcp"); - }); + sentry_utils::init_once(SentrySource::Mcp); tokio::runtime::Builder::new_multi_thread() .enable_all() .build() @@ -67,9 +55,9 @@ fn main() -> anyhow::Result<()> { let service = TaskServer::new(&base_url) .serve(stdio()) .await - .inspect_err(|e| { + .map_err(|e| { tracing::error!("serving error: {:?}", e); - sentry::capture_error(e); + e })?; service.waiting().await?; diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs index dbd1b9a8..f8198195 100644 --- a/crates/server/src/main.rs +++ b/crates/server/src/main.rs @@ -6,7 +6,10 @@ use strip_ansi_escapes::strip; use thiserror::Error; use tracing_subscriber::{EnvFilter, prelude::*}; use utils::{ - assets::asset_dir, browser::open_browser, port_file::write_port_file, sentry::sentry_layer, + assets::asset_dir, + browser::open_browser, + port_file::write_port_file, + sentry::{self as sentry_utils, SentrySource, sentry_layer}, }; #[derive(Debug, Error)] @@ -23,6 +26,8 @@ pub enum VibeKanbanError { #[tokio::main] async fn main() -> Result<(), VibeKanbanError> { + sentry_utils::init_once(SentrySource::Backend); + let log_level = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()); let filter_string = format!( "warn,server={level},services={level},db={level},executors={level},deployment={level},local_deployment={level},utils={level}", diff --git a/crates/server/src/routes/mod.rs b/crates/server/src/routes/mod.rs index 629b20be..a8da333e 100644 --- a/crates/server/src/routes/mod.rs +++ b/crates/server/src/routes/mod.rs @@ -1,5 +1,6 @@ use axum::{ Router, + middleware::from_fn_with_state, routing::{IntoMakeService, get}, }; @@ -39,6 +40,10 @@ pub fn router(deployment: DeploymentImpl) -> IntoMakeService { .merge(events::router(&deployment)) .merge(approvals::router()) .nest("/images", images::routes()) + .layer(from_fn_with_state( + deployment.clone(), + auth::sentry_user_context_middleware, + )) .with_state(deployment); Router::new() diff --git a/crates/services/Cargo.toml b/crates/services/Cargo.toml index d32aed4e..5874601c 100644 --- a/crates/services/Cargo.toml +++ b/crates/services/Cargo.toml @@ -39,8 +39,6 @@ regex = "1.11.1" notify-rust = "4.11" octocrab = "0.44" os_info = "3.12.0" -sentry = { version = "0.41.0", features = ["anyhow", "backtrace", "panic", "debug-images"] } -sentry-tracing = { version = "0.41.0", features = ["backtrace"] } reqwest = { version = "0.12", features = ["json"] } lazy_static = "1.4" futures-util = "0.3" diff --git a/crates/services/src/services/mod.rs b/crates/services/src/services/mod.rs index 91b2a9ba..fc8a3c12 100644 --- a/crates/services/src/services/mod.rs +++ b/crates/services/src/services/mod.rs @@ -16,5 +16,4 @@ pub mod github_service; pub mod image; pub mod notification; pub mod pr_monitor; -pub mod sentry; pub mod worktree_manager; diff --git a/crates/services/src/services/sentry.rs b/crates/services/src/services/sentry.rs deleted file mode 100644 index f9260dab..00000000 --- a/crates/services/src/services/sentry.rs +++ /dev/null @@ -1,33 +0,0 @@ -#[derive(Clone)] -pub struct SentryService {} - -impl Default for SentryService { - fn default() -> Self { - Self::new() - } -} - -impl SentryService { - pub fn new() -> Self { - SentryService {} - } - - pub async fn update_scope(&self, user_id: &str, username: Option<&str>, email: Option<&str>) { - let sentry_user = match (username, email) { - (Some(user), Some(email)) => sentry::User { - id: Some(user_id.to_string()), - username: Some(user.to_string()), - email: Some(email.to_string()), - ..Default::default() - }, - _ => sentry::User { - id: Some(user_id.to_string()), - ..Default::default() - }, - }; - - sentry::configure_scope(|scope| { - scope.set_user(Some(sentry_user)); - }); - } -} diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 66d897e5..e2b26bbe 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -19,6 +19,7 @@ rust-embed = "8.2" directories = "6.0.0" open = "5.3.2" regex = "1.11.1" +sentry = { version = "0.41.0", features = ["anyhow", "backtrace", "panic", "debug-images"] } sentry-tracing = { version = "0.41.0", features = ["backtrace"] } lazy_static = "1.4" futures-util = "0.3" diff --git a/crates/utils/src/sentry.rs b/crates/utils/src/sentry.rs index e6e7e1be..593d36bb 100644 --- a/crates/utils/src/sentry.rs +++ b/crates/utils/src/sentry.rs @@ -1,6 +1,71 @@ +use std::sync::OnceLock; + use sentry_tracing::{EventFilter, SentryLayer}; use tracing::Level; +const SENTRY_DSN: &str = "https://1065a1d276a581316999a07d5dffee26@o4509603705192449.ingest.de.sentry.io/4509605576441937"; + +static INIT_GUARD: OnceLock = OnceLock::new(); + +#[derive(Clone, Copy, Debug)] +pub enum SentrySource { + Backend, + Mcp, +} + +impl SentrySource { + fn tag(self) -> &'static str { + match self { + SentrySource::Backend => "backend", + SentrySource::Mcp => "mcp", + } + } +} + +fn environment() -> &'static str { + if cfg!(debug_assertions) { + "dev" + } else { + "production" + } +} + +pub fn init_once(source: SentrySource) { + INIT_GUARD.get_or_init(|| { + sentry::init(( + SENTRY_DSN, + sentry::ClientOptions { + release: sentry::release_name!(), + environment: Some(environment().into()), + ..Default::default() + }, + )) + }); + + sentry::configure_scope(|scope| { + scope.set_tag("source", source.tag()); + }); +} + +pub fn configure_user_scope(user_id: &str, username: Option<&str>, email: Option<&str>) { + let mut sentry_user = sentry::User { + id: Some(user_id.to_string()), + ..Default::default() + }; + + if let Some(username) = username { + sentry_user.username = Some(username.to_string()); + } + + if let Some(email) = email { + sentry_user.email = Some(email.to_string()); + } + + sentry::configure_scope(|scope| { + scope.set_user(Some(sentry_user)); + }); +} + pub fn sentry_layer() -> SentryLayer where S: tracing::Subscriber,