Paper2 Blog

ともに、かける

OpenTelemetryのインメモリエクスポータとは

OpenTelemetryのインメモリエクスポータについて説明をします!!また、例としてopentelemetry-jsInMemorySpanExporter の使用方法をご紹介します!!

インメモリエクスポータとは何か

ローカルのメモリにテレメトリデータを蓄積し、それをテストできるエクスポータです。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における対応状況のスクショを以下に記載します。 + がサポートされていることを示すので、ほぼ全ての言語で実装されいていることがわかります。

in-memory exporeter対応状況

例えば opentelemetry-jsでは InMemorySpanExporterInMemoryMetricExporterInMemoryLogRecordExporter が実装されています。

また、仕様なので具体的な実装方法には触れられていません。利用方法は実装されたエクスポータの実際のテストを見るのがおすすめです。

例えば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の使い方をテストコードをベースにご紹介しました!