Skip to content

Commit 6685763

Browse files
committed
feat: Added middleware config for actix
1 parent 5f52c37 commit 6685763

File tree

7 files changed

+227
-26
lines changed

7 files changed

+227
-26
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ We currently only verify this crate against a recent version of Sentry hosted on
2020
[sentry.io](https://sentry.io/) but it should work with on-prem Sentry versions
2121
8.20 and later.
2222

23-
Additionally, the lowest Rust version we target is _1.22.0_.
23+
Additionally, the lowest Rust version we target is _1.24.0_.
2424

2525
## Resources
2626

integrations/sentry-actix/Cargo.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
[package]
22
name = "sentry-actix"
33
version = "0.1.0"
4-
authors = ["Armin Ronacher <armin.ronacher@active-4.com>"]
4+
authors = ["Sentry <hello@sentry.io>"]
5+
license = "Apache-2.0"
6+
readme = "README.md"
7+
repository = "https://github.com/getsentry/sentry-rust"
8+
homepage = "https://github.com/getsentry/sentry-rust"
9+
documentation = "https://docs.rs/sentry-actix"
10+
description = """
11+
Sentry client extension for actix-web
12+
"""
13+
build = "build.rs"
514

615
[dependencies]
716
actix-web = "0.6.13"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<p align="center">
2+
<a href="https://sentry.io" target="_blank" align="center">
3+
<img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" width="280">
4+
</a>
5+
<br />
6+
</p>
7+
8+
# Sentry-Actix
9+
10+
[![Build Status](https://travis-ci.org/getsentry/sentry-rust.svg?branch=master)](https://travis-ci.org/getsentry/sentry-rust)
11+
[![Crates.io](https://img.shields.io/crates/v/sentry.svg?style=flat)](https://crates.io/crates/sentry)
12+
13+
This is an actix-web integration for the Sentry crate.
14+
15+
## Requirements
16+
17+
We currently only verify this crate against a recent version of Sentry hosted on
18+
[sentry.io](https://sentry.io/) but it should work with on-prem Sentry versions
19+
8.20 and later.
20+
21+
Additionally, the lowest Rust version we target is _1.24.0_.
22+
23+
## Resources
24+
25+
* [crates.io](https://crates.io/crate/sentry)
26+
* [Documentation](https://docs.rs/sentry)
27+
* [Bug Tracker](https://github.com/getsentry/sentry-rust/issues)
28+
* [IRC](irc://chat.freenode.net/sentry) (chat.freenode.net, #sentry)
29+
* Follow [@getsentry](https://twitter.com/getsentry) on Twitter for updates

integrations/sentry-actix/examples/basic.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ extern crate actix_web;
22
extern crate sentry;
33
extern crate sentry_actix;
44

5-
use std::io;
65
use std::env;
6+
use std::io;
77

8-
use sentry_actix::CaptureSentryError;
98
use actix_web::{server, App, Error, HttpRequest};
9+
use sentry_actix::SentryMiddleware;
1010

1111
fn failing(_req: HttpRequest) -> Result<String, Error> {
1212
Err(io::Error::new(io::ErrorKind::Other, "Something went really wrong here").into())
@@ -19,7 +19,7 @@ fn main() {
1919

2020
server::new(|| {
2121
App::new()
22-
.middleware(CaptureSentryError)
22+
.middleware(SentryMiddleware::builder().emit_header(true).finish())
2323
.resource("/", |r| r.f(failing))
2424
}).bind("127.0.0.1:3001")
2525
.unwrap()

integrations/sentry-actix/src/lib.rs

Lines changed: 180 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,166 @@
1+
//! This crate adds a middleware for [`actix-web`](https://actix.rs/) that captures errors and
2+
//! report them to `Sentry`.
3+
//!
4+
//! To use this middleware just configure Sentry and then add it to your actix web app as a
5+
//! middleware. Because actix is generally working with non sendable objects and highly concurrent
6+
//! this middleware creates a new hub per request. As a result many of the sentry integrations
7+
//! such as breadcrumbs do not work unless you bind the actix hub.
8+
//!
9+
//! # Example
10+
//!
11+
//! ```
12+
//! extern crate actix_web;
13+
//! extern crate sentry;
14+
//! extern crate sentry_actix;
15+
//!
16+
//! # fn main() {
17+
//! use std::env;
18+
//! use std::io;
19+
//!
20+
//! use actix_web::{server, App, Error, HttpRequest};
21+
//! use sentry_actix::SentryMiddleware;
22+
//!
23+
//! fn failing(_req: HttpRequest) -> Result<String, Error> {
24+
//! Err(io::Error::new(io::ErrorKind::Other, "An error happens here").into())
25+
//! }
26+
//!
27+
//! fn main() {
28+
//! let _guard = sentry::init("https://public@sentry.io/1234");
29+
//! env::set_var("RUST_BACKTRACE", "1");
30+
//! sentry::integrations::panic::register_panic_handler();
31+
//!
32+
//! server::new(|| {
33+
//! App::new()
34+
//! .middleware(SentryMiddleware::new())
35+
//! .resource("/", |r| r.f(failing))
36+
//! }).bind("127.0.0.1:3001")
37+
//! .unwrap()
38+
//! .run();
39+
//! }
40+
//! # }
41+
//! ```
42+
//!
43+
//! # Reusing the Hub
44+
//!
45+
//! If you use this integration the `Hub::current()` returned hub is typically the wrong one.
46+
//! To get the request specific one you need to use the `ActixWebHubExt` trait:
47+
//!
48+
//! ```
49+
//! # extern crate sentry;
50+
//! # extern crate sentry_actix;
51+
//! # extern crate actix_web;
52+
//! # fn test(req: &actix_web::HttpRequest) {
53+
//! use sentry::{Hub, Level};
54+
//! use sentry_actix::ActixWebHubExt;
55+
//!
56+
//! let hub = Hub::from_request(req);
57+
//! hub.capture_message("Something is not well", Level::Warning);
58+
//! # }
59+
//! ```
60+
//!
61+
//! The hub can also be made current:
62+
//!
63+
//! ```
64+
//! # extern crate sentry;
65+
//! # extern crate sentry_actix;
66+
//! # extern crate actix_web;
67+
//! # fn test(req: &actix_web::HttpRequest) {
68+
//! use sentry::{Hub, Level};
69+
//! use sentry_actix::ActixWebHubExt;
70+
//!
71+
//! let hub = Hub::from_request(req);
72+
//! Hub::run(hub, || {
73+
//! sentry::capture_message("Something is not well", Level::Warning);
74+
//! });
75+
//! # }
76+
//! ```
177
extern crate actix_web;
278
extern crate failure;
379
extern crate sentry;
480
extern crate uuid;
581

6-
use std::sync::Arc;
7-
use uuid::Uuid;
882
use actix_web::middleware::{Middleware, Response, Started};
983
use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse};
1084
use failure::Fail;
1185
use sentry::integrations::failure::exception_from_single_fail;
1286
use sentry::protocol::{Event, Level};
1387
use sentry::Hub;
88+
use std::sync::Arc;
89+
use uuid::Uuid;
90+
91+
/// A helper construct that can be used to reconfigure and build the middleware.
92+
pub struct SentryMiddlewareBuilder {
93+
middleware: SentryMiddleware,
94+
}
1495

1596
/// Reports certain failures to sentry.
16-
pub struct CaptureSentryError;
97+
pub struct SentryMiddleware {
98+
hub: Option<Arc<Hub>>,
99+
emit_header: bool,
100+
capture_server_errors: bool,
101+
}
102+
103+
impl SentryMiddlewareBuilder {
104+
/// Finishes the building and returns a middleware
105+
pub fn finish(self) -> SentryMiddleware {
106+
self.middleware
107+
}
108+
109+
/// Reconfigures the middleware so that it uses a specific hub instead of the default one.
110+
pub fn with_hub(mut self, hub: Arc<Hub>) -> Self {
111+
self.middleware.hub = Some(hub);
112+
self
113+
}
114+
115+
/// Reconfigures the middleware so that it uses a specific hub instead of the default one.
116+
pub fn with_default_hub(mut self) -> Self {
117+
self.middleware.hub = None;
118+
self
119+
}
120+
121+
/// If configured the sentry id is attached to a X-Sentry-Event header.
122+
pub fn emit_header(mut self, val: bool) -> Self {
123+
self.middleware.emit_header = val;
124+
self
125+
}
17126

18-
impl<S: 'static> Middleware<S> for CaptureSentryError {
127+
/// Enables or disables error reporting.
128+
///
129+
/// The default is to report all errors.
130+
pub fn capture_server_errors(mut self, val: bool) -> Self {
131+
self.middleware.capture_server_errors = val;
132+
self
133+
}
134+
}
135+
136+
impl SentryMiddleware {
137+
/// Creates a new sentry middleware.
138+
pub fn new() -> SentryMiddleware {
139+
SentryMiddleware {
140+
hub: None,
141+
emit_header: false,
142+
capture_server_errors: true,
143+
}
144+
}
145+
146+
/// Creates a new middleware builder.
147+
pub fn builder() -> SentryMiddlewareBuilder {
148+
SentryMiddleware::new().into_builder()
149+
}
150+
151+
/// Converts the middleware into a builder.
152+
pub fn into_builder(self) -> SentryMiddlewareBuilder {
153+
SentryMiddlewareBuilder { middleware: self }
154+
}
155+
156+
fn new_hub(&self) -> Arc<Hub> {
157+
Arc::new(Hub::new_from_top(Hub::current()))
158+
}
159+
}
160+
161+
impl<S: 'static> Middleware<S> for SentryMiddleware {
19162
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started, Error> {
20-
let hub = Arc::new(Hub::new_from_top(Hub::current()));
163+
let hub = self.new_hub();
21164
let outer_req = req;
22165
let req = outer_req.clone();
23166
hub.add_event_processor(Box::new(move || {
@@ -48,36 +191,56 @@ impl<S: 'static> Middleware<S> for CaptureSentryError {
48191
Ok(Started::Done)
49192
}
50193

51-
fn response(&self, req: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response, Error> {
52-
if resp.status().is_server_error() {
53-
if let Some(error) = resp.error() {
54-
let hub = Hub::from_request(req);
55-
println!("capturing error");
56-
hub.capture_actix_error(error);
194+
fn response(
195+
&self,
196+
req: &mut HttpRequest<S>,
197+
mut resp: HttpResponse,
198+
) -> Result<Response, Error> {
199+
if self.capture_server_errors && resp.status().is_server_error() {
200+
let event_id = if let Some(error) = resp.error() {
201+
Some(Hub::from_request(req).capture_actix_error(error))
202+
} else {
203+
None
204+
};
205+
match event_id {
206+
Some(event_id) if self.emit_header => {
207+
resp.headers_mut().insert(
208+
"x-sentry-event",
209+
event_id.simple().to_string().parse().unwrap(),
210+
);
211+
}
212+
_ => {}
57213
}
58214
}
59215
Ok(Response::Done(resp))
60216
}
61217
}
62218

63219
/// Utility function that takes an actix error and reports it to the default hub.
220+
///
221+
/// This is typically not very since the actix hub is likely never bound as the
222+
/// default hub. It's generally recommended to use the `ActixWebHubExt` trait's
223+
/// extension method on the hub instead.
64224
pub fn capture_actix_error(err: &Error) -> Uuid {
65-
Hub::with_active(|hub| {
66-
hub.capture_actix_error(err)
67-
})
225+
Hub::with_active(|hub| hub.capture_actix_error(err))
68226
}
69227

70228
/// Hub extensions for actix.
71-
pub trait HubExt {
229+
pub trait ActixWebHubExt {
72230
/// Returns the hub from a given http request.
231+
///
232+
/// This requires that the `SentryMiddleware` middleware has been enabled or the
233+
/// call will panic.
73234
fn from_request<S>(req: &HttpRequest<S>) -> &Arc<Hub>;
74235
/// Captures an actix error on the given hub.
75236
fn capture_actix_error(&self, err: &Error) -> Uuid;
76237
}
77238

78-
impl HubExt for Hub {
239+
impl ActixWebHubExt for Hub {
79240
fn from_request<S>(req: &HttpRequest<S>) -> &Arc<Hub> {
80-
req.extensions().get().expect("CaptureSentryError middleware was not registered")
241+
req.extensions()
242+
.get()
243+
.expect("SentryMiddleware middleware was not registered")
81244
}
82245

83246
fn capture_actix_error(&self, err: &Error) -> Uuid {

src/integrations/error_chain.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,15 @@ where
8181
}
8282

8383
/// Hub extension methods for working with error chain
84-
pub trait HubExt {
84+
pub trait ErrorChainHubExt {
8585
/// Captures an error chain on a specific hub.
8686
fn capture_error_chain<T>(&self, e: &T) -> Uuid
8787
where
8888
T: ChainedError,
8989
T::ErrorKind: Debug + Display;
9090
}
9191

92-
impl HubExt for Hub {
92+
impl ErrorChainHubExt for Hub {
9393
fn capture_error_chain<T>(&self, e: &T) -> Uuid
9494
where
9595
T: ChainedError,

src/integrations/failure.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,14 @@ pub fn capture_fail<F: Fail + ?Sized>(fail: &F) -> Uuid {
165165
}
166166

167167
/// Hub extension methods for working with failure.
168-
pub trait HubExt {
168+
pub trait FailureHubExt {
169169
/// Captures a boxed failure (`failure::Error`).
170170
fn capture_error(&self, err: &Error) -> Uuid;
171171
/// Captures a `failure::Fail`.
172172
fn capture_fail<F: Fail + ?Sized>(&self, fail: &F) -> Uuid;
173173
}
174174

175-
impl HubExt for Hub {
175+
impl FailureHubExt for Hub {
176176
fn capture_error(&self, err: &Error) -> Uuid {
177177
self.capture_event(event_from_error(err))
178178
}

0 commit comments

Comments
 (0)