bitwarden_core/client/
tracing_middleware.rs1use std::{
20 str::from_utf8,
21 sync::atomic::{AtomicU32, Ordering},
22};
23
24use http::Extensions;
25use reqwest::{Request, Response};
26use reqwest_middleware::{Middleware, Next, Result};
27use tracing::{Instrument, trace};
28
29const MAX_REQUEST_BODY_LOG_SIZE: usize = 100 * 1024; const REDACTED_HEADERS: &[&str] = &[
32 "authorization",
33 "cookie",
34 "set-cookie",
35 "proxy-authorization",
36];
37
38pub struct ReqwestTracingMiddleware;
40
41#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
42#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
43impl Middleware for ReqwestTracingMiddleware {
44 async fn handle(
45 &self,
46 req: Request,
47 extensions: &mut Extensions,
48 next: Next<'_>,
49 ) -> Result<Response> {
50 if !tracing::enabled!(tracing::Level::TRACE) {
51 return next.run(req, extensions).await;
52 }
53
54 static REQUEST_ID: AtomicU32 = AtomicU32::new(1);
55 let request_id = REQUEST_ID.fetch_add(1, Ordering::Relaxed);
56 let span = tracing::span!(tracing::Level::TRACE, "http_request", request_id);
57
58 async move {
59 trace!(
60 method = %req.method(),
61 url = %req.url(),
62 "HTTP request"
63 );
64
65 for (name, value) in req.headers() {
66 if is_sensitive(name) {
67 trace!(name = %name, value = "<redacted>", "HTTP request header");
68 } else {
69 trace!(name = %name, value = ?value, "HTTP request header");
70 }
71 }
72
73 if let Some(body) = req.body().and_then(|b| b.as_bytes()) {
74 if body.len() > MAX_REQUEST_BODY_LOG_SIZE {
75 trace!(size = body.len(), "HTTP request body (truncated)");
76 } else if let Ok(text) = from_utf8(body) {
77 trace!(body = %text, "HTTP request body");
78 } else {
79 trace!(size = body.len(), "HTTP request body (binary)");
80 }
81 }
82
83 let response = next.run(req, extensions).await?;
84
85 trace!(
86 status = %response.status(),
87 "HTTP response"
88 );
89
90 for (name, value) in response.headers() {
91 if is_sensitive(name) {
92 trace!(name = %name, value = "<redacted>", "HTTP response header");
93 } else {
94 trace!(name = %name, value = ?value, "HTTP response header");
95 }
96 }
97
98 Ok(response)
99 }
100 .instrument(span)
101 .await
102 }
103}
104
105fn is_sensitive(name: &http::HeaderName) -> bool {
106 REDACTED_HEADERS.iter().any(|h| name.as_str() == *h)
107}