Skip to content

Commit 5e7b95d

Browse files
committed
middleware/log: Report errors and slow requests to Sentry
1 parent afd463e commit 5e7b95d

File tree

1 file changed

+104
-1
lines changed

1 file changed

+104
-1
lines changed

src/middleware/log_request.rs

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
44
use super::prelude::*;
55
use crate::util::request_header;
6-
use conduit::{header, RequestExt, StatusCode};
6+
use conduit::{header, Host, RequestExt, Scheme, StatusCode};
7+
use sentry::protocol::IpAddress;
8+
use sentry::Level;
79
use std::fmt::{self, Display, Formatter};
10+
use std::net::IpAddr;
811
use std::time::Instant;
912

1013
const SLOW_REQUEST_THRESHOLD_MS: u64 = 1000;
@@ -40,6 +43,8 @@ impl Middleware for LogRequests {
4043
}
4144
);
4245

46+
report_to_sentry(req, &res, response_time);
47+
4348
res
4449
}
4550
}
@@ -60,6 +65,104 @@ pub fn add_custom_metadata<V: Display>(req: &mut dyn RequestExt, key: &'static s
6065
}
6166
}
6267

68+
fn report_to_sentry(req: &dyn RequestExt, res: &AfterResult, response_time: u64) {
69+
let (message, level) = match res {
70+
Err(e) => (e.to_string(), Level::Error),
71+
Ok(_) => {
72+
if response_time <= SLOW_REQUEST_THRESHOLD_MS {
73+
return;
74+
}
75+
76+
let message = format!("Slow Request: {} {}", req.method(), req.path());
77+
(message, Level::Info)
78+
}
79+
};
80+
81+
let config = |scope: &mut sentry::Scope| {
82+
let method = Some(req.method().as_str().to_owned());
83+
84+
let scheme = match req.scheme() {
85+
Scheme::Http => "http",
86+
Scheme::Https => "https",
87+
};
88+
89+
let host = match req.host() {
90+
Host::Name(name) => name.to_owned(),
91+
Host::Socket(addr) => addr.to_string(),
92+
};
93+
94+
let path = &req.extensions().find::<OriginalPath>().unwrap().0;
95+
96+
let url = format!("{}://{}{}", scheme, host, path).parse().ok();
97+
98+
{
99+
let ip_addr = match req.headers().get("x-real-ip") {
100+
Some(value) => value
101+
.to_str()
102+
.ok()
103+
.and_then(|str| str.parse::<IpAddr>().ok()),
104+
None => Some(req.remote_addr().ip()),
105+
};
106+
107+
let user = sentry::User {
108+
ip_address: ip_addr.map(IpAddress::Exact),
109+
..Default::default()
110+
};
111+
112+
scope.set_user(Some(user));
113+
}
114+
115+
{
116+
let headers = req
117+
.headers()
118+
.iter()
119+
.map(|(k, v)| (k.to_string(), v.to_str().unwrap_or_default().to_string()))
120+
.collect();
121+
122+
let sentry_req = sentry::protocol::Request {
123+
method,
124+
url,
125+
headers,
126+
..Default::default()
127+
};
128+
129+
scope.add_event_processor(Box::new(move |mut event| {
130+
if event.request.is_none() {
131+
event.request = Some(sentry_req.clone());
132+
}
133+
Some(event)
134+
}));
135+
}
136+
137+
if let Some(request_id) = req
138+
.headers()
139+
.get("x-request-id")
140+
.and_then(|value| value.to_str().ok())
141+
{
142+
scope.set_tag("request.id", request_id);
143+
}
144+
145+
{
146+
let status = res
147+
.as_ref()
148+
.map(|resp| resp.status())
149+
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
150+
151+
scope.set_tag("response.status", status.as_str());
152+
}
153+
154+
scope.set_extra("Response time [ms]", response_time.into());
155+
156+
if let Some(metadata) = req.extensions().find::<CustomMetadata>() {
157+
for (key, value) in &metadata.entries {
158+
scope.set_extra(key, value.to_string().into());
159+
}
160+
}
161+
};
162+
163+
sentry::with_scope(config, || sentry::capture_message(&message, level));
164+
}
165+
63166
#[cfg(test)]
64167
pub(crate) fn get_log_message(req: &dyn RequestExt, key: &'static str) -> String {
65168
// Unwrap shouldn't panic as no other code has access to the private struct to remove it

0 commit comments

Comments
 (0)