Expand description
§axum-tracing-opentelemetry
Middlewares to integrate axum + tracing + opentelemetry.
- Read OpenTelemetry header from incoming request
- Start a new trace if no trace found in the incoming request
- Trace is attached into tracing’span
- OpenTelemetry Span is created on close of the tracing’s span (behavior from [tracing-opentelemetry])
For examples, you can look at the examples folder.
//...
use axum_tracing_opentelemetry::middleware::{OtelAxumLayer, OtelInResponseLayer};
#[tokio::main]
async fn main() -> Result<(), axum::BoxError> {
// very opinionated init of tracing, look as is source to make your own
let _guard = init_tracing_opentelemetry::TracingConfig::production().init_subscriber()?;
let app = app();
// run it
let addr = &"0.0.0.0:3000".parse::<SocketAddr>()?;
tracing::warn!("listening on {}", addr);
let listener = tokio::net::TcpListener::bind(addr).await?;
axum::serve(listener, app.into_make_service()).await?;
Ok(())
}
fn app() -> Router {
Router::new()
.route("/", get(index)) // request processed inside span
// include trace context as header into the response
.layer(OtelInResponseLayer::default())
//start OpenTelemetry trace on incoming request
.layer(OtelAxumLayer::default())
.route("/health", get(health)) // request processed without span / trace
}For more info about how to initialize, you can look at crate init-tracing-opentelemetry or tracing-opentelemetry.

§Differences with tower_http::trace
tower_http::trace is a general-purpose logging middleware: it emits tracing events for request/response lifecycle (start, end, failure) and lets you customize the span fields yourself.
axum-tracing-opentelemetry is focused on OpenTelemetry distributed tracing instead:
- Extracts the
OTeltrace context (W3Ctraceparent/tracestateheaders) from the incoming request and makes it the parent of the request’s span, so traces connect across services. (No B3 support.) - Populates the span with OTel HTTP semantic convention attributes (
http.request.method,url.path,server.address, …) instead of arbitrary custom fields. - Optionally injects the trace context back into the response headers (
OtelInResponseLayer), so thetrace_idis available to clients — handy to surface in error messages (API or human-facing) for support/debugging. This is not the same astower_http’sX-Request-Id(from its separaterequest_idmodule, nottrace): ours is the actualOTeltrace id, correlated with your backend traces. - Does not log request/response bodies or emit per-phase log events — combine it with
tower_http::trace(or your own logging) if you need that.
Use tower_http::trace for human-readable request logging, and OtelAxumLayer for OTel trace propagation/correlation. They are complementary and can be layered together.
See issue #158 for more background and discussion.
§Metrics endpoint
Enable the metrics-prometheus feature to get prometheus_metrics::router, which mounts a GET /metrics route serving a Prometheus Registry in text format:
let app = Router::new()
.merge(axum_tracing_opentelemetry::prometheus_metrics::router(registry))
.route("/", get(index));See init-tracing-opentelemetry’s “Metrics” section for how to build registry via TracingConfig::with_metrics_prometheus(), and the axum-prometheus example.
§Changelog - History
Re-exports§
pub use self::middleware::opentelemetry_tracing_layer;Deprecated pub use self::middleware::response_with_trace_layer;Deprecated pub use tracing_opentelemetry_instrumentation_sdk;