Skip to content

Commit 1778505

Browse files
committed
Auto merge of #2234 - pietroalbini:log-publish-crate, r=jtgeibel
Include crate name and version in the publish API's log entries This PR includes the crate name and version in the log entries of the publish API, to fix #2228 and to aid debugging issues in production. This is done in three steps. First, the log entry generation code was refactored to easily allow adding new fields to it. Now, instead of hardcoding the list of fields in a `format!` macro, there is a builder with methods to add individual fields. ~~Then, a way to attach custom log fields to a response was added. This is implemented by setting HTTP headers in the response starting with `X-CratesIo-Log-Metadata-`, and by changing the middleware responsible for request logging to strip those headers and add their content to the log line instead.~~ Then, a way to attach custom log fields to a response was added. This is implemented as a "request extension", with a convenience method in the `ResponseExt` trait to add the custom log fields to all responses generated by that request. Finally, this new feature was used to attach the crate name and version to the publish API's log entries. r? @sgrif
2 parents 476d387 + 062645f commit 1778505

File tree

4 files changed

+139
-38
lines changed

4 files changed

+139
-38
lines changed

src/controllers.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ mod prelude {
3838
fn query(&self) -> IndexMap<String, String>;
3939
fn wants_json(&self) -> bool;
4040
fn query_with_params(&self, params: IndexMap<String, String>) -> String;
41+
42+
fn log_metadata<V: std::fmt::Display>(&mut self, key: &'static str, value: V);
4143
}
4244

4345
impl<'a> RequestUtils for dyn Request + 'a {
@@ -76,6 +78,10 @@ mod prelude {
7678
.finish();
7779
format!("?{}", query_string)
7880
}
81+
82+
fn log_metadata<V: std::fmt::Display>(&mut self, key: &'static str, value: V) {
83+
crate::middleware::log_request::add_custom_metadata(self, key, value);
84+
}
7985
}
8086
}
8187

src/controllers/krate/publish.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ pub fn publish(req: &mut dyn Request) -> AppResult<Response> {
4141

4242
let new_crate = parse_new_headers(req)?;
4343

44+
req.log_metadata("crate_name", new_crate.name.to_string());
45+
req.log_metadata("crate_version", new_crate.vers.to_string());
46+
4447
let conn = app.primary_database.get()?;
4548
let ids = req.authenticate(&conn)?;
4649
let user = ids.find_user(&conn)?;

src/middleware.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ mod ember_index_rewrite;
2525
mod ensure_well_formed_500;
2626
mod head;
2727
mod log_connection_pool_status;
28-
mod log_request;
28+
pub mod log_request;
2929
mod require_user_agent;
3030
mod static_or_continue;
3131

src/middleware/log_request.rs

Lines changed: 129 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
use super::prelude::*;
55
use crate::util::request_header;
66
use conduit::Request;
7-
use std::fmt;
7+
use std::fmt::{self, Display, Formatter};
88
use std::time::Instant;
99

10+
const SLOW_REQUEST_THRESHOLD_MS: u64 = 1000;
11+
1012
#[allow(missing_debug_implementations)] // We can't
1113
#[derive(Default)]
1214
pub struct LogRequests {
@@ -23,61 +25,151 @@ impl Handler for LogRequests {
2325
fn call(&self, req: &mut dyn Request) -> Result<Response> {
2426
let request_start = Instant::now();
2527
let res = self.handler.as_ref().unwrap().call(req);
26-
let (level, response_code) = match res {
27-
Ok(ref r) => ("info", r.status.0),
28-
Err(_) => ("error", 500),
29-
};
3028
let response_time = request_start.elapsed();
3129
let response_time =
3230
response_time.as_secs() * 1000 + u64::from(response_time.subsec_nanos()) / 1_000_000;
3331

34-
let metadata_length = req
35-
.extensions()
36-
.find::<u64>()
37-
.map_or(String::new(), |l| format!(" metadata_length={}", l));
32+
println!(
33+
"{}",
34+
RequestLine {
35+
req,
36+
res: &res,
37+
response_time,
38+
}
39+
);
3840

39-
let slow_request = if response_time > 1000 {
40-
" SLOW REQUEST"
41-
} else {
42-
""
41+
res
42+
}
43+
}
44+
45+
struct CustomMetadata {
46+
entries: Vec<(&'static str, String)>,
47+
}
48+
49+
pub fn add_custom_metadata<V: Display>(req: &mut dyn Request, key: &'static str, value: V) {
50+
if let Some(metadata) = req.mut_extensions().find_mut::<CustomMetadata>() {
51+
metadata.entries.push((key, value.to_string()));
52+
} else {
53+
let mut metadata = CustomMetadata {
54+
entries: Vec::new(),
4355
};
56+
metadata.entries.push((key, value.to_string()));
57+
req.mut_extensions().insert(metadata);
58+
}
59+
}
60+
61+
struct RequestLine<'r> {
62+
req: &'r dyn Request,
63+
res: &'r Result<Response>,
64+
response_time: u64,
65+
}
4466

45-
let error = if let Err(ref e) = res {
46-
format!(" error=\"{}\"", e)
47-
} else {
48-
String::new()
67+
impl Display for RequestLine<'_> {
68+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
69+
let mut line = LogLine::new(f);
70+
71+
let (at, status) = match self.res {
72+
Ok(resp) => ("info", resp.status.0),
73+
Err(_) => ("error", 500),
4974
};
5075

51-
println!(
52-
"at={level} method={method} path=\"{path}\" \
53-
request_id={request_id} fwd=\"{ip}\" service={time_ms}ms \
54-
status={status} user_agent=\"{user_agent}\"\
55-
{metadata_length}{error}{slow_request}",
56-
level = level,
57-
method = req.method(),
58-
path = FullPath(req),
59-
ip = request_header(req, "X-Real-Ip"),
60-
time_ms = response_time,
61-
user_agent = request_header(req, "User-Agent"),
62-
request_id = request_header(req, "X-Request-Id"),
63-
status = response_code,
64-
metadata_length = metadata_length,
65-
error = error,
66-
slow_request = slow_request,
67-
);
76+
line.add_field("at", at)?;
77+
line.add_field("method", self.req.method())?;
78+
line.add_quoted_field("path", FullPath(self.req))?;
79+
line.add_field("request_id", request_header(self.req, "X-Request-Id"))?;
80+
line.add_quoted_field("fwd", request_header(self.req, "X-Real-Ip"))?;
81+
line.add_field("service", TimeMs(self.response_time))?;
82+
line.add_field("status", status)?;
83+
line.add_quoted_field("user_agent", request_header(self.req, "User-Agent"))?;
6884

69-
res
85+
if let Some(metadata) = self.req.extensions().find::<CustomMetadata>() {
86+
for (key, value) in &metadata.entries {
87+
line.add_quoted_field(key, value)?;
88+
}
89+
}
90+
91+
if let Some(len) = self.req.extensions().find::<u64>() {
92+
line.add_field("metadata_length", len)?;
93+
}
94+
95+
if let Err(err) = self.res {
96+
line.add_quoted_field("error", err)?;
97+
}
98+
99+
if self.response_time > SLOW_REQUEST_THRESHOLD_MS {
100+
line.add_marker("SLOW REQUEST")?;
101+
}
102+
103+
Ok(())
70104
}
71105
}
72106

73107
struct FullPath<'a>(&'a dyn Request);
74108

75-
impl<'a> fmt::Display for FullPath<'a> {
76-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109+
impl<'a> Display for FullPath<'a> {
110+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
77111
write!(f, "{}", self.0.path())?;
78112
if let Some(q_string) = self.0.query_string() {
79113
write!(f, "?{}", q_string)?;
80114
}
81115
Ok(())
82116
}
83117
}
118+
119+
struct TimeMs(u64);
120+
121+
impl Display for TimeMs {
122+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
123+
self.0.fmt(f)?;
124+
f.write_str("ms")?;
125+
Ok(())
126+
}
127+
}
128+
129+
struct LogLine<'f, 'g> {
130+
f: &'f mut Formatter<'g>,
131+
first: bool,
132+
}
133+
134+
impl<'f, 'g> LogLine<'f, 'g> {
135+
fn new(f: &'f mut Formatter<'g>) -> Self {
136+
Self { f, first: true }
137+
}
138+
139+
fn add_field<K: Display, V: Display>(&mut self, key: K, value: V) -> fmt::Result {
140+
self.start_item()?;
141+
142+
key.fmt(self.f)?;
143+
self.f.write_str("=")?;
144+
value.fmt(self.f)?;
145+
146+
Ok(())
147+
}
148+
149+
fn add_quoted_field<K: Display, V: Display>(&mut self, key: K, value: V) -> fmt::Result {
150+
self.start_item()?;
151+
152+
key.fmt(self.f)?;
153+
self.f.write_str("=\"")?;
154+
value.fmt(self.f)?;
155+
self.f.write_str("\"")?;
156+
157+
Ok(())
158+
}
159+
160+
fn add_marker<M: Display>(&mut self, marker: M) -> fmt::Result {
161+
self.start_item()?;
162+
163+
marker.fmt(self.f)?;
164+
165+
Ok(())
166+
}
167+
168+
fn start_item(&mut self) -> fmt::Result {
169+
if !self.first {
170+
self.f.write_str(" ")?;
171+
}
172+
self.first = false;
173+
Ok(())
174+
}
175+
}

0 commit comments

Comments
 (0)