tracing

構造化・非同期ロギングとスパンベースの可観測性を提供する現代的なRustライブラリ。従来のロギングマクロ(info!、error!等)に加え、分散トレーシングとメトリクス収集に対応。マイクロサービス環境での監視に最適化。

ロギングRust構造化ログ非同期分散トレーシング可観測性

ライブラリ

tracing

概要

tracingは構造化・非同期ロギングとスパンベースの可観測性を提供する現代的なRustライブラリです。従来のロギングマクロ(info!、error!等)に加え、分散トレーシングとメトリクス収集に対応し、マイクロサービス環境での監視に最適化されています。2025年マイクロサービスとクラウドネイティブ環境でのRust開発において急速に採用拡大しており、構造化ログと分散トレーシングの重要性増加により、本格的な本番システムでの選択肢として注目されています。

詳細

tracing 2025年版は、可観測性を重視するモダンなRustプロジェクトでの標準的な選択肢となりつつあります。従来のログ出力機能を超えて、アプリケーションの実行フローを追跡する「スパン」という概念を導入し、分散システムでのリクエスト追跡、パフォーマンス分析、デバッグを強力にサポート。非同期処理との親和性が高く、Tokioエコシステムとの統合により、高性能な非同期アプリケーションでの利用価値が高く評価されています。

主な特徴

  • 構造化ロギング: key-value形式でのリッチなコンテキスト情報管理
  • スパンベーストレーシング: 処理の開始から終了までの追跡とネスト構造
  • 非同期対応: Tokio等の非同期ランタイムとの完全統合
  • 分散トレーシング: OpenTelemetryとの統合による分散システム可視化
  • メトリクス収集: パフォーマンス指標の自動収集と分析
  • 拡張可能なSubscriberシステム: プラガブルな出力先とフォーマット

メリット・デメリット

メリット

  • マイクロサービスやクラウドネイティブ環境での優れた可観測性
  • 非同期処理との完全統合によりパフォーマンス低下を最小化
  • OpenTelemetryサポートによる業界標準の分散トレーシング対応
  • 構造化ログによる機械的な解析とクエリが容易
  • スパン機能により複雑な処理フローの可視化が可能
  • 豊富なエコシステムと活発なコミュニティサポート

デメリット

  • 従来のログライブラリと比較して学習コストが高い
  • 単純なアプリケーションには機能が過剰になる場合がある
  • 設定とSubscriberの理解に時間を要する
  • メモリ使用量が従来のログライブラリより多い傾向
  • デバッグ時の出力が複雑で初心者には理解が困難
  • 小規模プロジェクトでは導入コストが見合わない場合がある

参考ページ

書き方の例

インストールと基本セットアップ

# Cargo.tomlに依存関係を追加
[dependencies]
tracing = "0.1"
tracing-subscriber = "0.3"

# 非同期アプリケーションでの推奨セットアップ
tokio = { version = "1.0", features = ["full"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
tracing-opentelemetry = "0.21"  # OpenTelemetry統合用
tracing-appender = "0.2"        # ファイル出力用

基本的なロギング(従来のlogクレート互換)

use tracing::{info, warn, error, debug, trace};
use tracing_subscriber;

fn main() {
    // 基本的なSubscriberの初期化
    tracing_subscriber::init();

    // 従来のログライブラリと同様の使用方法
    trace!("トレースレベルの詳細情報");
    debug!("デバッグ情報: アプリケーション開始");
    info!("情報: 設定ファイルを読み込みました");
    warn!("警告: 一時的な設定を使用中");
    error!("エラー: データベース接続に失敗");

    // 構造化ログ(key-value形式)
    info!(
        user_id = 12345,
        action = "login",
        ip_address = "192.168.1.100",
        "ユーザーがログインしました"
    );

    // 変数の展開
    let request_id = "req_abcd1234";
    let response_time_ms = 150;
    
    info!(
        request_id,
        response_time_ms,
        "リクエスト処理が完了しました"
    );
}

スパンベーストレーシング

use tracing::{info, warn, error, span, Level, instrument};
use tracing_subscriber;

#[tokio::main]
async fn main() {
    tracing_subscriber::init();

    info!("アプリケーション開始");

    // 手動スパン作成
    let span = span!(Level::INFO, "user_registration", user_id = 12345);
    let _enter = span.enter();

    info!("ユーザー登録プロセス開始");
    
    // ネストしたスパン
    register_user("[email protected]").await;
    
    info!("ユーザー登録プロセス完了");
}

// 自動スパン生成(instrument属性使用)
#[instrument]
async fn register_user(email: &str) -> Result<u64, String> {
    info!("ユーザー登録開始");
    
    // データベース操作のスパン
    let user_id = create_user_in_database(email).await?;
    
    // メール送信のスパン
    send_welcome_email(email, user_id).await?;
    
    info!(user_id, "ユーザー登録が正常に完了");
    Ok(user_id)
}

#[instrument(skip(email), fields(email = %email))]
async fn create_user_in_database(email: &str) -> Result<u64, String> {
    info!("データベースにユーザーを作成中");
    
    // データベース操作のシミュレーション
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    
    let user_id = 12345; // 生成されたユーザーID
    info!(user_id, "ユーザーがデータベースに作成されました");
    
    Ok(user_id)
}

#[instrument]
async fn send_welcome_email(email: &str, user_id: u64) -> Result<(), String> {
    info!("ウェルカムメール送信中");
    
    // メール送信のシミュレーション
    tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
    
    info!("ウェルカムメールの送信が完了");
    Ok(())
}

高度なSubscriber設定とフィルタリング

use tracing::{info, warn, error, Level};
use tracing_subscriber::{
    fmt,
    layer::SubscriberExt,
    util::SubscriberInitExt,
    EnvFilter,
    Registry,
};
use tracing_appender::{rolling, non_blocking};

fn main() {
    // 複数の出力先を持つSubscriberの設定
    let file_appender = rolling::daily("logs", "app.log");
    let (non_blocking, _guard) = non_blocking(file_appender);

    let subscriber = Registry::default()
        .with(EnvFilter::from_default_env()
            .add_directive("myapp=debug".parse().unwrap())
            .add_directive("myapp::database=trace".parse().unwrap()))
        .with(fmt::Layer::new()
            .with_writer(std::io::stdout)
            .with_ansi(true)
            .with_level(true)
            .with_target(true)
            .with_thread_ids(true)
            .with_thread_names(true))
        .with(fmt::Layer::new()
            .with_writer(non_blocking)
            .with_ansi(false)
            .json());

    subscriber.init();

    info!("設定されたSubscriberでアプリケーション開始");

    // さまざまなモジュールからのログ
    database_operations();
    network_operations();
    business_logic();
}

mod database {
    use tracing::{info, debug, error, instrument, span, Level};

    #[instrument]
    pub fn connect() -> Result<(), &'static str> {
        debug!("データベース接続設定を読み込み中");
        info!("データベースに接続しました");
        Ok(())
    }

    #[instrument(skip(query), fields(query_type = "SELECT"))]
    pub fn execute_query(query: &str) -> Result<Vec<String>, &'static str> {
        let span = span!(Level::DEBUG, "query_execution", query_length = query.len());
        let _enter = span.enter();
        
        debug!("SQLクエリ実行中");
        
        // クエリ実行のシミュレーション
        std::thread::sleep(std::time::Duration::from_millis(50));
        
        info!(rows_affected = 3, "クエリが正常に実行されました");
        Ok(vec!["row1".to_string(), "row2".to_string(), "row3".to_string()])
    }
}

mod network {
    use tracing::{info, warn, error, instrument};

    #[instrument]
    pub fn send_http_request(url: &str) -> Result<String, &'static str> {
        info!("HTTPリクエスト送信開始");
        
        // ネットワーク処理のシミュレーション
        std::thread::sleep(std::time::Duration::from_millis(300));
        
        info!(status_code = 200, "HTTPリクエストが成功");
        Ok("response_data".to_string())
    }

    #[instrument]
    pub fn handle_timeout() {
        warn!("ネットワークタイムアウトが発生");
        error!("接続がタイムアウトしました");
    }
}

fn database_operations() {
    let _ = database::connect();
    let _ = database::execute_query("SELECT * FROM users WHERE active = true");
}

fn network_operations() {
    let _ = network::send_http_request("https://api.example.com/data");
    network::handle_timeout();
}

fn business_logic() {
    info!("ビジネスロジック処理開始");
    info!("ビジネスロジック処理完了");
}

非同期処理とOpenTelemetry統合

use tracing::{info, instrument, Span};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use tracing_opentelemetry::OpenTelemetryLayer;
use opentelemetry::{global, KeyValue};
use opentelemetry_sdk::{
    trace::{self, TracerProvider},
    Resource,
};
use opentelemetry_jaeger::JaegerPipeline;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
    // OpenTelemetryトレーサーの設定
    global::set_text_map_propagator(opentelemetry_jaeger::Propagator::new());
    
    let tracer = opentelemetry_jaeger::new_agent_pipeline()
        .with_service_name("myapp")
        .install_simple()?;

    // Subscriberの設定
    tracing_subscriber::registry()
        .with(tracing_subscriber::EnvFilter::new("myapp=debug"))
        .with(tracing_subscriber::fmt::layer())
        .with(OpenTelemetryLayer::new(tracer))
        .init();

    info!("分散トレーシング対応アプリケーション開始");

    // 分散トレーシング対応の処理
    process_order("order_12345").await?;

    // OpenTelemetryのシャットダウン
    global::shutdown_tracer_provider();
    Ok(())
}

#[instrument]
async fn process_order(order_id: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    info!("注文処理開始");
    
    // カスタム属性の追加
    Span::current().set_attribute(KeyValue::new("order.priority", "high"));
    
    // 各ステップの処理
    validate_order(order_id).await?;
    reserve_inventory(order_id).await?;
    process_payment(order_id).await?;
    ship_order(order_id).await?;
    
    info!("注文処理完了");
    Ok(())
}

#[instrument]
async fn validate_order(order_id: &str) -> Result<(), &'static str> {
    info!("注文検証中");
    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
    
    // バリデーション結果
    Span::current().set_attribute(KeyValue::new("validation.result", "success"));
    info!("注文検証完了");
    Ok(())
}

#[instrument]
async fn reserve_inventory(order_id: &str) -> Result<(), &'static str> {
    info!("在庫確保中");
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    
    let reserved_items = 3;
    Span::current().set_attribute(KeyValue::new("inventory.reserved_items", reserved_items));
    info!(reserved_items, "在庫確保完了");
    Ok(())
}

#[instrument]
async fn process_payment(order_id: &str) -> Result<(), &'static str> {
    info!("決済処理中");
    tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
    
    let amount = 9999;
    Span::current().set_attribute(KeyValue::new("payment.amount", amount));
    Span::current().set_attribute(KeyValue::new("payment.method", "credit_card"));
    
    info!(amount, "決済処理完了");
    Ok(())
}

#[instrument]
async fn ship_order(order_id: &str) -> Result<(), &'static str> {
    info!("配送手配中");
    tokio::time::sleep(tokio::time::Duration::from_millis(80)).await;
    
    let tracking_number = "TRK123456789";
    Span::current().set_attribute(KeyValue::new("shipping.tracking_number", tracking_number));
    
    info!(tracking_number, "配送手配完了");
    Ok(())
}

カスタムSubscriberとメトリクス収集

use tracing::{info, warn, error, Subscriber, Event, Id, span};
use tracing_subscriber::{
    layer::{Context, SubscriberExt},
    Layer, Registry,
};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};

// カスタムメトリクス収集Layer
#[derive(Debug, Clone)]
struct MetricsLayer {
    metrics: Arc<Mutex<Metrics>>,
}

#[derive(Debug, Default)]
struct Metrics {
    log_counts: HashMap<String, u64>,
    error_counts: HashMap<String, u64>,
    span_durations: HashMap<String, Vec<Duration>>,
}

impl MetricsLayer {
    fn new() -> Self {
        Self {
            metrics: Arc::new(Mutex::new(Metrics::default())),
        }
    }

    fn print_metrics(&self) {
        let metrics = self.metrics.lock().unwrap();
        
        println!("\n=== メトリクス レポート ===");
        
        println!("ログカウント:");
        for (level, count) in &metrics.log_counts {
            println!("  {}: {}", level, count);
        }
        
        println!("エラーカウント:");
        for (target, count) in &metrics.error_counts {
            println!("  {}: {}", target, count);
        }
        
        println!("スパン実行時間 (平均):");
        for (name, durations) in &metrics.span_durations {
            if !durations.is_empty() {
                let avg = durations.iter().sum::<Duration>() / durations.len() as u32;
                println!("  {}: {:?}", name, avg);
            }
        }
    }
}

impl<S> Layer<S> for MetricsLayer
where
    S: Subscriber,
{
    fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
        let mut metrics = self.metrics.lock().unwrap();
        
        // ログレベル別カウント
        let level = event.metadata().level().to_string();
        *metrics.log_counts.entry(level.clone()).or_insert(0) += 1;
        
        // エラーレベルの場合、ターゲット別カウント
        if *event.metadata().level() == tracing::Level::ERROR {
            let target = event.metadata().target().to_string();
            *metrics.error_counts.entry(target).or_insert(0) += 1;
        }
    }

    fn on_enter(&self, id: &Id, ctx: Context<'_, S>) {
        // スパン開始時刻を記録
        if let Some(span) = ctx.span(id) {
            span.extensions_mut().insert(Instant::now());
        }
    }

    fn on_exit(&self, id: &Id, ctx: Context<'_, S>) {
        // スパン実行時間を計算
        if let Some(span) = ctx.span(id) {
            if let Some(start_time) = span.extensions().get::<Instant>() {
                let duration = start_time.elapsed();
                let span_name = span.name().to_string();
                
                let mut metrics = self.metrics.lock().unwrap();
                metrics.span_durations
                    .entry(span_name)
                    .or_insert_with(Vec::new)
                    .push(duration);
            }
        }
    }
}

#[tokio::main]
async fn main() {
    let metrics_layer = MetricsLayer::new();
    
    // カスタムSubscriberの設定
    let subscriber = Registry::default()
        .with(tracing_subscriber::fmt::layer())
        .with(metrics_layer.clone());

    tracing::subscriber::set_global_default(subscriber)
        .expect("Failed to set subscriber");

    info!("カスタムメトリクス収集開始");

    // 様々な処理の実行
    for i in 0..5 {
        process_request(i).await;
    }

    // 意図的なエラーの発生
    error!("テストエラー1");
    error!("テストエラー2");
    warn!("警告メッセージ");

    // メトリクスの表示
    metrics_layer.print_metrics();
}

#[tracing::instrument]
async fn process_request(request_id: usize) {
    info!(request_id, "リクエスト処理開始");
    
    // 処理時間のばらつきを作るためのランダムな遅延
    let delay = 50 + (request_id * 25);
    tokio::time::sleep(tokio::time::Duration::from_millis(delay as u64)).await;
    
    if request_id % 3 == 0 {
        error!("リクエスト処理エラー");
    } else {
        info!(request_id, "リクエスト処理完了");
    }
}

実用的なWebアプリケーション統合例

use tracing::{info, warn, error, instrument, Span};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use uuid::Uuid;
use std::collections::HashMap;

// リクエストIDの生成とコンテキスト管理
#[derive(Debug, Clone)]
struct RequestContext {
    request_id: String,
    user_id: Option<u64>,
    ip_address: String,
}

impl RequestContext {
    fn new(user_id: Option<u64>, ip_address: String) -> Self {
        Self {
            request_id: Uuid::new_v4().to_string(),
            user_id,
            ip_address,
        }
    }

    fn attach_to_span(&self) {
        let span = Span::current();
        span.record("request_id", &self.request_id);
        span.record("user_id", &self.user_id.unwrap_or(0));
        span.record("ip_address", &self.ip_address);
    }
}

#[tokio::main]
async fn main() {
    // 本格的なWebアプリケーション向けSubscriber設定
    tracing_subscriber::registry()
        .with(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| "myapp=debug,tower_http=debug".into()),
        )
        .with(tracing_subscriber::fmt::layer()
            .with_target(false)
            .with_timer(tracing_subscriber::fmt::time::UtcTime::rfc_3339())
            .with_level(true))
        .init();

    info!("Webアプリケーション開始");

    // 複数のリクエスト処理をシミュレート
    let requests = vec![
        (Some(12345), "192.168.1.100"),
        (None, "10.0.0.50"),
        (Some(67890), "203.0.113.25"),
    ];

    for (user_id, ip) in requests {
        let ctx = RequestContext::new(user_id, ip.to_string());
        handle_http_request(ctx).await;
    }
}

#[instrument(
    skip(ctx),
    fields(
        request_id = %ctx.request_id,
        user_id = ctx.user_id,
        ip_address = %ctx.ip_address
    )
)]
async fn handle_http_request(ctx: RequestContext) {
    info!("HTTPリクエスト受信");
    
    // リクエスト処理
    match process_business_logic(&ctx).await {
        Ok(result) => {
            info!(result = %result, "リクエスト処理成功");
        }
        Err(e) => {
            error!(error = %e, "リクエスト処理失敗");
        }
    }
}

#[instrument(skip(ctx))]
async fn process_business_logic(ctx: &RequestContext) -> Result<String, &'static str> {
    ctx.attach_to_span();
    
    info!("ビジネスロジック処理開始");
    
    // 認証チェック
    if let Some(user_id) = ctx.user_id {
        authenticate_user(user_id).await?;
    } else {
        warn!("未認証ユーザーからのリクエスト");
    }
    
    // データ処理
    let data = fetch_data().await?;
    let processed_data = transform_data(data).await?;
    
    info!("ビジネスロジック処理完了");
    Ok(processed_data)
}

#[instrument]
async fn authenticate_user(user_id: u64) -> Result<(), &'static str> {
    info!("ユーザー認証開始");
    
    // 認証処理のシミュレーション
    tokio::time::sleep(tokio::time::Duration::from_millis(30)).await;
    
    // ユーザー情報の記録
    Span::current().record("user_role", "premium");
    
    info!("ユーザー認証成功");
    Ok(())
}

#[instrument]
async fn fetch_data() -> Result<HashMap<String, String>, &'static str> {
    info!("データ取得開始");
    
    // データベースクエリのシミュレーション
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    
    let mut data = HashMap::new();
    data.insert("key1".to_string(), "value1".to_string());
    data.insert("key2".to_string(), "value2".to_string());
    
    info!(records_count = data.len(), "データ取得完了");
    Ok(data)
}

#[instrument(skip(data))]
async fn transform_data(data: HashMap<String, String>) -> Result<String, &'static str> {
    info!(input_size = data.len(), "データ変換開始");
    
    // データ変換処理のシミュレーション
    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
    
    let result = serde_json::to_string(&data).unwrap_or_else(|_| "{}".to_string());
    
    info!(output_size = result.len(), "データ変換完了");
    Ok(result)
}