rust
53 lines · 8 steps
Logging request bodies in Axum middleware
An Axum middleware that buffers the entire request body, logs it, then rebuilds the request so downstream handlers still receive it.
Explained by
highlit
1use axum::{
2 body::{Body, Bytes},
3 extract::Request,
4 http::StatusCode,
5 middleware::Next,
6 response::{IntoResponse, Response},
7};
8use http_body_util::BodyExt;
9
10pub async fn log_request_body(request: Request, next: Next) -> Result<Response, Response> {
11 let (parts, body) = request.into_parts();
12
13 let bytes = match body.collect().await {
14 Ok(collected) => collected.to_bytes(),
15 Err(err) => {
16 return Err((
17 StatusCode::BAD_REQUEST,
18 format!("failed to read request body: {err}"),
19 )
20 .into_response());
21 }
22 };
23
24 log_payload(&parts.method, &parts.uri, &bytes);
25
26 let request = Request::from_parts(parts, Body::from(bytes));
27 Ok(next.run(request).await)
28}
29
30fn log_payload(method: &axum::http::Method, uri: &axum::http::Uri, bytes: &Bytes) {
31 const MAX_LOGGED: usize = 4096;
32
33 if bytes.is_empty() {
34 tracing::info!(%method, %uri, "request with empty body");
35 return;
36 }
37
38 match std::str::from_utf8(bytes) {
39 Ok(text) => {
40 let truncated = text.len() > MAX_LOGGED;
41 let body = &text[..text.len().min(MAX_LOGGED)];
42 tracing::info!(%method, %uri, truncated, body, "request body");
43 }
44 Err(_) => {
45 tracing::info!(
46 %method,
47 %uri,
48 bytes = bytes.len(),
49 "request body (binary, not logged)"
50 );
51 }
52 }
53}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Reading a streaming body is destructive, so you must reassemble the request before passing it on.
- 2Buffering the whole body into memory trades streaming efficiency for the ability to inspect it.
- 3Guarding logged output with a size cap and UTF-8 check keeps logs safe from huge or binary payloads.
Related explainers
typescript
import { AsyncLocalStorage } from 'node:async_hooks'; import { randomUUID } from 'node:crypto'; import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express';
Request correlation IDs in NestJS with AsyncLocalStorage
async-local-storage
middleware
logging
Advanced
8 steps
rust
use std::time::Duration; use axum::{ body::Body,
Turning Axum timeouts into 504 JSON
middleware
timeouts
error-handling
Intermediate
7 steps
go
package middleware import ( "net/http"
A per-IP rate limiter middleware in Gin
rate-limiting
token-bucket
middleware
Intermediate
8 steps
rust
use axum::{ extract::State, response::sse::{Event, KeepAlive, Sse}, };
Broadcasting live prices over SSE in Axum
server-sent-events
broadcast-channel
streams
Advanced
9 steps
go
package tsvio import ( "bufio"
Reading and writing TSV records in Go
parsing
io-streams
error-handling
Intermediate
10 steps
rust
pub struct Fibonacci { current: u64, next: u64, }
A Fibonacci iterator in Rust
iterators
traits
lazy-evaluation
Intermediate
6 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-bodies-in-axum-middleware-explained-rust-8710/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.