extern crate actix_web;
extern crate failure;
extern crate sentry;
extern crate uuid;
use actix_web::middleware::{Middleware, Response, Started};
use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse};
use failure::Fail;
use sentry::integrations::failure::exception_from_single_fail;
use sentry::protocol::{Event, Level};
use sentry::Hub;
use std::sync::Arc;
use uuid::Uuid;
pub struct SentryMiddlewareBuilder {
middleware: SentryMiddleware,
}
pub struct SentryMiddleware {
hub: Option<Arc<Hub>>,
emit_header: bool,
capture_server_errors: bool,
}
impl SentryMiddlewareBuilder {
pub fn finish(self) -> SentryMiddleware {
self.middleware
}
pub fn with_hub(mut self, hub: Arc<Hub>) -> Self {
self.middleware.hub = Some(hub);
self
}
pub fn with_default_hub(mut self) -> Self {
self.middleware.hub = None;
self
}
pub fn emit_header(mut self, val: bool) -> Self {
self.middleware.emit_header = val;
self
}
pub fn capture_server_errors(mut self, val: bool) -> Self {
self.middleware.capture_server_errors = val;
self
}
}
impl SentryMiddleware {
pub fn new() -> SentryMiddleware {
SentryMiddleware {
hub: None,
emit_header: false,
capture_server_errors: true,
}
}
pub fn builder() -> SentryMiddlewareBuilder {
SentryMiddleware::new().into_builder()
}
pub fn into_builder(self) -> SentryMiddlewareBuilder {
SentryMiddlewareBuilder { middleware: self }
}
fn new_hub(&self) -> Arc<Hub> {
Arc::new(Hub::new_from_top(Hub::current()))
}
}
impl<S: 'static> Middleware<S> for SentryMiddleware {
fn start(&self, req: &HttpRequest<S>) -> Result<Started, Error> {
let hub = self.new_hub();
let outer_req = req;
let req = outer_req.clone();
let client = hub.client();
hub.add_event_processor(move || {
let resource = req.resource();
let transaction = if let Some(rdef) = resource.rdef() {
Some(rdef.pattern().to_string())
} else {
if resource.name() != "" {
Some(resource.name().to_string())
} else {
None
}
};
let mut sentry_req = sentry::protocol::Request {
url: format!(
"{}://{}{}",
req.connection_info().scheme(),
req.connection_info().host(),
req.uri()
).parse()
.ok(),
method: Some(req.method().to_string()),
headers: req
.headers()
.iter()
.map(|(k, v)| (k.as_str().into(), v.to_str().unwrap_or("").into()))
.collect(),
..Default::default()
};
if client.map_or(false, |x| x.options().send_default_pii) {
if let Some(remote) = req.connection_info().remote() {
sentry_req.env.insert("REMOTE_ADDR".into(), remote.into());
}
};
Box::new(move |event| {
if event.transaction.is_none() {
event.transaction = transaction.clone();
}
if event.request.is_none() {
event.request = Some(sentry_req.clone());
}
})
});
outer_req.extensions_mut().insert(hub);
Ok(Started::Done)
}
fn response(&self, req: &HttpRequest<S>, mut resp: HttpResponse) -> Result<Response, Error> {
if self.capture_server_errors && resp.status().is_server_error() {
let event_id = if let Some(error) = resp.error() {
Some(Hub::from_request(req).capture_actix_error(error))
} else {
None
};
match event_id {
Some(event_id) if self.emit_header => {
resp.headers_mut().insert(
"x-sentry-event",
event_id.simple().to_string().parse().unwrap(),
);
}
_ => {}
}
}
Ok(Response::Done(resp))
}
}
pub fn capture_actix_error(err: &Error) -> Uuid {
Hub::with_active(|hub| hub.capture_actix_error(err))
}
pub trait ActixWebHubExt {
fn from_request<S>(req: &HttpRequest<S>) -> Arc<Hub>;
fn capture_actix_error(&self, err: &Error) -> Uuid;
}
impl ActixWebHubExt for Hub {
fn from_request<S>(req: &HttpRequest<S>) -> Arc<Hub> {
req.extensions()
.get::<Arc<Hub>>()
.expect("SentryMiddleware middleware was not registered")
.clone()
}
fn capture_actix_error(&self, err: &Error) -> Uuid {
let mut exceptions = vec![];
let mut ptr: Option<&Fail> = Some(err.as_fail());
let mut idx = 0;
while let Some(fail) = ptr {
exceptions.push(exception_from_single_fail(
fail,
if idx == 0 {
Some(err.backtrace())
} else {
fail.backtrace()
},
));
ptr = fail.cause();
idx += 1;
}
exceptions.reverse();
self.capture_event(Event {
exceptions: exceptions,
level: Level::Error,
..Default::default()
})
}
}