Overview
Enabling logging for the Reqwest client helps debug HTTP requests, monitor network activity, and troubleshoot connection issues. Rust provides several logging options through the log
facade and modern tracing
ecosystem.
Method 1: Using env_logger (Simple Setup)
Dependencies
Add these dependencies to your Cargo.toml
:
[dependencies]
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1.0", features = ["full"] }
log = "0.4"
env_logger = "0.10"
Basic Implementation
use log::{info, debug};
use reqwest::Client;
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Initialize logger with default settings
env_logger::init();
let client = Client::new();
info!("Making request to httpbin.org");
let response = client
.get("https://httpbin.org/get")
.header("User-Agent", "my-rust-app/1.0")
.send()
.await?;
info!("Response status: {}", response.status());
debug!("Response headers: {:?}", response.headers());
Ok(())
}
Environment Variables
Control logging levels with RUST_LOG
:
# Enable all debug logs
RUST_LOG=debug cargo run
# Only Reqwest debug logs
RUST_LOG=reqwest=debug cargo run
# Multiple targets with different levels
RUST_LOG=reqwest=debug,hyper=info cargo run
# Enable trace-level logging for detailed HTTP info
RUST_LOG=reqwest=trace cargo run
Method 2: Using tracing (Recommended)
The tracing
crate provides more advanced logging capabilities:
Dependencies
[dependencies]
reqwest = { version = "0.11", features = ["json", "tracing"] }
tokio = { version = "1.0", features = ["full"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
Implementation
use reqwest::Client;
use tracing::{info, debug, instrument};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize tracing
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "reqwest=debug,hyper=info".into()),
)
.with(tracing_subscriber::fmt::layer())
.init();
make_request().await?;
Ok(())
}
#[instrument]
async fn make_request() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
info!("Starting HTTP request");
let response = client
.post("https://httpbin.org/post")
.json(&serde_json::json!({
"name": "John Doe",
"email": "john@example.com"
}))
.send()
.await?;
info!(status = %response.status(), "Request completed");
debug!(headers = ?response.headers(), "Response headers");
let body: serde_json::Value = response.json().await?;
debug!(response_body = %body, "Response body");
Ok(())
}
Method 3: Custom Logger Configuration
For more control over logging format and output:
use env_logger::{Builder, Target};
use log::LevelFilter;
use std::io::Write;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Custom logger configuration
Builder::from_default_env()
.target(Target::Stdout)
.filter_level(LevelFilter::Debug)
.format(|buf, record| {
writeln!(buf,
"{} [{}] - {} - {}",
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
record.level(),
record.target(),
record.args()
)
})
.init();
let client = reqwest::Client::builder()
.user_agent("my-app/1.0")
.timeout(std::time::Duration::from_secs(10))
.build()?;
// The client will now log detailed request/response info
let response = client
.get("https://api.github.com/users/octocat")
.send()
.await?;
println!("User data: {}", response.text().await?);
Ok(())
}
Logging Output Examples
With RUST_LOG=reqwest=debug
, you'll see output like:
DEBUG reqwest::connect: starting new connection: https://httpbin.org/
DEBUG reqwest::async_impl::client: response '200 OK' for https://httpbin.org/get
DEBUG reqwest::async_impl::decoder: decoding response body
With RUST_LOG=hyper=debug
, you'll get lower-level HTTP details:
DEBUG hyper::client::connect::http: connecting to 54.175.219.8:443
DEBUG hyper::client::connect::http: connected to 54.175.219.8:443
DEBUG hyper::proto::h1::io: flushed 78 bytes
Production Configuration
For production environments, consider structured logging:
use tracing_subscriber::fmt::format::FmtSpan;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt()
.with_env_filter("reqwest=info,my_app=debug")
.with_span_events(FmtSpan::CLOSE)
.json() // JSON format for log aggregation
.init();
// Your application code here
Ok(())
}
Common Log Levels
- TRACE: Very detailed, includes request/response bodies
- DEBUG: Request URLs, headers, timing information
- INFO: High-level request/response status
- WARN: Retry attempts, timeouts
- ERROR: Failed requests, connection errors
Enable the appropriate level based on your debugging needs while being mindful of performance and log volume in production.