use axum::{body::Body, http::Request};
use opentelemetry::{propagation::Extractor, trace::TraceContextExt};
use tower_http::{request_id::RequestId, trace::MakeSpan};
use tracing::{field::Empty, Level, Span};
use tracing_opentelemetry::OpenTelemetrySpanExt;
const DEFAULT_MESSAGE_LEVEL: Level = Level::DEBUG;
#[derive(Debug, Clone)]
pub(crate) struct CustomMakeSpan {
level: Level,
include_headers: bool,
}
impl Default for CustomMakeSpan {
fn default() -> Self {
Self {
level: DEFAULT_MESSAGE_LEVEL,
include_headers: false,
}
}
}
impl CustomMakeSpan {
pub(crate) fn new() -> Self {
Self::default()
}
#[allow(dead_code)]
pub(crate) fn level(mut self, level: Level) -> Self {
self.level = level;
self
}
pub(crate) fn include_headers(mut self, include_headers: bool) -> Self {
self.include_headers = include_headers;
self
}
}
impl MakeSpan<Body> for CustomMakeSpan {
fn make_span(&mut self, request: &Request<Body>) -> Span {
let x_request_id = request
.extensions()
.get::<RequestId>()
.and_then(|id| id.header_value().to_str().ok());
macro_rules! make_span {
($level:expr) => {
if self.include_headers {
tracing::span!(
$level,
"request",
"otel.kind" = "server",
"trace_id" = Empty,
"span_id" = Empty,
"x_request_id" = x_request_id,
"http.request.method" = %request.method(),
"url.full" = %request.uri(),
"http.version" = ?request.version(),
"http.request.headers" = ?request.headers(),
)
} else {
tracing::span!(
$level,
"request",
"otel.kind" = "server",
"trace_id" = Empty,
"span_id" = Empty,
"x_request_id" = x_request_id,
"http.request.method" = %request.method(),
"url.full" = %request.uri(),
"http.version" = ?request.version(),
)
}
}
}
match self.level {
Level::ERROR => make_span!(Level::ERROR),
Level::WARN => make_span!(Level::WARN),
Level::INFO => make_span!(Level::INFO),
Level::DEBUG => make_span!(Level::DEBUG),
Level::TRACE => make_span!(Level::TRACE),
}
}
}
pub(crate) struct HeaderExtractor<'a>(pub(crate) &'a http::HeaderMap);
impl Extractor for HeaderExtractor<'_> {
fn get(&self, key: &str) -> Option<&str> {
self.0.get(key).and_then(|value| value.to_str().ok())
}
fn keys(&self) -> Vec<&str> {
self.0
.keys()
.map(|value| value.as_str())
.collect::<Vec<_>>()
}
}
pub(crate) fn register_request(req: Request<Body>) -> Request<Body> {
let parent_context = opentelemetry::global::get_text_map_propagator(|prop| {
prop.extract(&HeaderExtractor(req.headers()))
});
let span = Span::current();
span.set_parent(parent_context);
let trace_id = span.context().span().span_context().trace_id();
let span_id = span.context().span().span_context().span_id();
span.record("trace_id", trace_id.to_string());
span.record("span_id", span_id.to_string());
req
}