Skip to content

Commit d1ecef4

Browse files
committed
use a thread local to store custom metadata
Before this commit, custom metadata was stored as part of a request extension, which required the mutable request object to be passed to every place which needed to log something. This commit changes the implementation to use a thread local that is cleared before a request is processed. This removes the dependency on the mutable request object. Note that when migrating the crates.io codebase to async the thread local will need to be replaced with an async equivalent.
1 parent c8b9138 commit d1ecef4

File tree

11 files changed

+49
-50
lines changed

11 files changed

+49
-50
lines changed

src/controllers.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ mod frontend_prelude {
88
pub use crate::util::errors::{bad_request, server_error};
99
}
1010

11-
pub(crate) use prelude::RequestUtils;
12-
1311
mod prelude {
1412
pub use super::helpers::ok_true;
1513
pub use diesel::prelude::*;
@@ -36,8 +34,6 @@ mod prelude {
3634
fn query(&self) -> IndexMap<String, String>;
3735
fn wants_json(&self) -> bool;
3836
fn query_with_params(&self, params: IndexMap<String, String>) -> String;
39-
40-
fn log_metadata<V: std::fmt::Display>(&mut self, key: &'static str, value: V);
4137
}
4238

4339
impl<'a> RequestUtils for dyn RequestExt + 'a {
@@ -74,10 +70,6 @@ mod prelude {
7470
.finish();
7571
format!("?{query_string}")
7672
}
77-
78-
fn log_metadata<V: std::fmt::Display>(&mut self, key: &'static str, value: V) {
79-
crate::middleware::log_request::add_custom_metadata(self, key, value);
80-
}
8173
}
8274
}
8375

src/controllers/helpers/pagination.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ impl PaginationOptionsBuilder {
9292
}
9393

9494
if numeric_page > MAX_PAGE_BEFORE_SUSPECTED_BOT {
95-
req.log_metadata("bot", "suspected");
95+
add_custom_metadata("bot", "suspected");
9696
}
9797

9898
// Block large offsets for known violators of the crawler policy
@@ -105,7 +105,7 @@ impl PaginationOptionsBuilder {
105105
.iter()
106106
.any(|blocked| user_agent.contains(blocked))
107107
{
108-
add_custom_metadata(req, "cause", "large page offset");
108+
add_custom_metadata("cause", "large page offset");
109109
return Err(bad_request("requested page offset is too large"));
110110
}
111111
}

src/controllers/krate/publish.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::models::{
1616
};
1717
use crate::worker;
1818

19+
use crate::middleware::log_request::add_custom_metadata;
1920
use crate::schema::*;
2021
use crate::util::errors::{cargo_err, AppResult};
2122
use crate::util::{read_fill, read_le_u32, CargoVcsInfo, LimitErrorReader, Maximums};
@@ -59,8 +60,8 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
5960

6061
let new_crate = parse_new_headers(req)?;
6162

62-
req.log_metadata("crate_name", new_crate.name.to_string());
63-
req.log_metadata("crate_version", new_crate.vers.to_string());
63+
add_custom_metadata("crate_name", new_crate.name.to_string());
64+
add_custom_metadata("crate_version", new_crate.vers.to_string());
6465

6566
let conn = app.primary_database.get()?;
6667
let ids = req.authenticate()?;
@@ -265,7 +266,7 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
265266
fn parse_new_headers(req: &mut dyn RequestExt) -> AppResult<EncodableCrateUpload> {
266267
// Read the json upload request
267268
let metadata_length = u64::from(read_le_u32(req.body())?);
268-
req.log_metadata("metadata_length", metadata_length);
269+
add_custom_metadata("metadata_length", metadata_length);
269270

270271
let max = req.app().config.max_upload_size;
271272
if metadata_length > max {

src/controllers/util.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,9 @@ impl<'a> UserAuthenticationExt for dyn RequestExt + 'a {
129129
}
130130
}
131131

132-
log_request::add_custom_metadata(self, "uid", authenticated_user.user_id());
132+
log_request::add_custom_metadata("uid", authenticated_user.user_id());
133133
if let Some(id) = authenticated_user.api_token_id() {
134-
log_request::add_custom_metadata(self, "tokenid", id);
134+
log_request::add_custom_metadata("tokenid", id);
135135
}
136136

137137
Ok(authenticated_user)

src/controllers/version/downloads.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use super::{extract_crate_name_and_semver, version_and_crate};
66
use crate::controllers::prelude::*;
77
use crate::db::PoolError;
8+
use crate::middleware::log_request::add_custom_metadata;
89
use crate::models::{Crate, VersionDownload};
910
use crate::schema::*;
1011
use crate::views::EncodableVersionDownload;
@@ -108,7 +109,7 @@ pub fn download(req: &mut dyn RequestExt) -> EndpointResult {
108109
.crate_location(&crate_name, &*version);
109110

110111
if let Some((key, value)) = log_metadata {
111-
req.log_metadata(key, value);
112+
add_custom_metadata(key, value);
112113
}
113114

114115
if req.wants_json() {

src/middleware/balance_capacity.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ impl BalanceCapacity {
5656
fn handle_high_load(&self, request: &mut dyn RequestExt, note: &str) -> AfterResult {
5757
if self.report_only {
5858
// In report-only mode we serve all requests but add log metadata
59-
add_custom_metadata(request, "would_reject", note);
59+
add_custom_metadata("would_reject", note);
6060
self.handle(request)
6161
} else {
6262
// Reject the request
63-
add_custom_metadata(request, "cause", note);
63+
add_custom_metadata("cause", note);
6464
let body = "Service temporarily unavailable";
6565
Response::builder()
6666
.status(StatusCode::SERVICE_UNAVAILABLE)
@@ -84,7 +84,7 @@ impl Handler for BalanceCapacity {
8484

8585
// Begin logging total request count so early stages of load increase can be located
8686
if in_flight_total >= self.log_total_at_count {
87-
add_custom_metadata(request, "in_flight_total", in_flight_total);
87+
add_custom_metadata("in_flight_total", in_flight_total);
8888
}
8989

9090
// Download requests are always accepted and do not affect the capacity tracking
@@ -98,7 +98,7 @@ impl Handler for BalanceCapacity {
9898

9999
// Begin logging non-download request count so early stages of non-download load increase can be located
100100
if load >= self.log_at_percentage {
101-
add_custom_metadata(request, "in_flight_non_dl_requests", count);
101+
add_custom_metadata("in_flight_non_dl_requests", count);
102102
}
103103

104104
// Reject read-only requests as load nears capacity. Bots are likely to send only safe

src/middleware/block_traffic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ impl Handler for BlockTraffic {
4848
.any(|value| self.blocked_values.iter().any(|v| v == value));
4949
if has_blocked_value {
5050
let cause = format!("blocked due to contents of header {}", self.header_name);
51-
add_custom_metadata(req, "cause", cause);
51+
add_custom_metadata("cause", cause);
5252
let body = format!(
5353
"We are unable to process your request at this time. \
5454
This usually means that you are in violation of our crawler \

src/middleware/log_request.rs

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,48 +8,51 @@ use conduit::{header, RequestExt, StatusCode};
88

99
use crate::middleware::normalize_path::OriginalPath;
1010
use crate::middleware::response_timing::ResponseTime;
11+
use std::cell::RefCell;
1112
use std::fmt::{self, Display, Formatter};
1213

1314
const SLOW_REQUEST_THRESHOLD_MS: u64 = 1000;
1415

16+
// A thread local is used instead of a request extension to avoid the need to pass the request
17+
// object everywhere in the codebase. When migrating to async this will need to be moved to an
18+
// async-equivalent, as thread locals misbehave in async contexes.
19+
thread_local! {
20+
static CUSTOM_METADATA: RefCell<Vec<(&'static str, String)>> = RefCell::new(Vec::new());
21+
}
22+
1523
#[derive(Default)]
1624
pub(super) struct LogRequests();
1725

1826
impl Middleware for LogRequests {
27+
fn before(&self, _: &mut dyn RequestExt) -> BeforeResult {
28+
// Remove any metadata set by the previous task before processing any new request.
29+
CUSTOM_METADATA.with(|metadata| metadata.borrow_mut().clear());
30+
31+
Ok(())
32+
}
33+
1934
fn after(&self, req: &mut dyn RequestExt, res: AfterResult) -> AfterResult {
2035
println!("{}", RequestLine { req, res: &res });
2136

2237
res
2338
}
2439
}
2540

26-
struct CustomMetadata {
27-
entries: Vec<(&'static str, String)>,
28-
}
29-
30-
pub fn add_custom_metadata<V: Display>(req: &mut dyn RequestExt, key: &'static str, value: V) {
31-
if let Some(metadata) = req.mut_extensions().get_mut::<CustomMetadata>() {
32-
metadata.entries.push((key, value.to_string()));
33-
} else {
34-
let mut metadata = CustomMetadata {
35-
entries: Vec::new(),
36-
};
37-
metadata.entries.push((key, value.to_string()));
38-
req.mut_extensions().insert(metadata);
39-
}
40-
41+
pub fn add_custom_metadata<V: Display>(key: &'static str, value: V) {
42+
CUSTOM_METADATA.with(|metadata| metadata.borrow_mut().push((key, value.to_string())));
4143
sentry::configure_scope(|scope| scope.set_extra(key, value.to_string().into()));
4244
}
4345

4446
#[cfg(test)]
45-
pub(crate) fn get_log_message(req: &dyn RequestExt, key: &'static str) -> String {
46-
// Unwrap shouldn't panic as no other code has access to the private struct to remove it
47-
for (k, v) in &req.extensions().get::<CustomMetadata>().unwrap().entries {
48-
if key == *k {
49-
return v.clone();
47+
pub(crate) fn get_log_message(key: &'static str) -> String {
48+
CUSTOM_METADATA.with(|metadata| {
49+
for (k, v) in &*metadata.borrow() {
50+
if key == *k {
51+
return v.clone();
52+
}
5053
}
51-
}
52-
panic!("expected log message for {} not found", key);
54+
panic!("expected log message for {} not found", key);
55+
})
5356
}
5457

5558
struct RequestLine<'r> {
@@ -95,11 +98,12 @@ impl Display for RequestLine<'_> {
9598
line.add_field("status", status.as_str())?;
9699
line.add_quoted_field("user_agent", request_header(self.req, header::USER_AGENT))?;
97100

98-
if let Some(metadata) = self.req.extensions().get::<CustomMetadata>() {
99-
for (key, value) in &metadata.entries {
101+
CUSTOM_METADATA.with(|metadata| {
102+
for (key, value) in &*metadata.borrow() {
100103
line.add_quoted_field(key, value)?;
101104
}
102-
}
105+
fmt::Result::Ok(())
106+
})?;
103107

104108
if let Err(err) = self.res {
105109
line.add_quoted_field("error", err)?;

src/middleware/normalize_path.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ impl Middleware for NormalizePath {
4343
.to_string_lossy()
4444
.to_string(); // non-Unicode is replaced with U+FFFD REPLACEMENT CHARACTER
4545

46-
add_custom_metadata(req, "normalized_path", path.clone());
46+
add_custom_metadata("normalized_path", path.clone());
4747

4848
*req.path_mut() = path;
4949
req.mut_extensions().insert(original_path);

src/middleware/require_user_agent.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ impl Handler for RequireUserAgent {
3232
let has_user_agent = !agent.is_empty() && agent != self.cdn_user_agent;
3333
let is_download = req.path().ends_with("download");
3434
if !has_user_agent && !is_download {
35-
add_custom_metadata(req, "cause", "no user agent");
35+
add_custom_metadata("cause", "no user agent");
3636
let body = format!(
3737
include_str!("no_user_agent_message.txt"),
3838
request_header(req, "x-request-id"),

0 commit comments

Comments
 (0)