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
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 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
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.