Skip to content

Commit 8f397f3

Browse files
committed
middleware::log_request: Split Sentry error reporting code into dedicated middleware
1 parent da0b61c commit 8f397f3

File tree

3 files changed

+111
-95
lines changed

3 files changed

+111
-95
lines changed

src/middleware.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use self::head::Head;
1111
use self::known_error_to_json::KnownErrorToJson;
1212
use self::log_connection_pool_status::LogConnectionPoolStatus;
1313
use self::request_timing::RequestTiming;
14+
use self::sentry::SentryMiddleware;
1415
use self::static_or_continue::StaticOrContinue;
1516
use self::update_metrics::UpdateMetrics;
1617

@@ -27,6 +28,7 @@ pub mod log_request;
2728
mod normalize_path;
2829
mod request_timing;
2930
mod require_user_agent;
31+
mod sentry;
3032
mod static_or_continue;
3133
mod update_metrics;
3234

@@ -47,6 +49,7 @@ pub fn build_middleware(app: Arc<App>, endpoints: RouteBuilder) -> MiddlewareBui
4749

4850
if env != Env::Test {
4951
m.add(ensure_well_formed_500::EnsureWellFormed500);
52+
m.add(SentryMiddleware::default());
5053
m.add(log_request::LogRequests::default());
5154
}
5255

src/middleware/log_request.rs

Lines changed: 3 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,14 @@
44
use super::prelude::*;
55
use crate::util::request_header;
66

7-
use conduit::{header, Host, RequestExt, Scheme, StatusCode};
8-
use conduit_cookie::RequestSession;
9-
use sentry::Level;
7+
use conduit::{header, RequestExt, StatusCode};
108

119
use crate::middleware::normalize_path::OriginalPath;
1210
use crate::middleware::request_timing::ResponseTime;
1311
use std::fmt::{self, Display, Formatter};
1412

1513
const SLOW_REQUEST_THRESHOLD_MS: u64 = 1000;
1614

17-
const FILTERED_HEADERS: &[&str] = &["Authorization", "Cookie", "X-Real-Ip", "X-Forwarded-For"];
18-
1915
#[derive(Default)]
2016
pub(super) struct LogRequests();
2117

@@ -32,14 +28,12 @@ impl Middleware for LogRequests {
3228
}
3329
);
3430

35-
report_to_sentry(req, &res, response_time.as_millis());
36-
3731
res
3832
}
3933
}
4034

41-
struct CustomMetadata {
42-
entries: Vec<(&'static str, String)>,
35+
pub struct CustomMetadata {
36+
pub entries: Vec<(&'static str, String)>,
4337
}
4438

4539
pub fn add_custom_metadata<V: Display>(req: &mut dyn RequestExt, key: &'static str, value: V) {
@@ -54,92 +48,6 @@ pub fn add_custom_metadata<V: Display>(req: &mut dyn RequestExt, key: &'static s
5448
}
5549
}
5650

57-
fn report_to_sentry(req: &dyn RequestExt, res: &AfterResult, response_time: u64) {
58-
let (message, level) = match res {
59-
Err(e) => (e.to_string(), Level::Error),
60-
Ok(_) => return,
61-
};
62-
63-
let config = |scope: &mut sentry::Scope| {
64-
let method = Some(req.method().as_str().to_owned());
65-
66-
let scheme = match req.scheme() {
67-
Scheme::Http => "http",
68-
Scheme::Https => "https",
69-
};
70-
71-
let host = match req.host() {
72-
Host::Name(name) => name.to_owned(),
73-
Host::Socket(addr) => addr.to_string(),
74-
};
75-
76-
let path = &req.extensions().find::<OriginalPath>().unwrap().0;
77-
78-
let url = format!("{}://{}{}", scheme, host, path).parse().ok();
79-
80-
{
81-
let id = req.session().get("user_id").map(|str| str.to_string());
82-
83-
let user = sentry::User {
84-
id,
85-
..Default::default()
86-
};
87-
88-
scope.set_user(Some(user));
89-
}
90-
91-
{
92-
let headers = req
93-
.headers()
94-
.iter()
95-
.filter(|(k, _v)| !FILTERED_HEADERS.iter().any(|name| k == name))
96-
.map(|(k, v)| (k.to_string(), v.to_str().unwrap_or_default().to_string()))
97-
.collect();
98-
99-
let sentry_req = sentry::protocol::Request {
100-
url,
101-
method,
102-
headers,
103-
..Default::default()
104-
};
105-
106-
scope.add_event_processor(Box::new(move |mut event| {
107-
if event.request.is_none() {
108-
event.request = Some(sentry_req.clone());
109-
}
110-
Some(event)
111-
}));
112-
}
113-
114-
if let Some(request_id) = req
115-
.headers()
116-
.get("x-request-id")
117-
.and_then(|value| value.to_str().ok())
118-
{
119-
scope.set_tag("request.id", request_id);
120-
}
121-
122-
{
123-
let status = res
124-
.as_ref()
125-
.map(|resp| resp.status())
126-
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
127-
128-
scope.set_tag("response.status", status.as_str());
129-
}
130-
131-
scope.set_extra("Response time [ms]", response_time.into());
132-
133-
if let Some(metadata) = req.extensions().find::<CustomMetadata>() {
134-
for (key, value) in &metadata.entries {
135-
scope.set_extra(key, value.to_string().into());
136-
}
137-
}
138-
};
139-
140-
sentry::with_scope(config, || sentry::capture_message(&message, level));
141-
}
142-
14351
#[cfg(test)]
14452
pub(crate) fn get_log_message(req: &dyn RequestExt, key: &'static str) -> String {
14553
// Unwrap shouldn't panic as no other code has access to the private struct to remove it

src/middleware/sentry.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
use super::prelude::*;
2+
use crate::middleware::log_request::CustomMetadata;
3+
use crate::middleware::normalize_path::OriginalPath;
4+
use crate::middleware::request_timing::ResponseTime;
5+
use conduit::{Host, RequestExt, Scheme, StatusCode};
6+
use conduit_cookie::RequestSession;
7+
use sentry::Level;
8+
9+
const FILTERED_HEADERS: &[&str] = &["Authorization", "Cookie", "X-Real-Ip", "X-Forwarded-For"];
10+
11+
#[derive(Default)]
12+
pub struct SentryMiddleware();
13+
14+
impl Middleware for SentryMiddleware {
15+
fn after(&self, req: &mut dyn RequestExt, res: AfterResult) -> AfterResult {
16+
let (message, level) = match res.as_ref() {
17+
Err(e) => (e.to_string(), Level::Error),
18+
Ok(_) => return res,
19+
};
20+
21+
let config = |scope: &mut sentry::Scope| {
22+
let method = Some(req.method().as_str().to_owned());
23+
24+
let scheme = match req.scheme() {
25+
Scheme::Http => "http",
26+
Scheme::Https => "https",
27+
};
28+
29+
let host = match req.host() {
30+
Host::Name(name) => name.to_owned(),
31+
Host::Socket(addr) => addr.to_string(),
32+
};
33+
34+
let path = &req.extensions().find::<OriginalPath>().unwrap().0;
35+
36+
let url = format!("{}://{}{}", scheme, host, path).parse().ok();
37+
38+
{
39+
let id = req.session().get("user_id").map(|str| str.to_string());
40+
41+
let user = sentry::User {
42+
id,
43+
..Default::default()
44+
};
45+
46+
scope.set_user(Some(user));
47+
}
48+
49+
{
50+
let headers = req
51+
.headers()
52+
.iter()
53+
.filter(|(k, _v)| !FILTERED_HEADERS.iter().any(|name| k == name))
54+
.map(|(k, v)| (k.to_string(), v.to_str().unwrap_or_default().to_string()))
55+
.collect();
56+
57+
let sentry_req = sentry::protocol::Request {
58+
url,
59+
method,
60+
headers,
61+
..Default::default()
62+
};
63+
64+
scope.add_event_processor(Box::new(move |mut event| {
65+
if event.request.is_none() {
66+
event.request = Some(sentry_req.clone());
67+
}
68+
Some(event)
69+
}));
70+
}
71+
72+
if let Some(request_id) = req
73+
.headers()
74+
.get("x-request-id")
75+
.and_then(|value| value.to_str().ok())
76+
{
77+
scope.set_tag("request.id", request_id);
78+
}
79+
80+
{
81+
let status = res
82+
.as_ref()
83+
.map(|resp| resp.status())
84+
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
85+
86+
scope.set_tag("response.status", status.as_str());
87+
}
88+
89+
let response_time = req.extensions().find::<ResponseTime>();
90+
if let Some(response_time) = response_time {
91+
scope.set_extra("Response time [ms]", response_time.as_millis().into());
92+
}
93+
94+
if let Some(metadata) = req.extensions().find::<CustomMetadata>() {
95+
for (key, value) in &metadata.entries {
96+
scope.set_extra(key, value.to_string().into());
97+
}
98+
}
99+
};
100+
101+
sentry::with_scope(config, || sentry::capture_message(&message, level));
102+
103+
res
104+
}
105+
}

0 commit comments

Comments
 (0)