use bytes::Bytes;
use http::{HeaderMap, Request};
use crate::middleware::request_id::REQUEST_ID_HEADER;
pub fn inject_request_headers(headers: &mut HeaderMap) {
if !headers.contains_key(REQUEST_ID_HEADER) {
let id = uuid::Uuid::new_v4().to_string();
match http::HeaderValue::from_str(&id) {
Ok(value) => {
headers.insert(REQUEST_ID_HEADER, value);
}
Err(e) => {
#[cfg(feature = "tracing")]
tracing::warn!(msg = "failed to build request id header", error = %e);
let _ = e;
}
}
}
#[cfg(feature = "tracing")]
{
use tracing_opentelemetry::OpenTelemetrySpanExt as _;
let context = tracing::Span::current().context();
opentelemetry::global::get_text_map_propagator(|propagator| {
propagator.inject_context(&context, &mut HeaderInjector(headers));
});
}
}
#[cfg(feature = "tracing")]
pub fn client_span(req: &Request<Bytes>) -> tracing::Span {
let request_id = req
.headers()
.get(REQUEST_ID_HEADER)
.and_then(|v| v.to_str().ok())
.map(str::to_string);
tracing::info_span!(
"http.client",
"otel.kind" = "client",
"http.request.method" = %req.method(),
"url.full" = %req.uri(),
"server.address" = req.uri().host(),
"request_id" = request_id,
"http.response.status_code" = tracing::field::Empty,
"otel.status_code" = tracing::field::Empty,
)
}
#[cfg(feature = "tracing")]
struct HeaderInjector<'a>(&'a mut HeaderMap);
#[cfg(feature = "tracing")]
impl opentelemetry::propagation::Injector for HeaderInjector<'_> {
fn set(&mut self, key: &str, value: String) {
let Ok(name) = http::header::HeaderName::try_from(key) else {
return;
};
let Ok(value) = http::HeaderValue::from_str(&value) else {
return;
};
self.0.insert(name, value);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_injects_request_id_when_absent() {
let mut headers = HeaderMap::new();
inject_request_headers(&mut headers);
let value = headers
.get(REQUEST_ID_HEADER)
.and_then(|v| v.to_str().ok())
.expect("x-request-id injected");
assert!(uuid::Uuid::parse_str(value).is_ok());
}
#[test]
fn test_preserves_existing_request_id() {
let mut headers = HeaderMap::new();
headers.insert(
REQUEST_ID_HEADER,
http::HeaderValue::from_static("caller-set"),
);
inject_request_headers(&mut headers);
assert_eq!(headers.get(REQUEST_ID_HEADER).unwrap(), "caller-set");
}
}