Rustでaxumとreqwestを使用したロギングの実装
背景
最近はRustの勉強にはまっており、個人アプリをつくりながら学習しています。
Rustは主にWeb API Backendの用途で使っています。
APIをつくっていると、loggingしたいですが自分で設定していく必要があります。
今回はその実装時に調べたことを記事にしました。
はじめに
RustのWebフレームワークaxumと、HTTPクライアントライブラリreqwestを使用する際のロギング実装について説明します。トレーシングとロギングを適切に設定することで、アプリケーションの動作を詳細に把握し、デバッグや性能分析を効果的に行うことができます。
axumでのロギング設定
axumでロギングを実装するには、主にtracingクレートを使用します。
1. 依存関係の追加
Cargo.toml
に以下の依存関係を追加します:
[dependencies]
axum = "0.6.0"
tokio = { version = "1.0", features = ["full"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
2. トレーシングの初期化
main.rs
でトレーシングを初期化します:
use axum::{Router, routing::get};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
#[tokio::main]
async fn main() {
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::new(
std::env::var("RUST_LOG").unwrap_or_else(|_| "info".into()),
))
.with(tracing_subscriber::fmt::layer())
.init();
// ルーターの設定
let app = Router::new().route("/", get(handler));
// サーバーの起動
let addr = "127.0.0.1:3000";
tracing::info!("Listening on {}", addr);
axum::Server::bind(&addr.parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
async fn handler() -> &'static str {
"Hello, World!"
}
3. ミドルウェアを使用したリクエストのロギング
すべてのリクエストをログに記録するミドルウェアを実装します:
use axum::{
Router,
routing::get,
middleware::{self, Next},
response::IntoResponse,
http::Request,
body::Body,
};
use tracing::instrument;
pub async fn logging_middleware(req: Request<Body>, next: Next) -> impl IntoResponse {
let path = req.uri().path().to_owned();
let method = req.method().clone();
tracing::info!("Received request: {} {}", method, path);
next.run(req).await
}
#[tokio::main]
async fn main() {
// トレーシングの初期化(前述のコードと同じ)
let app = Router::new()
.route("/", get(handler))
.layer(middleware::from_fn(logging_middleware));
// サーバーの起動(前述のコードと同じ)
}
#[instrument]
async fn handler() -> impl IntoResponse {
tracing::info!("Handling request");
"Hello, World!"
}
reqwestを使った外部API呼び出しのトレーシング
reqwestを使用した外部API呼び出しをトレーシングするには、reqwest-tracingクレートを使用します。
1. 依存関係の追加
[dependencies]
reqwest = { version = "0.11", features = ["json"] }
reqwest-middleware = "0.2"
reqwest-tracing = "0.4"
tracing = "0.1"
2. トレーシングミドルウェアの設定
use reqwest_middleware::ClientBuilder;
use reqwest_tracing::TracingMiddleware;
let client = ClientBuilder::new(reqwest::Client::new())
.with(TracingMiddleware::default())
.build();
3. APIリクエストの実装
#[tracing::instrument]
async fn call_api() -> Result<(), Box<dyn std::error::Error>> {
let response = client
.get("https://api.example.com/data")
.send()
.await?;
let data = response.json::<serde_json::Value>().await?;
tracing::info!("Received data: {:?}", data);
Ok(())
}
リクエストボディのロギング
reqwestのリクエストボディをログ出力するには、カスタムミドルウェアを作成します。
1. 依存関係の追加
[dependencies]
reqwest = { version = "0.11", features = ["json"] }
reqwest-middleware = "0.2"
tracing = "0.1"
serde_json = "1.0"
2. カスタムミドルウェアの実装
use reqwest_middleware::{Middleware, Next, Request, Response};
use std::future::Future;
use std::pin::Pin;
use tracing::info;
pub struct LoggingMiddleware;
impl Middleware for LoggingMiddleware {
fn handle<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
req: Request,
extensions: &'life1 mut task_local_extensions::Extensions,
next: Next<'life2>,
) -> Pin<Box<dyn Future<Output = reqwest_middleware::Result<Response>> + Send + 'async_trait>>
where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
{
Box::pin(async move {
// リクエストボディをログ出力
if let Some(body) = req.body() {
if let Ok(body_str) = String::from_utf8(body.as_bytes().unwrap_or(&[]).to_vec()) {
info!("Request body: {}", body_str);
}
}
// JSONボディの場合
if let Some(json) = req.json() {
if let Ok(json_str) = serde_json::to_string_pretty(json) {
info!("Request JSON body: {}", json_str);
}
}
let response = next.run(req, extensions).await?;
// レスポンスボディをログ出力(必要に応じて)
let body = response.text().await?;
info!("Response body: {}", body);
Ok(response)
})
}
}
3. ミドルウェアの適用
use reqwest_middleware::ClientBuilder;
let client = ClientBuilder::new(reqwest::Client::new())
.with(LoggingMiddleware)
.build();
まとめ
この記事では、Rustでaxumとreqwestを使用する際のロギング実装について説明しました。トレーシングの初期化、ミドルウェアの使用、外部API呼び出しのトレーシング、そしてリクエストボディのロギングまで、段階的に実装方法を紹介しました。
これらの技術を適切に組み合わせることで、アプリケーションの動作を詳細に把握し、効果的なデバッグや性能分析が可能になります。ただし、セキュリティとプライバシーの観点から、機密情報や個人情報を含むデータのログ出力には十分注意する必要があります。また、大量のリクエストを処理する場合は、ロギングがアプリケーションのパフォーマンスに与える影響も考慮してください。
Discussion