Skip to content

Commit 8e4ddf9

Browse files
committed
Auto merge of #4116 - Turbo87:timing-middleware, r=JohnTitor
middleware::log_request: Split request timing code into dedicated middleware This is generic and not strictly related to the `LogRequest` middleware. The `UpdateMetrics` middleware should probably also use this in the future.
2 parents 6c5dc90 + 775a75a commit 8e4ddf9

File tree

3 files changed

+86
-58
lines changed

3 files changed

+86
-58
lines changed

src/middleware.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use self::ember_html::EmberHtml;
1010
use self::head::Head;
1111
use self::known_error_to_json::KnownErrorToJson;
1212
use self::log_connection_pool_status::LogConnectionPoolStatus;
13+
use self::response_timing::ResponseTiming;
1314
use self::static_or_continue::StaticOrContinue;
1415
use self::update_metrics::UpdateMetrics;
1516

@@ -24,6 +25,7 @@ mod log_connection_pool_status;
2425
pub mod log_request;
2526
mod normalize_path;
2627
mod require_user_agent;
28+
mod response_timing;
2729
mod static_or_continue;
2830
mod update_metrics;
2931

@@ -48,6 +50,8 @@ pub fn build_middleware(app: Arc<App>, endpoints: RouteBuilder) -> MiddlewareBui
4850
m.add(log_request::LogRequests::default());
4951
}
5052

53+
m.add(ResponseTiming::default());
54+
5155
if env == Env::Development {
5256
// Optionally print debug information for each request
5357
// To enable, set the environment variable: `RUST_LOG=cargo_registry::middleware=debug`

src/middleware/log_request.rs

Lines changed: 16 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use crate::util::request_header;
77
use conduit::{header, RequestExt, StatusCode};
88
use conduit_cookie::RequestSession;
99

10+
use crate::middleware::response_timing::ResponseTime;
1011
use std::fmt::{self, Display, Formatter};
11-
use std::time::{SystemTime, UNIX_EPOCH};
1212

1313
const SLOW_REQUEST_THRESHOLD_MS: u64 = 1000;
1414

@@ -34,53 +34,14 @@ impl Middleware for LogRequests {
3434
}
3535

3636
fn after(&self, req: &mut dyn RequestExt, res: AfterResult) -> AfterResult {
37-
let response_time =
38-
if let Ok(start_ms) = request_header(req, "x-request-start").parse::<u128>() {
39-
let current_ms = SystemTime::now()
40-
.duration_since(UNIX_EPOCH)
41-
.expect("Time went way backwards")
42-
.as_millis();
43-
44-
if current_ms > start_ms {
45-
// The result cannot be negative
46-
current_ms - start_ms
47-
} else {
48-
// Because our nginx proxy and app run on the same dyno in production, we
49-
// shouldn't have to worry about clock drift. But if something goes wrong,
50-
// calculate the response time based on when the request reached this app.
51-
fallback_response_time(req)
52-
}
53-
} else {
54-
// X-Request-Start header couldn't be parsed.
55-
// We are probably running locally and not behind nginx.
56-
fallback_response_time(req)
57-
};
58-
59-
// This will only trucate for requests lasting > 500 million years
60-
let response_time = response_time as u64;
61-
62-
println!(
63-
"{}",
64-
RequestLine {
65-
req,
66-
res: &res,
67-
response_time,
68-
}
69-
);
37+
println!("{}", RequestLine { req, res: &res });
7038

71-
report_to_sentry(req, &res, response_time);
39+
report_to_sentry(req, &res);
7240

7341
res
7442
}
7543
}
7644

77-
/// Calculate the response time based on when the request reached the in-app web server.
78-
///
79-
/// This serves as a fallback in case the `X-Request-Start` header is missing or invalid.
80-
fn fallback_response_time(req: &mut dyn RequestExt) -> u128 {
81-
req.elapsed().as_millis()
82-
}
83-
8445
struct CustomMetadata {
8546
entries: Vec<(&'static str, String)>,
8647
}
@@ -99,7 +60,7 @@ pub fn add_custom_metadata<V: Display>(req: &mut dyn RequestExt, key: &'static s
9960
sentry::configure_scope(|scope| scope.set_extra(key, value.to_string().into()));
10061
}
10162

102-
fn report_to_sentry(req: &dyn RequestExt, res: &AfterResult, response_time: u64) {
63+
fn report_to_sentry(req: &dyn RequestExt, res: &AfterResult) {
10364
sentry::configure_scope(|scope| {
10465
{
10566
let id = req.session().get("user_id").map(|str| str.to_string());
@@ -121,7 +82,9 @@ fn report_to_sentry(req: &dyn RequestExt, res: &AfterResult, response_time: u64)
12182
scope.set_tag("response.status", status.as_str());
12283
}
12384

124-
scope.set_extra("Response time [ms]", response_time.into());
85+
if let Some(response_time) = req.extensions().find::<ResponseTime>() {
86+
scope.set_extra("Response time [ms]", response_time.as_millis().into());
87+
}
12588
});
12689
}
12790

@@ -139,7 +102,6 @@ pub(crate) fn get_log_message(req: &dyn RequestExt, key: &'static str) -> String
139102
struct RequestLine<'r> {
140103
req: &'r dyn RequestExt,
141104
res: &'r AfterResult,
142-
response_time: u64,
143105
}
144106

145107
impl Display for RequestLine<'_> {
@@ -172,7 +134,11 @@ impl Display for RequestLine<'_> {
172134
}
173135

174136
line.add_quoted_field("fwd", request_header(self.req, "x-real-ip"))?;
175-
line.add_field("service", TimeMs(self.response_time))?;
137+
138+
let response_time = self.req.extensions().find::<ResponseTime>();
139+
if let Some(response_time) = response_time {
140+
line.add_field("service", response_time)?;
141+
}
176142
line.add_field("status", status.as_str())?;
177143
line.add_quoted_field("user_agent", request_header(self.req, header::USER_AGENT))?;
178144

@@ -186,8 +152,10 @@ impl Display for RequestLine<'_> {
186152
line.add_quoted_field("error", err)?;
187153
}
188154

189-
if self.response_time > SLOW_REQUEST_THRESHOLD_MS {
190-
line.add_marker("SLOW REQUEST")?;
155+
if let Some(response_time) = response_time {
156+
if response_time.as_millis() > SLOW_REQUEST_THRESHOLD_MS {
157+
line.add_marker("SLOW REQUEST")?;
158+
}
191159
}
192160

193161
Ok(())
@@ -211,16 +179,6 @@ impl<'a> Display for FullPath<'a> {
211179
}
212180
}
213181

214-
struct TimeMs(u64);
215-
216-
impl Display for TimeMs {
217-
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
218-
self.0.fmt(f)?;
219-
f.write_str("ms")?;
220-
Ok(())
221-
}
222-
}
223-
224182
struct LogLine<'f, 'g> {
225183
f: &'f mut Formatter<'g>,
226184
first: bool,

src/middleware/response_timing.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use super::prelude::*;
2+
use crate::util::request_header;
3+
4+
use conduit::RequestExt;
5+
6+
use std::fmt::{self, Display, Formatter};
7+
use std::time::{SystemTime, UNIX_EPOCH};
8+
9+
#[derive(Default)]
10+
pub struct ResponseTiming();
11+
12+
pub struct ResponseTime(u64);
13+
14+
impl ResponseTime {
15+
pub fn as_millis(&self) -> u64 {
16+
self.0
17+
}
18+
}
19+
20+
impl Display for ResponseTime {
21+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
22+
self.0.fmt(f)?;
23+
f.write_str("ms")?;
24+
Ok(())
25+
}
26+
}
27+
28+
impl Middleware for ResponseTiming {
29+
fn after(&self, req: &mut dyn RequestExt, res: AfterResult) -> AfterResult {
30+
let response_time =
31+
if let Ok(start_ms) = request_header(req, "x-request-start").parse::<u128>() {
32+
let current_ms = SystemTime::now()
33+
.duration_since(UNIX_EPOCH)
34+
.expect("Time went way backwards")
35+
.as_millis();
36+
37+
if current_ms > start_ms {
38+
// The result cannot be negative
39+
current_ms - start_ms
40+
} else {
41+
// Because our nginx proxy and app run on the same dyno in production, we
42+
// shouldn't have to worry about clock drift. But if something goes wrong,
43+
// calculate the response time based on when the request reached this app.
44+
fallback_response_time(req)
45+
}
46+
} else {
47+
// X-Request-Start header couldn't be parsed.
48+
// We are probably running locally and not behind nginx.
49+
fallback_response_time(req)
50+
};
51+
52+
// This will only trucate for requests lasting > 500 million years
53+
let response_time = response_time as u64;
54+
55+
req.mut_extensions().insert(ResponseTime(response_time));
56+
57+
res
58+
}
59+
}
60+
61+
/// Calculate the response time based on when the request reached the in-app web server.
62+
///
63+
/// This serves as a fallback in case the `X-Request-Start` header is missing or invalid.
64+
fn fallback_response_time(req: &mut dyn RequestExt) -> u128 {
65+
req.elapsed().as_millis()
66+
}

0 commit comments

Comments
 (0)