use conduit::{Host, RequestExt, Scheme};
use conduit_middleware::{AfterResult, BeforeResult, Middleware};
use sentry_core::protocol::{ClientSdkPackage, Event, Request, SessionStatus};
use sentry_core::{Hub, ScopeGuard};
use std::borrow::Cow;
pub struct SentryMiddleware {
track_sessions: bool,
with_pii: bool,
}
impl Default for SentryMiddleware {
fn default() -> Self {
let (with_pii, track_sessions) = Hub::with_active(|hub| {
let client = hub.client();
let with_pii = client
.as_ref()
.map_or(false, |client| client.options().send_default_pii);
let track_sessions = client.as_ref().map_or(false, |client| {
let options = client.options();
options.auto_session_tracking
&& options.session_mode == sentry_core::SessionMode::Request
});
(with_pii, track_sessions)
});
SentryMiddleware {
track_sessions,
with_pii,
}
}
}
impl SentryMiddleware {
pub fn new() -> SentryMiddleware {
Default::default()
}
}
impl Middleware for SentryMiddleware {
fn before(&self, req: &mut dyn RequestExt) -> BeforeResult {
let scope = Hub::with_active(|hub| hub.push_scope());
if self.track_sessions {
sentry_core::start_session();
}
let sentry_req = sentry_request_from_http(req, self.with_pii);
sentry_core::configure_scope(|scope| {
scope.add_event_processor(Box::new(move |event| {
Some(process_event(event, &sentry_req))
}));
});
req.mut_extensions().insert(scope);
Ok(())
}
fn after(&self, req: &mut dyn RequestExt, result: AfterResult) -> AfterResult {
if let Some(scope) = req.mut_extensions().remove::<ScopeGuard>() {
#[cfg(feature = "router")]
{
sentry_core::configure_scope(|scope| {
use conduit_router::RoutePattern;
let transaction = req
.extensions()
.get::<RoutePattern>()
.map(|pattern| pattern.pattern());
scope.set_transaction(transaction);
});
}
if let Err(error) = &result {
sentry_core::capture_error(error.as_ref());
}
if self.track_sessions {
let status = match &result {
Ok(_) => SessionStatus::Exited,
Err(_) => SessionStatus::Abnormal,
};
sentry_core::end_session_with_status(status);
}
drop(scope);
}
result
}
}
fn sentry_request_from_http(request: &dyn RequestExt, with_pii: bool) -> Request {
let method = Some(request.method().to_string());
let scheme = match request.scheme() {
Scheme::Http => "http",
Scheme::Https => "https",
};
let host = match request.host() {
Host::Name(name) => Cow::from(name),
Host::Socket(addr) => Cow::from(addr.to_string()),
};
let path = request.path();
let mut url = format!("{}://{}{}", scheme, host, path);
if let Some(query_string) = request.query_string() {
url += "?";
url += query_string;
}
let headers = request
.headers()
.iter()
.filter(|(_name, value)| !value.is_sensitive())
.map(|(k, v)| (k.to_string(), v.to_str().unwrap_or_default().to_string()))
.collect();
let mut sentry_req = Request {
url: url.parse().ok(),
method,
headers,
..Default::default()
};
if with_pii {
let remote_addr = request.remote_addr().to_string();
sentry_req.env.insert("REMOTE_ADDR".into(), remote_addr);
};
sentry_req
}
fn process_event(mut event: Event<'static>, request: &Request) -> Event<'static> {
if event.request.is_none() {
event.request = Some(request.clone());
}
if let Some(sdk) = event.sdk.take() {
let mut sdk = sdk.into_owned();
sdk.packages.push(ClientSdkPackage {
name: "sentry-conduit".into(),
version: env!("CARGO_PKG_VERSION").into(),
});
event.sdk = Some(Cow::Owned(sdk));
}
event
}