OpenTelemetryのインメモリエクスポータについて説明をします!!また、例としてopentelemetry-jsの InMemorySpanExporter
の使用方法をご紹介します!!
インメモリエクスポータとは何か
ローカルのメモリにテレメトリデータを蓄積し、それをテストできるエクスポータです。OpenTelemetryを利用した際の単体テストなどで役に立ちます。
しっかりとOpenTelemetryの仕様にも記載があり、各言語で用意されています。
The SDK implementation should include the following exporters:
- logs, metrics, trace
- ...
- In-memory (mock) exporter that accumulates telemetry data in the local memory and allows to inspect it (useful for e.g. unit tests).
各言語での2024/10/31における対応状況のスクショを以下に記載します。 +
がサポートされていることを示すので、ほぼ全ての言語で実装されいていることがわかります。
例えば opentelemetry-jsでは InMemorySpanExporter
、InMemoryMetricExporter
、InMemoryLogRecordExporter
が実装されています。
また、仕様なので具体的な実装方法には触れられていません。利用方法は実装されたエクスポータの実際のテストを見るのがおすすめです。
例えばopentelemetry-jsではSpanExporterとMetricExporterでシャットダウン後にもエクスポータからデータが取得できるかの挙動が異なりました。 MetricExporterではシャットダウン後もメトリクスが取得できますが、シャットダウン前に取得するのが正しい使い方です。筆者はそのあたりの挙動差異にハマり時間を溶かしました...
InMemorySpanExporterの使い方
では、早速テストを見てみましょう。それぞれポイントを解説します。
describe('InMemorySpanExporter', () => { let memoryExporter: InMemorySpanExporter; let provider: BasicTracerProvider; beforeEach(() => { memoryExporter = new InMemorySpanExporter(); provider = new BasicTracerProvider(); provider.addSpanProcessor(new SimpleSpanProcessor(memoryExporter)); }); it('should get finished spans', () => { const root = provider.getTracer('default').startSpan('root'); const child = provider .getTracer('default') .startSpan('child', {}, trace.setSpan(context.active(), root)); const grandChild = provider .getTracer('default') .startSpan('grand-child', {}, trace.setSpan(context.active(), child)); assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); grandChild.end(); assert.strictEqual(memoryExporter.getFinishedSpans().length, 1); child.end(); assert.strictEqual(memoryExporter.getFinishedSpans().length, 2); root.end(); assert.strictEqual(memoryExporter.getFinishedSpans().length, 3); const [span1, span2, span3] = memoryExporter.getFinishedSpans(); assert.strictEqual(span1.name, 'grand-child'); assert.strictEqual(span2.name, 'child'); assert.strictEqual(span3.name, 'root'); assert.strictEqual( span1.spanContext().traceId, span2.spanContext().traceId ); assert.strictEqual( span2.spanContext().traceId, span3.spanContext().traceId ); assert.strictEqual(span1.parentSpanId, span2.spanContext().spanId); assert.strictEqual(span2.parentSpanId, span3.spanContext().spanId); });
まずは各テストの初期化処理です。
beforeEach(() => { memoryExporter = new InMemorySpanExporter(); provider = new BasicTracerProvider(); provider.addSpanProcessor(new SimpleSpanProcessor(memoryExporter)); });
InMemorySpanExporter
自体のテストというのもあり毎回インスタンス化しています。providerもこのタイミングで作成しています。
テストなのでSimpleSpanProcessorを利用しています。このプロセッサはスパンが作られるとすぐにスパンをエクスポートします。毎回エクスポートするのは効率が悪いのでテストでない場合などは基本的にBatchSpanProcessorを利用します。
実はInMemorySpanExporter
にはreset関数も用意されており、resetを使ってもうまく初期化できました。その場合はインスタンス化は1回でも大丈夫です。
ただ、InMemorySpanExporter
にreset関数のテストがなかったので、、、一旦上記の方法でテストしておくのもありかもしれません...
次にトレーサを取得し、スパンを開始しています。
const root = provider.getTracer('default').startSpan('root'); const child = provider .getTracer('default') .startSpan('child', {}, trace.setSpan(context.active(), root)); const grandChild = provider .getTracer('default') .startSpan('grand-child', {}, trace.setSpan(context.active(), child));
次にスパンを完了し、インメモリエクスポータからgetFinishedSpans関数でスパンを取得しています。ここでは個数をテストしています。
assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); grandChild.end(); assert.strictEqual(memoryExporter.getFinishedSpans().length, 1); child.end(); assert.strictEqual(memoryExporter.getFinishedSpans().length, 2); root.end(); assert.strictEqual(memoryExporter.getFinishedSpans().length, 3);
このように完了したスパンがgetFinishedSpans関数で取得できることがわかります。
次に詳細の内容をassertしています。
const [span1, span2, span3] = memoryExporter.getFinishedSpans(); assert.strictEqual(span1.name, 'grand-child'); assert.strictEqual(span2.name, 'child'); assert.strictEqual(span3.name, 'root');
ここまで来れば使い方が理解できるのではないでしょうか。
まとめ
OpenTelemetryのインメモリエクスポータについて説明をしました!また、例としてInMemorySpanExporter
の使い方をテストコードをベースにご紹介しました!