OpenTelemetry コレクターにおけるコネクターは異なるパイプラインを接続する役割を担います。コネクターはパイプラインのレシーバーとエクスポーターとして動作します。
OpenTelemetry には、メトリック、トレース、ログの 3 つの形式があり、これらの形式はそれぞれ別のパイプラインで処理されます。ある形式のデータを別の形式に変換し、あたかも 1 つのパイプライン上でデータを処理したい場合にコネクターを使用します。例えば、あるコネクターはトレースのパイプラインのエクスポーターとメトリックのパイプラインのレシーバーとして動作します。このような場合、コネクターはトレースをメトリックに変換して受け渡す役割を担います。

この記事では Exceptions Connector を使用して、トレーシングのスパンに紐付けられたアプリケーションの例外 をメトリックに変換し Mackerel に送信する方法をご紹介します。
- OpenTelemetry におけるトレースの概要
- Node.js アプリケーションをトレーシングする
- Exceptions Connector を同梱したカスタム OpenTelemetry コレクターを作成する
- OpenTelemetry コレクターの設定
- Mackerel でメトリックを確認する
- まとめ
- 「ラベル付きメトリック機能」ベータ版テストの参加者募集中
OpenTelemetry におけるトレースの概要
例として、Node.js のアプリケーションにトレーシングを導入してみましょう。OpenTelemetry においてトレースを生成するには、以下の 2 つの方法があります。
- 自動計装:アプリケーションのコードを変更することなくトレースを生成する
- 手動計装:トレーシングを行いたい箇所に自分でコードを追加する
多くの言語やフレームワークでは、自動計装を行うためのライブラリが提供されています。例えば Node.js では opentelemetry/sdk-node というライブラリをセットアップするだけで計装できます。この方法は既存のアプリケーションに変更を加えずに容易に導入できるので、トレーシングを導入する最初のステップとして最適です。
手動計装は、開発者自身の手によってアプリケーションコードを修正してトレーシングを行う方法です。スパンに追加の属性を設定したり、新しいスパンを生成したりといった、自動計装では手が届かない詳細なカスタマイズを行いたい場合に使用します。また、学習目的として実際のトレーシングが行われているときに何が発生しているのかを確認するために、すべてを手動計装として実装してみるのもよいでしょう。
今回は手動計装を行う方法を紹介します。まずはトレーシングを行うために必要なパッケージをインストールします。
npm install @opentelemetry/api @opentelemetry/tracing @opentelemetry/instrumentation @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/exporter-trace-otlp-grpc
tracer.js
という名前でファイルを作成して、以下のコードを記述します。
import { NodeTracerProvider } from "@opentelemetry/node"; import { registerInstrumentations } from "@opentelemetry/instrumentation"; import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc"; import { diag, DiagConsoleLogger, DiagLogLevel, trace, } from "@opentelemetry/api"; import { SimpleSpanProcessor } from "@opentelemetry/tracing"; import { Resource } from "@opentelemetry/resources"; import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions"; // OpenTelemetry に関するデバッグログを有効にする diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG); // TracerProvider を初期化する const provider = new NodeTracerProvider({ // Resource はアプリケーションが実行される環境の属性の集合を表す resource: new Resource({ // ここではサービス名を指定している // サービス名により、どのアプリケーションからのトレースなのかを識別できる [SemanticResourceAttributes.SERVICE_NAME]: "express-app", }), }); registerInstrumentations({ tracerProvider: provider, }); // エクスポーターを設定 // エクスポーターはトレースデータをどこに送信するかを決定する // OTLPTraceExporter は OpenTelemetry Protocol でデータを送信するエクスポーター // OpenTelemetry コレクターにデータを送信する const exporter = new OTLPTraceExporter(); // スパンがどのように処理されるかどうかの設定 // エクスポーターによりスパンが外部へ送信される provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); // TracerProvider を登録する provider.register(); // tracer を取得する export const tracer = trace.getTracer("express-app");
OpenTelemetry におけるトレースは、以下の 3 つのコンポーネントから構成されています。
- TracerProvider:トレースのエントリーポイント
- Tracer:スパンを生成する
- Span:トレース内の 1 つのオペレーション
TracerProvider
Trace Provider はトレース API のエントリーポイントです。ステートフルなオブジェクトとして、設定を保持するのが役割となります。provider.getTracer()
メソッドを呼び出すことで Tracer を生成します。
Tracer
Tracer は Span を生成するためのオブジェクトです。tracer.startSpan()
メソッドを呼び出すことで Span を開始します。
Span
Span はトレース内の 1 つのオペレーションを表すオブジェクトです。Span は以下のような構造を持ちます。
{ attributes: { 'http.url': 'http://localhost:3000/', 'http.host': 'localhost:3000', 'net.host.name': 'localhost', 'http.method': 'GET', 'http.scheme': 'http', 'http.target': '/', }, links: [], events: [], status: { code: 0 }, endTime: [ 1694320903, 7013875 ], _duration: [ 1, 15013875 ], name: 'GET', context: { trace_id: "0x5b8aa5a2d2c872e8321cf37308d69df2", span_id: "0x051581bf3cb55c13" }, parentSpanId: undefined, kind: 1, startTime: [ 1694320901, 992000000 ], resource: Resource { _attributes: [Object], asyncAttributesPending: false, _syncAttributes: [Object], _asyncAttributesPromise: [Promise] }, }
スパンには traceId
と parentId
が含まれています。traceId
とは一連のクライアント - サーバー間のリクエストを識別するための ID です。同じ traceId
を持つスパンは同じトレースに属しているとみなされます。parentId
はそのエンドポイントを呼び出したスパンの ID です。parentId
が undefined
の場合には、そのスパンがトレースのルートスパンであることを表します。
また、スパンには通常属性やイベントが含まれており、オペレーションに関する情報を伝えることができます。
Node.js アプリケーションをトレーシングする
それでは、先ほど作成した tracer.js
を使用して Node.js アプリケーションをトレーシングしてみましょう。例として、Express を使用したアプリケーションを作成します。
npm install express
server.js
という名前でファイルを作成して、以下のコードを記述します。
import express from "express"; import { tracer } from "./tracer.js"; import { ROOT_CONTEXT, context, trace, SpanKind, SpanStatusCode, } from "@opentelemetry/api"; import { SemanticAttributes } from "@opentelemetry/semantic-conventions"; const app = express(); app.get("/", async (req, res) => { // トレースを開始する const span = tracer.startSpan("GET /", { kind: SpanKind.SERVER, }); // スパンに属性を設定する span.setAttribute(SemanticAttributes.HTTP_METHOD, "GET"); span.setAttribute(SemanticAttributes.HTTP_URL, "http://localhost:3000/"); span.setAttribute(SemanticAttributes.HTTP_TARGET, "/"); span.setAttribute(SemanticAttributes.HTTP_SCHEME, "http"); span.setAttribute(SemanticAttributes.HTTP_HOST, "localhost:3000"); try { const result = await fetch("http://localhost:3001/price"); const json = await result.json(); if (!result.ok) { throw new Error(json.message); } span.setStatus({ code: SpanStatusCode.OK }); res.json({ message: "Hello World!", price: json.price }); } catch (e) { // スパンに例外を設定する span.recordException(e); span.setStatus({ code: SpanStatusCode.ERROR }); res.status(500).json({ message: e.message }); } finally { // スパンを終了する span.end(); } }); app.listen(3000, () => { console.log("Server is running on port 3000"); });
/
ルートにリクエストが送信されたときに、tracer.startSpan()
メソッドでトレーシングを開始しています。第 1 引数でスパンの名前を設定しています。続いて、span.setAttribute()
でスパンに対する属性を指定しています。HTTP や データベースなど、よく使われる属性のキー名は Trace Semantic Conventions で定義されており、この規則に従うことが推奨されています。そうすることでキー名を統一することができ、複数の環境から収集されたテレメトリーデータを関連付けしやすくなります。
このエンドポイントでは http://localhost:3001/price
というエンドポイントにリクエストを送信しています。このリクエストに対するレスポンスが 200 以外の場合には、例外を投げています。catch
句で行っている span.recordException(e)
が今回の記事の主題となるスパンに例外を設定する処理です。span
API が提供する recordException
メソッドを呼び出すことで、例外が Event としてスパンに記録されます。
この記録された例外が、後ほど OpenTelemetry コレクターの Exceptions Connector によりメトリックに変換され Mackerel に送信されます。
ここまでコードを記述したら、以下のコマンドでアプリケーションを起動します。
node server.js
試しに http://localhost:3000/
にリクエストを送信してみましょう。以下のようなログが出力されます。
items to be sent [ Span { attributes: { 'http.method': 'GET', 'http.url': 'http://localhost:3000/', 'http.target': '/', 'http.scheme': 'http', 'http.host': 'localhost:3000' }, links: [], events: [ [Object], [Object] ], status: { code: 2 }, endTime: [ 1694327822, 192952075 ], _ended: true, _duration: [ 0, 17833209 ], name: 'GET /', _spanContext: { traceId: 'ab0fdc95c2e142508191cfd0870ffb88', spanId: 'a0578afe41aae614', traceFlags: 1, traceState: undefined }, parentSpanId: undefined, kind: 1, startTime: [ 1694327822, 175118866 ], resource: Resource { attributes: [Object] }, instrumentationLibrary: { name: '', version: undefined }, _spanLimits: { attributeCountLimit: 128, linkCountLimit: 128, eventCountLimit: 128 }, _spanProcessor: MultiSpanProcessor { _spanProcessors: [Array] } } ] Service request { resourceSpans: [ { resource: [Object], scopeSpans: [Array], schemaUrl: undefined } ] } {"stack":"Error: 14 UNAVAILABLE: No connection established\n
items to sent
の後に出力されているのが、エクスポーターにより外部へ送信される予定のスパンです。実際にアプリケーションコード内で設定した属性や名前が出力されていることが確認できます。
その後のログでは、{"stack":"Error: 14 UNAVAILABLE: No connection established\n
というエラーが出力されています。これは、テレメトリーデータを送信するための OpenTelemetry コレクターをまだ起動していないのが原因です。続いて、OpenTelemetry コレクターを起動していきます。
Exceptions Connector を同梱したカスタム OpenTelemetry コレクターを作成する
Exceptions Connector とは、トレースのスパンに紐付けられた例外をメトリックまたはログに変換するためのコネクターーです。例外が発生した回数を exceptions
という名前のメトリックとして Mackerel に送信できます。
Exceptions Connector は、2023 年 9 月現在 OpenTelemetry Collector Contrib には含まれていません。そのため、Exceptions Connector を使用したい場合には、自身で OpenTelemetry コレクターをビルドする必要があります。
ビルダーをインストールする
まずは ocb(OpenTelemetry Collector Builder)というツールをインストールします。
go install go.opentelemetry.io/collector/cmd/builder@latest
インストールが完了したら、以下のコマンドで確認しましょう。
builder version ocb version dev
マニフェストファイルを作成する
マニフェストファイルは、ビルダーに対してどのコンポーネントを含めるかを指定するためのファイルです。manifest.yaml
という名前で以下の内容を保存してください。
dist: name: otelcol-dev description: Basic OTel Collector distribution for Developers output_path: ./otelcol-dev otelcol_version: 0.84.0 exporters: - gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.84.0 receivers: - gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.84.0 connectors: - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/connector/exceptionsconnector v0.84.0
exporters
, processors
, receivers
, connectors
には、それぞれビルダーに含めるコンポーネントを指定します。dist
には、ビルダーが生成するバイナリの名前やバージョンを指定します。ここでは、以下の 3 つのコンポーネントを指定しています。
- OTLP Exporter:Mackerel に OTLP でデータを送信するエクスポーター
- OTLP Receiver:Node.js アプリケーションから送信されたデータを OTLP で受け取るレシーバー
- Exceptions Connector:スパンに紐付けられた例外をメトリックに変換するコネクター
ビルド
マニフェストファイルを作成したら、以下のコマンドでビルドを実行します。
builder --config manifest.yaml
ビルドが完了すると、./otelcol-dev
という名前のディレクトリが生成されます。
OpenTelemetry コレクターの設定
OpenTelemetry コレクターの設定ファイルを作成します。otel-collector^config.yaml
という名前でファイルを作成し、先ほど用意した ./otelcol-dev
ディレクトリに保存します。
receivers: otlp: protocols: grpc: connectors: exceptions: exporters: otlp/mackerel: endpoint: otlp.mackerelio.com:4317 compression: gzip headers: Mackerel-Api-Key: ${env:MACKEREL_APIKEY} service: pipelines: traces: receivers: [otlp] exporters: [exceptions] metrics: receivers: [exceptions, otlp] exporters: [otlp/mackerel]
receivers には OpenTelemetry Collector が受け取るデータの形式を設定します。ここでは otlp レシーバー を使用して、OTLP で送信されるデータを受け取るように設定しています。先ほどのアプリケーションでは、 OTLPLogExporter を使用して OTLP でデータを送信するように設定していました。
exporters
として otlp/mackerel
を設定し、メトリックが Mackerel に送信されるための設定を行っています。エンドポイントは otlp.mackerelio.com:4317
で固定となります。headers/Mackerel-Api-Key
にはメトリックを送信したい Mackerel のオーガニゼーションの API キーを設定してください。この API キーには書き込み権限が必要です。
connectors が今回の構成の肝です。ここでは Exceptions Connector を使用しています。これは、トレース形式のデータを受け取り、その中で例外に関連するスパンをメトリックに変換して別のパイプラインに受け渡すために使用します。
service にはパイプラインの設定を記述します。ここでは traces と metrics の 2 つのパイプラインを設定しています。冒頭でも説明したとおり、コネクター を使用して traces と metrics の 2 つのパイプラインを接続します。これにより、最終的にメトリックの形式として Mackerel に送信されます。
以下のコマンドで OpenTelemetry Collector を起動します。
cd otelcol-dev go run . --config otelcol-dev/otel-collector-config.yaml
Mackerel でメトリックを確認する
起動が完了したら、http://localhost:3000 にいくつかリクエストを送信してみてください。http://localhost:3001/price エンドポイントは用意していないので、例外が発生しているはずです。
Mackerel のダッシュボードで例外がメトリックとして収集されていることを確認してみましょう。以下の URL からカスタムダッシュボードの作成画面に移動します。
https://mackerel.io/my/dashboards/-/new#period=30m
グラフウィジェットを選択して、「グラフのタイプ」で「クエリグラフ」を選択します。PromQL を記述するエディタが表示されるので、exceptions{service.name="express-app"}
というクエリを入力します。exceptions
は Exceptions Connector により生成されたメトリックの名前です。service.name="express-app"}
という絞り込み条件を指定することで、先ほど作成した Node.js アプリケーションのメトリックのみを表示できます。
まとめ
今回の内容をまとめます。
- OpenTelemetry コレクターのコネクターは、異なるパイプラインを接続するための機能
- 1 つのパイプラインでは、コネクターをエクスポーターとして、もう 1 のパイプラインで同じコネクターをレシーバーとして利用することで、データを別のパイプラインに流すことができる
- 例外に関連するスパンをメトリックに変換するためには、Exceptions Connector を使用する
span.recordException(e)
でスパンに例外を設定できる
「ラベル付きメトリック機能」ベータ版テストの参加者募集中
現在、「ラベル付きメトリック機能」ベータ版テストの参加者を募集しています。下記のフォームよりベータ版テストへの参加お申し込みをいただけます。