コンテキスト

OpenTelemetry JavaScript Context API ドキュメント

OpenTelemetryが動作するためには、重要なテレメトリーデータを保存し、伝搬する必要があります。 たとえば、リクエストを受信してスパンが開始されるとき、その子スパンを作成するコンポーネントでそのスパンが利用可能である必要があります。 この問題を解決するため、OpenTelemetryはスパンをContextに保存します。 このドキュメントでは、JavaScript用のOpenTelemetry context APIとその使用方法について説明します。

詳細は以下を確認してください。

コンテキストマネージャー

context APIが動作するためには、コンテキストマネージャーに依存します。 このドキュメントの例では、すでにコンテキストマネージャーが設定されていることを前提としています。 通常、コンテキストマネージャーはSDKによって提供されますが、以下のように直接登録することも可能です。

import * as api from '@opentelemetry/api';
import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks';

const contextManager = new AsyncHooksContextManager();
contextManager.enable();
api.context.setGlobalContextManager(contextManager);

ルートコンテキスト

ROOT_CONTEXTは空のコンテキストです。 アクティブなコンテキストがない場合、ROOT_CONTEXTがアクティブになります。 アクティブコンテキストについては、以下のアクティブコンテキストで説明します。

コンテキストキー

コンテキストエントリはキーと値のペアです。 キーはapi.createContextKey(description)を呼び出すことで作成できます。

import * as api from '@opentelemetry/api';

const key1 = api.createContextKey('My first key');
const key2 = api.createContextKey('My second key');

基本操作

エントリの取得

エントリはcontext.getValue(key)メソッドを使用してアクセスします。

import * as api from '@opentelemetry/api';

const key = api.createContextKey('some key');
// ROOT_CONTEXTは空のコンテキスト
const ctx = api.ROOT_CONTEXT;

const value = ctx.getValue(key);

エントリの設定

エントリはcontext.setValue(key, value)メソッドを使用して作成します。 コンテキストエントリを設定すると、前のコンテキストのすべてのエントリを持つ新しいコンテキストが作成されますが、新しいエントリも含まれます。 コンテキストエントリの設定は、前のコンテキストを変更しません。

import * as api from '@opentelemetry/api';

const key = api.createContextKey('some key');
const ctx = api.ROOT_CONTEXT;

// 新しいエントリを追加
const ctx2 = ctx.setValue(key, 'context 2');

// ctx2には新しいエントリが含まれる
console.log(ctx2.getValue(key)); // "context 2"

// ctxは変更されない
console.log(ctx.getValue(key)); // undefined

エントリの削除

エントリはcontext.deleteValue(key)を呼び出すことで削除されます。 コンテキストエントリを削除すると、前のコンテキストのすべてのエントリを含む新しいコンテキストが作成されますが、キーで識別されるエントリは除外されます。 コンテキストエントリの削除は、前のコンテキストを変更しません。

import * as api from '@opentelemetry/api';

const key = api.createContextKey('some key');
const ctx = api.ROOT_CONTEXT;
const ctx2 = ctx.setValue(key, 'context 2');

// エントリを削除
const ctx3 = ctx.deleteValue(key);

// ctx3にはエントリが含まれない
console.log(ctx3.getValue(key)); // undefined

// ctx2は変更されない
console.log(ctx2.getValue(key)); // "context 2"
// ctxは変更されない
console.log(ctx.getValue(key)); // undefined

アクティブコンテキスト

重要: これはコンテキストマネージャーが設定されていることを前提としています。コンテキストマネージャーがないと、api.context.active()常にROOT_CONTEXTを返します。

アクティブコンテキストは、api.context.active()によって返されるコンテキストです。 コンテキストオブジェクトには、単一の実行スレッドをトレースするトレーシングコンポーネントが相互に通信し、トレースが正常に作成されることを保証するエントリが含まれています。 たとえば、スパンが作成されるとき、そのスパンがコンテキストに追加される場合があります。 後で別のスパンが作成されるとき、コンテキストからのスパンを親スパンとして使用する場合があります。 これは、Node.jsではasync_hooksAsyncLocalStorage、Webではzone.jsなどのメカニズムを使用して、単一の実行を通じてコンテキストを伝搬することで実現されます。 アクティブなコンテキストがない場合、空のコンテキストオブジェクトであるROOT_CONTEXTが返されます。

アクティブコンテキストの取得

アクティブコンテキストは、api.context.active()によって返されるコンテキストです。

import * as api from '@opentelemetry/api';

// アクティブコンテキストを返す
// アクティブなコンテキストがない場合、ROOT_CONTEXTが返される
const ctx = api.context.active();

アクティブコンテキストの設定

api.context.with(ctx, callback)を使用してコンテキストをアクティブにできます。 callbackの実行中、withに渡されたコンテキストがcontext.activeによって返されます。

import * as api from '@opentelemetry/api';

const key = api.createContextKey('Key to store a value');
const ctx = api.context.active();

api.context.with(ctx.setValue(key, 'context 2'), async () => {
  // "context 2"がアクティブ
  console.log(api.context.active().getValue(key)); // "context 2"
});

api.context.with(context, callback)の戻り値は、コールバックの戻り値です。 コールバックは常に同期的に呼び出されます。

import * as api from '@opentelemetry/api';

const name = await api.context.with(api.context.active(), async () => {
  const row = await db.getSomeValue();
  return row['name'];
});

console.log(name); // dbによって返された名前

アクティブコンテキストの実行はネストできます。

import * as api from '@opentelemetry/api';

const key = api.createContextKey('Key to store a value');
const ctx = api.context.active();

// アクティブなコンテキストなし
console.log(api.context.active().getValue(key)); // undefined

api.context.with(ctx.setValue(key, 'context 2'), () => {
  // "context 2"がアクティブ
  console.log(api.context.active().getValue(key)); // "context 2"
  api.context.with(ctx.setValue(key, 'context 3'), () => {
    // "context 3"がアクティブ
    console.log(api.context.active().getValue(key)); // "context 3"
  });
  // "context 2"がアクティブ
  console.log(api.context.active().getValue(key)); // "context 2"
});

// アクティブなコンテキストなし
console.log(api.context.active().getValue(key)); // undefined

以下のより複雑な例では、コンテキストが変更されるのではなく、新しいコンテキストオブジェクトが作成されることを示しています。

import * as api from '@opentelemetry/api';

const key = api.createContextKey('Key to store a value');

const ctx = api.context.active(); // アクティブコンテキストがない場合ROOT_CONTEXTを返す
const ctx2 = ctx.setValue(key, 'context 2'); // ctxを変更しない

console.log(ctx.getValue(key)); //? undefined
console.log(ctx2.getValue(key)); //? "context 2"

const ret = api.context.with(ctx2, () => {
  const ctx3 = api.context.active().setValue(key, 'context 3');

  console.log(api.context.active().getValue(key)); //? "context 2"
  console.log(ctx.getValue(key)); //? undefined
  console.log(ctx2.getValue(key)); //? "context 2"
  console.log(ctx3.getValue(key)); //? "context 3"

  api.context.with(ctx3, () => {
    console.log(api.context.active().getValue(key)); //? "context 3"
  });
  console.log(api.context.active().getValue(key)); //? "context 2"

  return 'return value';
});

// コールバックによって返された値が呼び出し元に返される
console.log(ret); //? "return value"