use axum::extract::MatchedPath;
use http::{Request, Response};
use pin_project_lite::pin_project;
use std::{
error::Error,
future::Future,
pin::Pin,
task::{Context, Poll},
};
use tower::{Layer, Service};
use tracing::Span;
use tracing_opentelemetry_instrumentation_sdk::http as otel_http;
use crate::axum::http_server;
#[deprecated(
since = "0.12.0",
note = "keep for transition, replaced by OtelAxumLayer"
)]
#[must_use]
pub fn opentelemetry_tracing_layer() -> OtelAxumLayer {
OtelAxumLayer::default()
}
pub type Filter = fn(&str) -> bool;
#[derive(Default, Debug, Clone)]
pub struct OtelAxumLayer {
filter: Option<Filter>,
}
impl OtelAxumLayer {
#[must_use]
pub fn filter(self, filter: Filter) -> Self {
OtelAxumLayer {
filter: Some(filter),
}
}
}
impl<S> Layer<S> for OtelAxumLayer {
type Service = OtelAxumService<S>;
fn layer(&self, inner: S) -> Self::Service {
OtelAxumService {
inner,
filter: self.filter,
}
}
}
#[derive(Debug, Clone)]
pub struct OtelAxumService<S> {
inner: S,
filter: Option<Filter>,
}
impl<S, B, B2> Service<Request<B>> for OtelAxumService<S>
where
S: Service<Request<B>, Response = Response<B2>> + Clone + Send + 'static,
S::Error: Error + 'static, S::Future: Send + 'static,
B: Send + 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future = ResponseFuture<S::Future>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx).map_err(Into::into)
}
fn call(&mut self, req: Request<B>) -> Self::Future {
use tracing_opentelemetry::OpenTelemetrySpanExt;
let req = req;
let span = if self.filter.map_or(true, |f| f(req.uri().path())) {
let span = http_server::make_span_from_request(&req);
let route = http_route(&req);
let method = otel_http::http_method(req.method());
span.record("http.route", route);
span.record("otel.name", format!("{method} {route}").trim());
span.set_parent(otel_http::extract_context(req.headers()));
span
} else {
tracing::Span::none()
};
let future = {
let _ = span.enter();
self.inner.call(req)
};
ResponseFuture {
inner: future,
span,
}
}
}
pin_project! {
pub struct ResponseFuture<F> {
#[pin]
pub(crate) inner: F,
pub(crate) span: Span,
}
}
impl<Fut, ResBody, E> Future for ResponseFuture<Fut>
where
Fut: Future<Output = Result<Response<ResBody>, E>>,
E: std::error::Error + 'static,
{
type Output = Result<Response<ResBody>, E>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
let _guard = this.span.enter();
let result = futures_util::ready!(this.inner.poll(cx));
http_server::update_span_from_response_or_error(this.span, &result);
Poll::Ready(result)
}
}
#[inline]
fn http_route<B>(req: &Request<B>) -> &str {
req.extensions()
.get::<MatchedPath>()
.map_or_else(|| "", |mp| mp.as_str())
}