rust
55 lines · 8 steps
Logging request and response sizes in Axum
An Axum middleware buffers each request and response body to measure and log their byte sizes without breaking the handler chain.
Explained by
highlit
1use axum::{
2 body::{Body, Bytes},
3 extract::Request,
4 http::StatusCode,
5 middleware::{from_fn, Next},
6 response::{IntoResponse, Response},
7 routing::{get, post},
8 Router,
9};
10use http_body_util::BodyExt;
11use tracing::info;
12
13pub fn upload_routes() -> Router {
14 Router::new()
15 .route("/upload", post(handle_upload))
16 .route("/blobs/:id", get(fetch_blob))
17 .layer(from_fn(log_body_sizes))
18}
19
20async fn log_body_sizes(request: Request, next: Next) -> Result<Response, StatusCode> {
21 let method = request.method().clone();
22 let path = request.uri().path().to_owned();
23
24 let (parts, body) = request.into_parts();
25 let req_bytes = buffer(body).await?;
26 let req_len = req_bytes.len();
27 let request = Request::from_parts(parts, Body::from(req_bytes));
28
29 let response = next.run(request).await;
30
31 let (parts, body) = response.into_parts();
32 let res_bytes = buffer(body).await?;
33 let res_len = res_bytes.len();
34
35 info!(
36 %method,
37 %path,
38 status = parts.status.as_u16(),
39 request_bytes = req_len,
40 response_bytes = res_len,
41 "handled request"
42 );
43
44 Ok((parts, Body::from(res_bytes)).into_response())
45}
46
47async fn buffer<B>(body: B) -> Result<Bytes, StatusCode>
48where
49 B: axum::body::HttpBody<Data = Bytes>,
50{
51 body.collect()
52 .await
53 .map(|c| c.to_bytes())
54 .map_err(|_| StatusCode::BAD_REQUEST)
55}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Buffering a streaming body consumes it, so you must rebuild the request or response from the collected bytes to pass it along.
- 2Splitting a request or response into parts and body lets middleware inspect metadata and payload independently.
- 3A generic helper bounded on HttpBody can collect any body type into Bytes, mapping errors into a clean status code.
Related explainers
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect app = FastAPI()
Building a WebSocket chat with FastAPI
websockets
broadcast
connection-management
Intermediate
9 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
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/logging-request-and-response-sizes-in-axum-explained-rust-9ff0/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.