rust
48 lines · 7 steps
Structured request tracing in Axum
Wire a TraceLayer into an Axum router to emit a structured span for every HTTP request.
Explained by
highlit
1use std::time::Duration;
2
3use axum::{
4 body::Body,
5 extract::MatchedPath,
6 http::{Request, Response},
7 routing::get,
8 Router,
9};
10use tower_http::trace::TraceLayer;
11use tracing::{info_span, Span};
12
13pub fn app() -> Router {
14 Router::new()
15 .route("/health", get(|| async { "ok" }))
16 .route("/users/:id", get(get_user))
17 .layer(
18 TraceLayer::new_for_http()
19 .make_span_with(|request: &Request<Body>| {
20 let matched_path = request
21 .extensions()
22 .get::<MatchedPath>()
23 .map(MatchedPath::as_str);
24
25 info_span!(
26 "http_request",
27 method = %request.method(),
28 uri = %request.uri(),
29 matched_path,
30 request_id = %uuid::Uuid::new_v4(),
31 )
32 })
33 .on_response(|response: &Response<Body>, latency: Duration, _span: &Span| {
34 tracing::info!(
35 status = response.status().as_u16(),
36 latency_ms = latency.as_millis(),
37 "request completed"
38 );
39 })
40 .on_failure(|error, latency: Duration, _span: &Span| {
41 tracing::error!(
42 %error,
43 latency_ms = latency.as_millis(),
44 "request failed"
45 );
46 }),
47 )
48}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A single tower layer can instrument every route without touching individual handlers.
- 2Reading MatchedPath keeps span labels low-cardinality by grouping requests under their route template.
- 3Separate hooks for span creation, success, and failure give you full-lifecycle observability per request.
Related explainers
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
rust
use std::cmp::{Ordering, Reverse}; use std::collections::BinaryHeap; use std::fs::File; use std::io::{self, BufRead, BufReader, BufWriter, Lines, Write};
K-way merge of sorted logs in Rust
binary-heap
k-way-merge
streaming-io
Intermediate
8 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
rust
use axum::{ extract::{Path, State}, response::sse::{Event, KeepAlive, Sse}, };
Streaming import progress with SSE in Axum
server-sent-events
streams
watch-channel
Advanced
7 steps
rust
use std::f64::consts::PI; #[derive(Debug, Clone, Copy)] pub struct LatLng {
Building geographic bounding boxes in Rust
geospatial
value-types
option
Intermediate
7 steps
rust
use chrono::{Duration, NaiveDate}; #[derive(Debug)] pub struct DateRange {
Parsing and iterating date ranges in Rust
error-handling
iterators
parsing
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/structured-request-tracing-in-axum-explained-rust-d021/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.