pacenotes_server/
access_log.rs1pub async fn log_response(
7 request: axum::extract::Request,
8 next: axum::middleware::Next,
9) -> axum::response::Response {
10 let started = std::time::Instant::now();
11 let method = request.method().clone();
12 let target = request
13 .uri()
14 .path_and_query()
15 .map(|path_and_query| path_and_query.as_str().to_string())
16 .unwrap_or_default();
17 let key = key_label(request.headers());
18 let agent = agent_label(request.headers());
19
20 let response = next.run(request).await;
21 tracing::info!(
22 target: "access",
23 method = %method,
24 target_uri = %target,
25 status = response.status().as_u16(),
26 bytes = %bytes_label(&response),
27 ms = started.elapsed().as_millis() as u64,
28 key = %key,
29 agent = %agent,
30 "request"
31 );
32 response
33}
34
35fn key_label(headers: &axum::http::HeaderMap) -> String {
38 match crate::auth::bearer_from_headers(headers) {
39 None => "-".to_string(),
40 Some(raw) if !crate::auth::well_formed(&raw) => "malformed".to_string(),
41 Some(raw) => crate::auth::hash_token(&raw)[..12].to_string(),
42 }
43}
44
45fn agent_label(headers: &axum::http::HeaderMap) -> String {
47 let agent = headers
48 .get(axum::http::header::USER_AGENT)
49 .and_then(|value| value.to_str().ok())
50 .unwrap_or("-");
51 agent.chars().take(200).collect()
52}
53
54fn bytes_label(response: &axum::response::Response) -> String {
57 response
58 .headers()
59 .get(axum::http::header::CONTENT_LENGTH)
60 .and_then(|value| value.to_str().ok())
61 .map(str::to_string)
62 .or_else(|| {
63 <axum::body::Body as axum::body::HttpBody>::size_hint(response.body())
64 .exact()
65 .map(|size| size.to_string())
66 })
67 .unwrap_or_else(|| "-".to_string())
68}
69
70#[cfg(test)]
71mod tests {
72 fn authorization(value: &str) -> axum::http::HeaderMap {
73 let mut headers = axum::http::HeaderMap::new();
74 headers.insert(
75 axum::http::header::AUTHORIZATION,
76 axum::http::HeaderValue::from_str(value).unwrap(),
77 );
78 headers
79 }
80
81 #[test]
82 fn the_access_log_never_sees_a_raw_key() {
83 let key = crate::auth::mint_token();
84 let label = super::key_label(&authorization(&format!("Bearer {key}")));
85 assert_eq!(label, crate::auth::hash_token(&key)[..12]);
86 assert_eq!(super::key_label(&axum::http::HeaderMap::new()), "-");
87 assert_eq!(
88 super::key_label(&authorization("Bearer garbage")),
89 "malformed"
90 );
91 }
92}