AI

OpenTelemetry完全ガイド2026:分散トレーシング・メトリクス・ログの統合可観測性をOSSで実現

オープンソースラボ編集部2026年6月13日

OpenTelemetry完全ガイド2026:分散トレーシング・メトリクス・ログの統合可観測性をOSSで実現

マイクロサービスが増えると「本番でエラーが起きたが、どのサービスで遅延が発生したか分からない」「APIのレイテンシーが突然上がったが原因が追えない」問題が深刻になります。OpenTelemetry(OTel)はCNCF(Cloud Native Computing Foundation)のプロジェクトで、分散トレーシング・メトリクス・ログを統一的に収集してDatadog・Grafana・Jaeger・OpenSearchに送る業界標準OSSです。

OpenTelemetryとは

OpenTelemetryはクラウドネイティブアプリケーションの「可観測性(Observability)」を実現するためのSDK・API・コレクター(収集エージェント)のOSSです。Traces・Metrics・Logsの3つのシグナルを統一的に扱い、特定のベンダーに依存しない形で監視データを収集できます。

3つのシグナル:

  • Traces: リクエスト1件がどのサービス・関数を通り、どこで何秒かかったかの追跡(分散トレーシング)
  • Metrics: CPU・メモリ・リクエスト数・エラー率等の数値時系列データ
  • Logs: アプリケーションが出力するテキストの記録(構造化ログ)

OpenTelemetryを使う理由

  • ベンダーロックイン解消: Datadog・New Relic・Grafana等をバックエンドとして自由に切り替えられる
  • 自動計装: Node.js・Python・Java等のSDKが一般的なライブラリ(Express・Django・Spring等)のトレースを自動生成
  • 標準化: CNCF卒業プロジェクト。AWS・Google Cloud・Azure・Grafana・Datadogが公式サポート
  • コスト最適化: オープンなプロトコルで複数のバックエンドに送れるため、安いバックエンドに切り替え可能
  • OSSスタック: Jaeger(トレース)+ Prometheus(メトリクス)+ Loki(ログ)と組み合わせてフルOSSの可観測性スタックが構築できる

Node.js / Next.jsでのOpenTelemetry実装

// Next.jsにOpenTelemetryを統合する(自動計装)
// npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node
// npm install @opentelemetry/exporter-otlp-grpc @opentelemetry/resources
// npm install @opentelemetry/semantic-conventions

// instrumentation.ts(Next.js 13+ App Routerのinstrumentation hook)
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { Resource } from '@opentelemetry/resources';
import { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';

export function register() {
  const sdk = new NodeSDK({
    resource: new Resource({
      [SEMRESATTRS_SERVICE_NAME]: 'my-nextjs-app',
      [SEMRESATTRS_SERVICE_VERSION]: process.env.npm_package_version,
    }),
    // OpenTelemetry Collector(またはTempoやJaeger)にトレースを送信
    traceExporter: new OTLPTraceExporter({
      url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://otel-collector:4318/v1/traces',
    }),
    metricReader: new PeriodicExportingMetricReader({
      exporter: new OTLPMetricExporter({
        url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://otel-collector:4318/v1/metrics',
      }),
      exportIntervalMillis: 30000,  // 30秒ごとにメトリクスをエクスポート
    }),
    // Node.js標準ライブラリ・Express・HTTPクライアント・DBを自動計装
    instrumentations: [
      getNodeAutoInstrumentations({
        '@opentelemetry/instrumentation-http': { enabled: true },
        '@opentelemetry/instrumentation-dns': { enabled: true },
        '@opentelemetry/instrumentation-pg': { enabled: true },  // PostgreSQL
        '@opentelemetry/instrumentation-redis': { enabled: true },
        '@opentelemetry/instrumentation-fetch': { enabled: true },
      }),
    ],
  });

  sdk.start();
  process.on('SIGTERM', () => sdk.shutdown());
}
// カスタムスパン・メトリクス・ログを手動で計装する
import { trace, metrics, context, SpanStatusCode } from '@opentelemetry/api';

const tracer = trace.getTracer('my-app', '1.0.0');
const meter = metrics.getMeter('my-app', '1.0.0');

// カスタムメトリクス(ビジネス指標)
const orderCounter = meter.createCounter('orders.created', {
  description: '作成された注文数',
  unit: '{orders}',
});

const paymentHistogram = meter.createHistogram('payment.duration', {
  description: '決済処理の所要時間',
  unit: 'ms',
});

// ビジネスロジックのトレース+メトリクス計測例
async function processOrder(orderId: string, items: OrderItem[]) {
  return tracer.startActiveSpan('process-order', async (span) => {
    span.setAttribute('order.id', orderId);
    span.setAttribute('order.item_count', items.length);

    try {
      // 在庫確認(子スパンとして自動ネスト)
      const inventoryOk = await tracer.startActiveSpan('check-inventory', async (childSpan) => {
        const result = await checkInventory(items);
        childSpan.setAttribute('inventory.available', result.available);
        childSpan.end();
        return result.available;
      });

      if (!inventoryOk) {
        span.setStatus({ code: SpanStatusCode.ERROR, message: '在庫不足' });
        span.end();
        return { success: false, reason: 'out_of_stock' };
      }

      // 決済処理の時間計測
      const paymentStart = Date.now();
      await processPayment(orderId);
      paymentHistogram.record(Date.now() - paymentStart, { 'payment.method': 'credit_card' });

      // 成功メトリクスを記録
      orderCounter.add(1, { 'order.status': 'success', 'order.region': 'jp' });
      span.setStatus({ code: SpanStatusCode.OK });
      return { success: true };

    } catch (error) {
      span.recordException(error as Error);
      span.setStatus({ code: SpanStatusCode.ERROR });
      orderCounter.add(1, { 'order.status': 'error' });
      throw error;
    } finally {
      span.end();
    }
  });
}

OpenTelemetry Collectorのセットアップ

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 10s
    send_batch_size: 1024

  # 機密情報をマスク
  attributes:
    actions:
      - key: http.request.header.authorization
        action: delete
      - key: user.email
        action: hash

exporters:
  # Grafana Tempoにトレースを送信(無料の自己ホスト版)
  otlp/tempo:
    endpoint: http://tempo:4317
    tls:
      insecure: true

  # Prometheusにメトリクスをエクスポート
  prometheus:
    endpoint: "0.0.0.0:8889"

  # Lokiにログを送信
  loki:
    endpoint: http://loki:3100/loki/api/v1/push

  # デバッグ用(コンソール出力)
  debug:
    verbosity: detailed

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch, attributes]
      exporters: [otlp/tempo]

    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus]

    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [loki]
# docker-compose.yml(OpenTelemetry + Grafana Observabilityスタック)
version: "3"
services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    restart: always
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - "4317:4317"   # OTLP gRPC
      - "4318:4318"   # OTLP HTTP
      - "8889:8889"   # Prometheus metrics export

  # 分散トレーシング(Grafana Tempo)
  tempo:
    image: grafana/tempo:latest
    restart: always
    command: ["-config.file=/etc/tempo.yaml"]
    volumes:
      - ./tempo.yaml:/etc/tempo.yaml
      - tempo_data:/var/tempo

  # ログ管理(Grafana Loki)
  loki:
    image: grafana/loki:latest
    restart: always
    command: -config.file=/etc/loki/local-config.yaml
    volumes:
      - loki_data:/loki

  # メトリクス(Prometheus)
  prometheus:
    image: prom/prometheus:latest
    restart: always
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prom_data:/prometheus

  # 可視化(Grafana)
  grafana:
    image: grafana/grafana:latest
    restart: always
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
      GF_FEATURE_TOGGLES_ENABLE: traceqlEditor
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning

volumes:
  tempo_data:
  loki_data:
  prom_data:
  grafana_data:

Pythonでの実装

# FastAPI + OpenTelemetry(Python)
# pip install opentelemetry-sdk opentelemetry-instrumentation-fastapi
# pip install opentelemetry-exporter-otlp-proto-http

from opentelemetry import trace, metrics
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
from fastapi import FastAPI
import time

# トレーサープロバイダーの設定
provider = TracerProvider()
provider.add_span_processor(
    BatchSpanProcessor(OTLPSpanExporter(endpoint="http://otel-collector:4318/v1/traces"))
)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)

app = FastAPI()
FastAPIInstrumentor.instrument_app(app)  # FastAPIを自動計装
SQLAlchemyInstrumentor().instrument()    # SQLAlchemyを自動計装

# カスタムスパンの例
@app.get("/orders/{order_id}")
async def get_order(order_id: str):
    with tracer.start_as_current_span("get-order") as span:
        span.set_attribute("order.id", order_id)

        start = time.time()
        order = await db.fetch_order(order_id)
        span.set_attribute("db.query_time_ms", (time.time() - start) * 1000)

        if not order:
            span.set_status(trace.StatusCode.ERROR, "Order not found")
            return {"error": "not found"}

        return order

バックエンド選定比較表

バックエンドトレースメトリクスログ料金
Grafana Cloud(OSS)✅ Tempo✅ Prometheus✅ Loki無料枠あり
Jaeger(セルフホスト)無料
Datadog$15/ホスト〜
New Relic100GB/月無料
OpenSearch無料(セルフホスト)

OpenTelemetryの設定はDevOpsカテゴリ/categories/devopsのKubernetesモニタリングと組み合わせると効果的です。LLMのトレーシングはLangfuseをOTelバックエンドとして使うパターンをLLMツールカテゴリ/categories/llm-toolsにまとめています。

FAQ

Q. OpenTelemetryとOpenTracingの違いは何ですか?どちらを使うべきですか?

A. OpenTelemetry(OTel)が新標準です。OpenTracing(2016年〜)はトレーシングのみを標準化した先行プロジェクトで、現在はArchived(メンテナンス終了)です。OpenTelemetryは2019年にOpenTracing + OpenCensusの後継として誕生し、Traces・Metrics・Logsの3シグナルを統一的に扱います。既存コードでJaegerClient・Zipkin等のOpenTracingライブラリを使っている場合、opentelemetry-shim-opentracingパッケージで段階的に移行できます。新規プロジェクトでは迷わずOpenTelemetryを使ってください。

Q. Next.jsのEdge RuntimeでOpenTelemetryは動作しますか?

A. Edge Runtimeは制限されたWebAssembly環境のため、Node.jsのOpenTelemetry SDKは直接動作しません。対応策: ①App Routerのinstrumentation.tsをNode.jsランタイムに限定する: Edge Runtimeを使うルートはトレースの対象外にする②Vercel OTel対応の@vercel/otelパッケージを使う: Vercelはエッジ向けの軽量OTel実装を提供しており、Vercel環境ではEdge RuntimeでもBasicなトレーシングが可能③フロントエンド側はBrowser SDK使用: @opentelemetry/sdk-trace-webでブラウザのリアルユーザーモニタリング(RUM)を別途実装。現時点ではEdge RuntimeのOTelサポートは発展途上で、完全なサポートは2026〜2027年の見込みです。

Q. Kubernetes環境でOpenTelemetryを全サービスに適用する最も効率的な方法は?

A. OpenTelemetry Operator for Kubernetesを使うのが最も効率的です。

# OpenTelemetry Operatorをインストール
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm install opentelemetry-operator open-telemetry/opentelemetry-operator   -n opentelemetry-operator-system --create-namespace   --set "manager.collectorImage.repository=otel/opentelemetry-collector-contrib"

# Instrumentationリソースを定義(自動計装)
kubectl apply -f - <<EOF
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: my-instrumentation
  namespace: default
spec:
  exporter:
    endpoint: http://otel-collector:4317
  propagators:
    - tracecontext
    - baggage
  sampler:
    type: parentbased_traceidratio
    argument: "0.1"  # 10%サンプリング(本番負荷軽減)
  nodejs:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:latest
  python:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:latest
  java:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:latest
EOF

# Podに自動計装を注入(アノテーション追加のみ)
kubectl patch deployment my-nodejs-app -p '{
  "spec": {"template": {"metadata": {"annotations": {
    "instrumentation.opentelemetry.io/inject-nodejs": "true"
  }}}}
}'

Podが再起動すると自動的にOTel SDKがサイドカーとして注入され、コードを一行も変更せずにトレーシングが開始されます。

Q. OpenTelemetryのサンプリング戦略はどうするべきですか?

A. 本番環境では全トレースを記録するとストレージコストが高くなるため、サンプリングが重要です。推奨戦略:

// Head-based sampling(リクエスト開始時に決定)
// 1%サンプリング(高トラフィックAPI向け)
import { TraceIdRatioBasedSampler, ParentBasedSampler } from '@opentelemetry/sdk-trace-node';

const sdk = new NodeSDK({
  sampler: new ParentBasedSampler({
    // 親スパンがある場合は親の判断に従い、なければ1%サンプリング
    root: new TraceIdRatioBasedSampler(0.01),
  }),
  // ...
});

// Tail-based sampling(OpenTelemetry Collectorで実装)
// エラー・高レイテンシーのトレースは100%保持、正常は1%
# otel-collector-config.yaml
processors:
  tail_sampling:
    decision_wait: 10s
    num_traces: 100
    expected_new_traces_per_sec: 10
    policies:
      - name: errors-policy
        type: status_code
        status_code: {status_codes: [ERROR]}
      - name: slow-traces-policy
        type: latency
        latency: {threshold_ms: 1000}
      - name: random-sampling-policy
        type: probabilistic
        probabilistic: {sampling_percentage: 1}

エラートレースと1秒超のスロートレースは100%保持、正常リクエストは1%のみ保持する設定が、コストと可視性のバランスが良い構成です。

まとめ

ユースケース推奨構成
フルOSSスタックOTel + Tempo + Prometheus + Loki + Grafana
最小コストOTel + Jaeger(トレースのみ)
エンタープライズOTel → Datadog/New Relic
Kubernetes自動計装OTel Operator

関連外部リソース

他の記事も読む

Let's Build Together

OSS導入、自社だけで悩まない。

ツール選定から構築・運用・AI活用まで、オープンソースラボ運営元のClasslessが伴走します。初回のご相談は無料です。