Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第4章 実践アプリケーション実装:Python と C# による計測

本章では、第 3 章で構築したフルスタック基盤(Collector)へ向けて、Python と C# のアプリケーションから「トレース」と「メトリクス」の双方を計測・送信する完全な実装例を解説します。

Python によるトレースとメトリクスの同時計測

OpenTelemetry の Python SDK を使用し、注文処理のトレース(Span)と、注文数・処理時間のメトリクス(Counter、Histogram)を計測します。

ライブラリの準備

pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp

実装コード(app_order.py)

import random
import time
from opentelemetry import metrics, trace
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

# 共通のリソース(サービス情報)を定義
resource = Resource.create(
    attributes={
        "service.name": "order-service-python",
        "service.version": "2.0.0",
        "deployment.environment": "production",
    }
)

# 1. トレース設定 (TracerProvider)
trace_provider = TracerProvider(resource=resource)
trace_processor = BatchSpanProcessor(
    OTLPSpanExporter(endpoint="localhost:4317", insecure=True)
)
trace_provider.add_span_processor(trace_processor)
trace.set_tracer_provider(trace_provider)
tracer = trace.get_tracer("order.tracer")

# 2. メトリクス設定 (MeterProvider)
# 5秒ごとに Collector へメトリクスを送信
metric_reader = PeriodicExportingMetricReader(
    OTLPMetricExporter(endpoint="localhost:4317", insecure=True),
    export_interval_millis=5000,
)
meter_provider = MeterProvider(resource=resource, metric_readers=[metric_reader])
metrics.set_meter_provider(meter_provider)
meter = metrics.get_meter("order.meter")

# 計測器(Instruments)の定義
order_counter = meter.create_counter(
    name="order_requests_total",
    description="注文リクエストの総数",
    unit="1",
)
order_duration_histogram = meter.create_histogram(
    name="order_duration_seconds",
    description="注文処理の所要時間(秒)",
    unit="s",
)


def handle_order(order_id: str, item_category: str):
    start_time = time.time()

    with tracer.start_as_current_span("handle_order") as span:
        span.set_attribute("order.id", order_id)
        span.set_attribute("order.category", item_category)

        # 疑似的な業務処理と子スパン
        with tracer.start_as_current_span("check_stock"):
            time.sleep(random.uniform(0.05, 0.15))

        with tracer.start_as_current_span("record_order"):
            time.sleep(random.uniform(0.05, 0.10))

        # 処理結果のメトリクスを記録
        elapsed = time.time() - start_time
        span.set_attribute("order.duration", elapsed)

        order_counter.add(1, {"category": item_category, "status": "success"})
        order_duration_histogram.record(elapsed, {"category": item_category})

    print(f"[Python] 注文完了: {order_id} ({item_category}) - {elapsed:.3f}s")


if __name__ == "__main__":
    print("Python 注文サービスを開始します(10回のリクエストをシミュレート)...")
    categories = ["electronics", "books", "clothing"]

    for i in range(1, 11):
        handle_order(f"ORD-PY-{i:03d}", random.choice(categories))
        time.sleep(0.5)

    print("テレメトリデータを送信中...")
    # プロバイダーをシャットダウンして未送信データを Collector へフラッシュ
    meter_provider.shutdown()
    trace_provider.shutdown()
    print("Python アプリケーションが正常終了しました。")

C# によるトレースとメトリクスの同時計測

.NET では、標準の ActivitySource(トレース)と Meter(メトリクス)を使用し、OpenTelemetry SDK 経由で Collector へ送信します。

ライブラリの準備

dotnet new console -n OtelFullstackCSharp
cd OtelFullstackCSharp
dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol

実装コード(Program.cs)

using System.Diagnostics;
using System.Diagnostics.Metrics;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

const string serviceName = "payment-service-csharp";
const string serviceVersion = "2.0.0";

// 1. .NET 組み込みの ActivitySource(トレース)と Meter(メトリクス)を宣言
var activitySource = new ActivitySource(serviceName, serviceVersion);
var meter = new Meter(serviceName, serviceVersion);

// 計測器(Instruments)の作成
var transactionCounter = meter.CreateCounter<long>(
    name: "payment_transactions_total",
    unit: "1",
    description: "決済トランザクションの総数");

var transactionAmountHistogram = meter.CreateHistogram<double>(
    name: "payment_amount_yen",
    unit: "JPY",
    description: "決済金額の分布");

// 2. OpenTelemetry TracerProvider の構築
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
    .ConfigureResource(r => r.AddService(serviceName, serviceVersion: serviceVersion))
    .AddSource(serviceName)
    .AddOtlpExporter(opt =>
    {
        opt.Endpoint = new Uri("http://localhost:4317");
        opt.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.Grpc;
    })
    .Build();

// 3. OpenTelemetry MeterProvider の構築
using var meterProvider = Sdk.CreateMeterProviderBuilder()
    .ConfigureResource(r => r.AddService(serviceName, serviceVersion: serviceVersion))
    .AddMeter(serviceName)
    .AddOtlpExporter(opt =>
    {
        opt.Endpoint = new Uri("http://localhost:4317");
        opt.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.Grpc;
    })
    .Build();

Console.WriteLine("C# 決済サービスを開始します(10件の決済をシミュレート)...");

var random = new Random();
var methods = new[] { "credit_card", "qr_code", "bank_transfer" };

for (int i = 1; i <= 10; i++)
{
    ExecutePayment($"PAY-CS-{i:03d}", methods[random.Next(methods.Length)], random.Next(1000, 15000));
    Thread.Sleep(500);
}

Console.WriteLine("C# アプリケーションが正常終了しました。");

void ExecutePayment(string transactionId, string method, double amount)
{
    // トレーススパンの作成
    using (var activity = activitySource.StartActivity("ExecutePayment"))
    {
        activity?.SetTag("payment.transaction_id", transactionId);
        activity?.SetTag("payment.method", method);

        using (var authActivity = activitySource.StartActivity("ValidateAccount"))
        {
            Thread.Sleep(random.Next(40, 100));
            authActivity?.AddEvent(new ActivityEvent("口座確認完了"));
        }

        using (var transferActivity = activitySource.StartActivity("ExecuteTransfer"))
        {
            Thread.Sleep(random.Next(60, 120));
        }

        // メトリクスの記録
        var tags = new TagList { { "method", method }, { "status", "success" } };
        transactionCounter.Add(1, tags);
        transactionAmountHistogram.Record(amount, tags);
    }

    Console.WriteLine($"[C#] 決済完了: {transactionId} ({method}) - ¥{amount:N0}");
}

可視化とデータ確認

両アプリケーションを実行した後、バックエンドツールで収集結果を確認します。

1. Prometheus でメトリクスを確認

ブラウザで http://localhost:9090 にアクセスし、上部の検索欄に以下のクエリを入力して Execute をクリックします。

  • Python の注文総数: app_order_requests_total
  • C# の決済トランザクション総数: app_payment_transactions_total
  • 平均処理時間の計算: rate(app_order_duration_seconds_sum[1m]) / rate(app_order_duration_seconds_count[1m])

ラベル(category や method)ごとに分類された時系列データが取得できていることを確認します。

2. Jaeger v2 でトレースを確認

ブラウザで http://localhost:16686 にアクセスします。

  • Service に order-service-python および payment-service-csharp が表示されます。
  • トレースを検索すると、各関数の呼び出し関係(handle_order -> check_stock -> record_order など)がガントチャート形式で正確に可視化されます。

3. Grafana でダッシュボード作成

ブラウザで http://localhost:3000 にアクセスします。

  1. 左メニューの Explore を開きます。
  2. データソースとして Prometheus を選択し、app_order_requests_total をグラフ描画します。
  3. 次にデータソースを Jaeger に切り替え、Trace ID で検索を行うことで、メトリクスのスパイク発生箇所から該当トレースへ直接深掘り(ドリルダウン)することが可能になります。