mirror of https://github.com/buster-so/buster.git
sentry setup
This commit is contained in:
parent
6113ff6b92
commit
41f60ec874
|
@ -65,3 +65,4 @@ node_modules/
|
|||
|
||||
/prds
|
||||
.DS_Store
|
||||
.aider*
|
||||
|
|
|
@ -74,8 +74,9 @@ redis = { version = "0.27.5", features = [
|
|||
"tls-rustls-webpki-roots",
|
||||
] }
|
||||
resend-rs = "0.10.0"
|
||||
sentry = { version = "0.37.0", features = ["tokio", "sentry-tracing"] }
|
||||
sentry = { version = "0.37.0", features = ["tokio"] }
|
||||
sentry-tower = { version = "0.37.0", features = ["axum", "http"] }
|
||||
sentry-tracing = { version = "0.37.0"}
|
||||
serde_urlencoded = "0.7.1"
|
||||
snowflake-api = "0.11.0"
|
||||
tempfile = "3.10.1"
|
||||
|
|
|
@ -22,9 +22,16 @@ serde_urlencoded = { workspace = true }
|
|||
|
||||
# Web framework dependencies
|
||||
axum = { workspace = true }
|
||||
tower = { workspace = true }
|
||||
tower-http = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
|
||||
# Sentry dependencies
|
||||
sentry = { workspace = true }
|
||||
sentry-tower = { workspace = true }
|
||||
sentry-tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
|
||||
# Internal workspace dependencies
|
||||
database = { path = "../database" }
|
||||
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
//! Error handling middleware with Sentry integration
|
||||
//!
|
||||
//! This module provides middleware for error tracking and logging with Sentry
|
||||
|
||||
use std::env;
|
||||
use std::fmt::Display;
|
||||
use anyhow::Error;
|
||||
use axum::extract::Request;
|
||||
use sentry::protocol::{Event, Level};
|
||||
use tower::ServiceBuilder;
|
||||
use tracing::{error, warn};
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||
|
||||
/// Creates a Sentry layer for the Axum application
|
||||
///
|
||||
/// This function configures Sentry error tracking for an Axum application.
|
||||
/// It adds two layers:
|
||||
/// 1. NewSentryLayer - Creates a new Sentry hub for each request
|
||||
/// 2. SentryHttpLayer - Automatically creates transactions for HTTP requests
|
||||
///
|
||||
/// # Returns
|
||||
/// A ServiceBuilder with the Sentry layers configured
|
||||
pub fn sentry_layer() -> ServiceBuilder<tower::layer::util::Stack<
|
||||
sentry_tower::SentryHttpLayer,
|
||||
tower::layer::util::Stack<
|
||||
sentry_tower::SentryLayer<sentry_tower::NewFromTopProvider, std::sync::Arc<sentry::Hub>, axum::extract::Request>,
|
||||
tower::layer::util::Identity
|
||||
>
|
||||
>> {
|
||||
ServiceBuilder::new()
|
||||
.layer(sentry_tower::NewSentryLayer::<Request>::new_from_top())
|
||||
.layer(sentry_tower::SentryHttpLayer::with_transaction())
|
||||
}
|
||||
|
||||
/// Initializes the Sentry client with proper configuration
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `dsn` - The Sentry DSN (Data Source Name)
|
||||
///
|
||||
/// # Returns
|
||||
/// A Sentry client guard that keeps the client alive
|
||||
pub fn init_sentry(dsn: &str) -> Option<sentry::ClientInitGuard> {
|
||||
let environment = env::var("ENVIRONMENT").unwrap_or_else(|_| "development".to_string());
|
||||
let is_development = environment == "development";
|
||||
|
||||
if is_development {
|
||||
return None;
|
||||
}
|
||||
|
||||
let options = sentry::ClientOptions {
|
||||
release: sentry::release_name!(),
|
||||
environment: Some(environment.into()),
|
||||
traces_sample_rate: 1.0,
|
||||
attach_stacktrace: true,
|
||||
default_integrations: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Some(sentry::init((dsn, options)))
|
||||
}
|
||||
|
||||
/// Determines if Sentry should be enabled based on the environment
|
||||
///
|
||||
/// # Returns
|
||||
/// true if Sentry should be enabled, false otherwise
|
||||
pub fn is_sentry_enabled() -> bool {
|
||||
let environment = env::var("ENVIRONMENT").unwrap_or_else(|_| "development".to_string());
|
||||
environment != "development"
|
||||
}
|
||||
|
||||
/// Initializes the tracing subscriber with optional Sentry integration
|
||||
///
|
||||
/// This function creates a tracing subscriber and conditionally adds Sentry integration
|
||||
/// based on the environment.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `env_filter` - The environment filter to use
|
||||
///
|
||||
/// # Returns
|
||||
/// Unit, after initializing the global subscriber
|
||||
pub fn init_tracing_subscriber(env_filter: EnvFilter) {
|
||||
// Create a base registry
|
||||
let registry = tracing_subscriber::registry()
|
||||
.with(env_filter)
|
||||
.with(tracing_subscriber::fmt::layer());
|
||||
|
||||
if is_sentry_enabled() {
|
||||
// Add Sentry layer if Sentry is enabled
|
||||
registry.with(sentry_tracing::layer()).init();
|
||||
} else {
|
||||
// Otherwise just initialize the registry as is
|
||||
registry.init();
|
||||
}
|
||||
}
|
||||
|
||||
/// Report an error to Sentry and also log it with tracing
|
||||
///
|
||||
/// This function should be used when you want to both log an error and report it to Sentry.
|
||||
/// It accepts any error type that implements Display + Send + Sync + 'static.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `err` - The error to report
|
||||
/// * `msg` - Optional additional context message
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use middleware::error::report_error;
|
||||
///
|
||||
/// if let Err(e) = some_operation() {
|
||||
/// report_error(e, Some("Failed during important operation"));
|
||||
/// return some_fallback();
|
||||
/// }
|
||||
/// ```
|
||||
pub fn report_error<E>(err: E, msg: Option<&str>)
|
||||
where
|
||||
E: Display + Send + Sync + 'static
|
||||
{
|
||||
let error_msg = if let Some(context) = msg {
|
||||
format!("{}: {}", context, err)
|
||||
} else {
|
||||
format!("{}", err)
|
||||
};
|
||||
|
||||
// Log the error with tracing
|
||||
error!("{}", error_msg);
|
||||
|
||||
// Report to Sentry
|
||||
sentry::capture_event(Event {
|
||||
message: Some(error_msg),
|
||||
level: Level::Error,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
/// Report a warning to Sentry and also log it with tracing
|
||||
///
|
||||
/// Similar to report_error but for warning level events
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `warning` - The warning message
|
||||
/// * `context` - Optional additional context
|
||||
pub fn report_warning(warning: &str, context: Option<&str>) {
|
||||
let warning_msg = if let Some(ctx) = context {
|
||||
format!("{}: {}", ctx, warning)
|
||||
} else {
|
||||
warning.to_string()
|
||||
};
|
||||
|
||||
// Log the warning with tracing
|
||||
warn!("{}", warning_msg);
|
||||
|
||||
// Report to Sentry
|
||||
sentry::capture_event(Event {
|
||||
message: Some(warning_msg),
|
||||
level: Level::Warning,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
/// Capture an anyhow::Error and report it to Sentry
|
||||
///
|
||||
/// This function is particularly useful for handling anyhow errors,
|
||||
/// which are commonly used in the codebase.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `err` - The anyhow error to report
|
||||
/// * `msg` - Optional additional context message
|
||||
pub fn capture_anyhow(err: &Error, msg: Option<&str>) {
|
||||
// Extract the error chain for better context
|
||||
let mut err_msg = if let Some(context) = msg {
|
||||
format!("{}: {}", context, err)
|
||||
} else {
|
||||
format!("{}", err)
|
||||
};
|
||||
|
||||
// Add the error chain for better context
|
||||
let mut source = err.source();
|
||||
while let Some(err) = source {
|
||||
err_msg.push_str(&format!("\nCaused by: {}", err));
|
||||
source = err.source();
|
||||
}
|
||||
|
||||
// Log the error with tracing
|
||||
error!("{}", err_msg);
|
||||
|
||||
// Capture the full error chain in Sentry
|
||||
sentry::capture_event(Event {
|
||||
message: Some(err_msg),
|
||||
level: Level::Error,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
|
@ -6,8 +6,18 @@
|
|||
pub mod auth;
|
||||
pub mod cors;
|
||||
pub mod types;
|
||||
pub mod error;
|
||||
|
||||
// Re-export commonly used types
|
||||
pub use auth::auth;
|
||||
pub use cors::cors;
|
||||
pub use error::{
|
||||
sentry_layer,
|
||||
init_sentry,
|
||||
is_sentry_enabled,
|
||||
init_tracing_subscriber,
|
||||
report_error,
|
||||
report_warning,
|
||||
capture_anyhow
|
||||
};
|
||||
pub use types::{AuthenticatedUser, OrganizationMembership, TeamMembership};
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::env;
|
|||
use std::sync::Arc;
|
||||
|
||||
use axum::{Extension, Router, extract::Request};
|
||||
use middleware::cors::cors;
|
||||
use middleware::{cors::cors, error::{init_sentry, sentry_layer, init_tracing_subscriber}};
|
||||
use database::{self, pool::init_pools};
|
||||
use diesel::{Connection, PgConnection};
|
||||
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
|
||||
|
@ -30,33 +30,21 @@ async fn main() {
|
|||
.install_default()
|
||||
.expect("Failed to install default crypto provider");
|
||||
|
||||
// Only initialize Sentry if not in development environment
|
||||
let _guard = if !is_development {
|
||||
Some(sentry::init((
|
||||
"https://a417fbed1de30d2714a8afbe38d5bc1b@o4505360096428032.ingest.us.sentry.io/4507360721043456",
|
||||
sentry::ClientOptions {
|
||||
release: sentry::release_name!(),
|
||||
environment: Some(environment.clone().into()),
|
||||
traces_sample_rate: 1.0,
|
||||
..Default::default()
|
||||
}
|
||||
)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Initialize Sentry using our middleware helper
|
||||
let _guard = init_sentry(
|
||||
"https://a417fbed1de30d2714a8afbe38d5bc1b@o4505360096428032.ingest.us.sentry.io/4507360721043456"
|
||||
);
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| {
|
||||
let log_level = env::var("LOG_LEVEL")
|
||||
.unwrap_or_else(|_| "warn".to_string())
|
||||
.to_uppercase();
|
||||
EnvFilter::new(log_level)
|
||||
}),
|
||||
)
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.init();
|
||||
// Set up the tracing subscriber with conditional Sentry integration
|
||||
let log_level = env::var("LOG_LEVEL")
|
||||
.unwrap_or_else(|_| "warn".to_string())
|
||||
.to_uppercase();
|
||||
|
||||
let env_filter = EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| EnvFilter::new(log_level));
|
||||
|
||||
// Initialize the tracing subscriber with Sentry integration using our middleware helper
|
||||
init_tracing_subscriber(env_filter);
|
||||
|
||||
if let Err(e) = init_pools().await {
|
||||
tracing::error!("Failed to initialize database pools: {}", e);
|
||||
|
@ -78,7 +66,7 @@ async fn main() {
|
|||
let (shutdown_tx, _) = broadcast::channel::<()>(1);
|
||||
let shutdown_tx = Arc::new(shutdown_tx);
|
||||
|
||||
// Build the router with or without Sentry layers based on environment
|
||||
// Base router configuration
|
||||
let app = Router::new()
|
||||
.merge(protected_router)
|
||||
.merge(public_router)
|
||||
|
@ -87,13 +75,9 @@ async fn main() {
|
|||
.layer(CompressionLayer::new())
|
||||
.layer(Extension(shutdown_tx.clone()));
|
||||
|
||||
// Add Sentry layers if not in development
|
||||
// Add Sentry layers if not in development using our middleware helper
|
||||
let app = if !is_development {
|
||||
app.layer(
|
||||
ServiceBuilder::new()
|
||||
.layer(sentry_tower::NewSentryLayer::<Request>::new_from_top())
|
||||
.layer(sentry_tower::SentryHttpLayer::with_transaction())
|
||||
)
|
||||
app.layer(sentry_layer())
|
||||
} else {
|
||||
app
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue