Repository guide ·
axum-otel ·
tracing-otel ·
otel-init ·
Examples
otel-kit is a workspace of three crates with separate
observability responsibilities:
axum-otelprovidestower-http::TraceLayercallbacks for inbound Axum request spans;tracing-otelprovides shared HTTP tracing utilities and an optional, opinionated logging/bootstrap facade;otel-initinitializes OpenTelemetry providers and tracing subscribers and owns their shutdown lifecycle.
Choose the smallest crate that owns the behavior your application needs. Use
axum-otel with an existing subscriber for focused request tracing, or combine
it with tracing-otel when you want tracing, metrics, structured logs, and
provider lifecycle management configured together.
| Crate | Responsibility | Use it when |
|---|---|---|
axum-otel |
Axum-specific request span creation, response events, and failure status | You need inbound Axum request tracing |
tracing-otel |
HTTP tracing helpers, dynamic tracing macros, console/file logging, and application bootstrap | You need shared tracing behavior or the Logger facade |
otel-init |
OTLP tracer, meter, and logger providers; subscriber setup; provider shutdown | You own subscriber setup or need provider-level initialization |
The feature-dependent dependency direction is:
axum-otel -> tracing-otel -> otel-init
tracing-otel has no default features. Applications must explicitly enable
the capabilities they use; see the Cargo feature guide.
This example combines the Axum callbacks with the opinionated Logger facade:
[dependencies]
anyhow = "1"
axum = "0.8"
axum-otel = "0.33.1"
tokio = { version = "1", features = ["macros", "net", "rt-multi-thread"] }
tower-http = { version = "0.6", features = ["trace"] }
tracing-otel = { version = "0.33.1", features = ["logger"] }use axum::{Router, routing::get};
use axum_otel::{
AxumOtelOnFailure, AxumOtelOnResponse, AxumOtelSpanCreator, Level,
};
use tower_http::trace::TraceLayer;
use tracing_otel::{LogFormat, Logger};
async fn health() -> &'static str {
"OK"
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let _guard = Logger::new("web-api")
.with_format(LogFormat::Json)
.with_ansi(false)
.init()?;
let app = Router::new().route("/health", get(health)).layer(
TraceLayer::new_for_http()
.make_span_with(AxumOtelSpanCreator::new().level(Level::INFO))
.on_response(AxumOtelOnResponse::new().level(Level::INFO))
.on_failure(AxumOtelOnFailure::new().level(Level::ERROR)),
);
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await?;
axum::serve(listener, app).await?;
Ok(())
}Keep the returned LoggerGuard alive for the application lifetime. It owns the
OpenTelemetry providers and any non-blocking file writer guard, and shuts them
down in the required order.
OTLP export is opt-in. Without a configured endpoint, providers remain local
and no connection to localhost:4317 is attempted:
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpcSee Getting started, Logger configuration, and OTLP export and lifecycle for the complete setup.
Both axum-otel and axum-tracing-opentelemetry help Axum applications create
OpenTelemetry-aware HTTP request spans. The difference is mainly the intended
scope.
axum-tracing-opentelemetry is a focused choice when you:
- want request tracing and context propagation with a small middleware surface;
- already own the
tracingsubscriber, exporters, metrics, and logging setup; - prefer to assemble the observability stack from independent components.
For applications with an established observability bootstrap, that narrower scope can be exactly what is needed.
axum-otel itself stays focused on three TraceLayer callbacks:
AxumOtelSpanCreatorcreates and enriches request spans;AxumOtelOnResponserecords response status and emits completion events;AxumOtelOnFailuremarks classified server failures as OpenTelemetry errors.
The broader, opinionated setup comes from composing it with the other workspace
crates. tracing-otel adds structured console/file logging and application
bootstrap, while otel-init adds trace, metric, and optional log providers plus
RAII lifecycle management.
Choose axum-tracing-opentelemetry when you primarily need focused middleware
and already own the rest of the stack. Choose this workspace when you want the
same HTTP tracing boundary plus reusable HTTP span helpers or a cohesive
tracing, metrics, logging, and shutdown setup.
axum-otel builds on tower-http::TraceLayer and records OpenTelemetry-aligned
HTTP attributes including method, route, URL parts, protocol, server.address,
client.address when ConnectInfo<SocketAddr> is available,
user_agent.original, request_id, and trace_id.
The shared span schema lives in tracing-otel; the Axum adapter owns matched
route, transport peer, span name, and server span kind. See
HTTP span attributes before writing dashboards, alerts,
or sampling rules.
examples/oteldemonstrates one Axum service with request IDs and environment-based logger configuration.examples/microservicesdemonstrates cross-service propagation with Grafana, Loki, and Tempo-oriented local infrastructure.
Run the basic service with:
cargo run -p axum-otel-demoVersion 0.33.1 introduced new crates.io package names. Cargo does not migrate
package names automatically:
# Before
tracing-otel-extra = "0.33.0"
tracing-opentelemetry-extra = "0.33.0"
# After
tracing-otel = "0.33.1"
otel-init = "0.33.1"Update Rust imports from tracing_otel_extra to tracing_otel and from
tracing_opentelemetry_extra to otel_init. Also select explicit
tracing-otel features: logger for code configuration, env for
Logger::from_env, or the narrower HTTP features listed in the
feature guide.
See the complete migration guide for downstream dashboard and HTTP attribute changes.
- Rust 1.96.0 is the workspace compatibility baseline.
- The repository toolchain may use a newer compiler for local development.
- OpenTelemetry export supports gRPC, HTTP/Protobuf, and HTTP/JSON according to the configured endpoint and protocol environment variables.
- Apache License, Version 2.0 (LICENSE-APACHE);
- MIT License (LICENSE-MIT).