伝搬
コンテキスト伝搬により、シグナルは、生成される場所に関係なく、相互に関連付けることができます。 トレーシングに限定されませんが、コンテキスト伝搬により、トレースは、プロセスとネットワークの境界を越えて任意に分散されたサービス間で、システムに関する因果関係の情報を構築できます。
大多数のユースケースでは、OpenTelemetryをネイティブにサポートするライブラリまたは計装ライブラリが、自動的にサービス間でトレースコンテキストを伝搬します。 手動でコンテキストを伝搬する必要があるのは、まれなケースのみです。
詳細については、コンテキスト伝搬を参照してください。
自動コンテキスト伝搬
@opentelemetry/instrumentation-http
や@opentelemetry/instrumentation-express
などの計装ライブラリは、サービス間でのコンテキストの伝搬を自動的に行います。
Getting Startedガイドに従った場合、/rolldice
エンドポイントにクエリを送信するクライアントアプリケーションを作成できます。
この例は、他の言語のGetting Startedガイドのサンプルアプリケーションと組み合わせることもできます。相関は異なる言語で書かれたアプリケーション間でも違いなく動作します。
まず、dice-client
という新しいフォルダを作成し、必要な依存関係をインストールします。
npm init -y
npm install typescript \
ts-node \
@types/node \
undici \
@opentelemetry/instrumentation-undici \
@opentelemetry/sdk-node
# TypeScriptを初期化
npx tsc --init
npm init -y
npm install undici \
@opentelemetry/instrumentation-undici \
@opentelemetry/sdk-node
次に、client.ts
(またはclient.js)という新しいファイルを以下の内容で作成します。
import { NodeSDK } from '@opentelemetry/sdk-node';
import {
SimpleSpanProcessor,
ConsoleSpanExporter,
} from '@opentelemetry/sdk-trace-node';
import { UndiciInstrumentation } from '@opentelemetry/instrumentation-undici';
const sdk = new NodeSDK({
spanProcessors: [new SimpleSpanProcessor(new ConsoleSpanExporter())],
instrumentations: [new UndiciInstrumentation()],
});
sdk.start();
import { request } from 'undici';
request('http://localhost:8080/rolldice').then((response) => {
response.body.json().then((json: any) => console.log(json));
});
const { NodeSDK } = require('@opentelemetry/sdk-node');
const {
SimpleSpanProcessor,
ConsoleSpanExporter,
} = require('@opentelemetry/sdk-trace-node');
const {
UndiciInstrumentation,
} = require('@opentelemetry/instrumentation-undici');
const sdk = new NodeSDK({
spanProcessors: [new SimpleSpanProcessor(new ConsoleSpanExporter())],
instrumentations: [new UndiciInstrumentation()],
});
sdk.start();
const { request } = require('undici');
request('http://localhost:8080/rolldice').then((response) => {
response.body.json().then((json) => console.log(json));
});
Getting Startedの計装されたapp.ts
(またはapp.js
)が一つのシェルで実行されていることを確認してください。
$ npx ts-node --require ./instrumentation.ts app.ts
Listening for requests on http://localhost:8080
$ node --require ./instrumentation.js app.js
Listening for requests on http://localhost:8080
二つ目のシェルを開始し、client.ts
(またはclient.js
)を実行します。
npx ts-node client.ts
node client.js
両方のシェルはスパンの詳細をコンソールに出力するはずです。 クライアントの出力は以下のようになります。
{
resource: {
attributes: {
// ...
}
},
traceId: 'cccd19c3a2d10e589f01bfe2dc896dc2',
parentId: undefined,
traceState: undefined,
name: 'GET',
id: '6f64ce484217a7bf',
kind: 2,
timestamp: 1718875320295000,
duration: 19836.833,
attributes: {
'url.full': 'http://localhost:8080/rolldice',
// ...
},
status: { code: 0 },
events: [],
links: []
}
traceId(cccd19c3a2d10e589f01bfe2dc896dc2
)とID(6f64ce484217a7bf
)をメモしてください。
両方はクライアントの出力でも見つけることができます。
{
resource: {
attributes: {
// ...
},
traceId: 'cccd19c3a2d10e589f01bfe2dc896dc2',
parentId: '6f64ce484217a7bf',
traceState: undefined,
name: 'GET /rolldice',
id: '027c5c8b916d29da',
kind: 1,
timestamp: 1718875320310000,
duration: 3894.792,
attributes: {
'http.url': 'http://localhost:8080/rolldice',
// ...
},
status: { code: 0 },
events: [],
links: []
}
クライアントとサーバーアプリケーションは接続されたスパンを正常に報告します。これらを今バックエンドに送信すると、視覚化でこの依存関係が表示されます。
手動コンテキスト伝搬
前のセクションで説明したように、コンテキストを自動的に伝搬できない場合があります。 サービス間の通信に使用するライブラリに対応する計装ライブラリが存在しない場合があります。 または、そのようなライブラリが存在していても満たせない要件がある場合があります。
コンテキストを手動で伝搬する必要がある場合は、コンテキストAPIを使用できます。
汎用例
以下の汎用例では、トレースコンテキストを手動で伝搬する方法を示します。
まず、送信側のサービスで、現在のcontext
を注入する必要があります。
// 送信側サービス
import { context, propagation, trace } from '@opentelemetry/api';
// トレース情報を保持する出力オブジェクトのインターフェースを定義
interface Carrier {
traceparent?: string;
tracestate?: string;
}
// そのインターフェースに準拠する出力オブジェクトを作成
const output: Carrier = {};
// traceparentとtracestateをコンテキストから出力オブジェクトに
// シリアライズ
//
// この例ではアクティブなトレースコンテキストを使用していますが、
// シナリオに適したコンテキストを使用できます
propagation.inject(context.active(), output);
// 出力オブジェクトからtraceparentとtracestate値を抽出
const { traceparent, tracestate } = output;
// その後、traceparentとtracestateデータを
// サービス間でプロパゲートするために使用する
// メカニズムに渡すことができます
// 送信側サービス
const { context, propagation } = require('@opentelemetry/api');
const output = {};
// traceparentとtracestateをコンテキストから出力オブジェクトに
// シリアライズ
//
// この例ではアクティブなトレースコンテキストを使用していますが、
// シナリオに適したコンテキストを使用できます
propagation.inject(context.active(), output);
const { traceparent, tracestate } = output;
// その後、traceparentとtracestateデータを
// サービス間でプロパゲートするために使用する
// メカニズムに渡すことができます
受信側のサービスでは、context
を(たとえば、解析されたHTTPヘッダーから)抽出し、それらを現在のトレースコンテキストとして設定する必要があります。
// 受信側サービス
import {
type Context,
propagation,
trace,
Span,
context,
} from '@opentelemetry/api';
// 'traceparent'と'tracestate'を含む入力オブジェクトのインターフェースを定義
interface Carrier {
traceparent?: string;
tracestate?: string;
}
// "input"が'traceparent'と'tracestate'キーを持つオブジェクトと仮定
const input: Carrier = {};
// 'traceparent'と'tracestate'データをコンテキストオブジェクトに抽出
//
// その後、このコンテキストをトレースのアクティブコンテキストとして
// 扱うことができます
let activeContext: Context = propagation.extract(context.active(), input);
let tracer = trace.getTracer('app-name');
let span: Span = tracer.startSpan(
spanName,
{
attributes: {},
},
activeContext,
);
// 作成されたスパンを逆シリアル化されたコンテキストでアクティブに設定
trace.setSpan(activeContext, span);
// 受信側サービス
import { context, propagation, trace } from '@opentelemetry/api';
// "input"が'traceparent'と'tracestate'キーを持つオブジェクトと仮定
const input = {};
// 'traceparent'と'tracestate'データをコンテキストオブジェクトに抽出
//
// その後、このコンテキストをトレースのアクティブコンテキストとして
// 扱うことができます
let activeContext = propagation.extract(context.active(), input);
let tracer = trace.getTracer('app-name');
let span = tracer.startSpan(
spanName,
{
attributes: {},
},
activeContext,
);
// 作成されたスパンを逆シリアル化されたコンテキストでアクティブに設定
trace.setSpan(activeContext, span);
そこから、逆シリアル化されたアクティブコンテキストがある場合、他のサービスからの同じトレースの一部となるスパンを作成できます。
Context APIを使用して、逆シリアル化されたコンテキストを他の方法で変更または設定することもできます。
カスタムプロトコルの例
コンテキストを手動で伝搬する必要がある一般的なユースケースは、サービス間の通信にカスタムプロトコルを使用する場合です。 以下の例では、基本的なテキストベースのTCPプロトコルを使用して、あるサービスから別のサービスにシリアライズされたオブジェクトを送信します。
まず、propagation-example
という新しいフォルダを作成し、以下のように依存関係で初期化します。
npm init -y
npm install @opentelemetry/api @opentelemetry/sdk-node
次に、以下の内容でclient.js
とserver.js
ファイルを作成します。
// client.js
const net = require('net');
const { context, propagation, trace } = require('@opentelemetry/api');
let tracer = trace.getTracer('client');
// サーバーに接続
const client = net.createConnection({ port: 8124 }, () => {
// シリアライズされたオブジェクトをサーバーに送信
let span = tracer.startActiveSpan('send', { kind: 1 }, (span) => {
const output = {};
propagation.inject(context.active(), output);
const { traceparent, tracestate } = output;
const objToSend = { key: 'value' };
if (traceparent) {
objToSend._meta = { traceparent, tracestate };
}
client.write(JSON.stringify(objToSend), () => {
client.end();
span.end();
});
});
});
// server.js
const net = require('net');
const { context, propagation, trace } = require('@opentelemetry/api');
let tracer = trace.getTracer('server');
const server = net.createServer((socket) => {
socket.on('data', (data) => {
const message = data.toString();
// クライアントから受信したJSONオブジェクトを解析
try {
const json = JSON.parse(message);
let activeContext = context.active();
if (json._meta) {
activeContext = propagation.extract(context.active(), json._meta);
delete json._meta;
}
span = tracer.startSpan('receive', { kind: 1 }, activeContext);
trace.setSpan(activeContext, span);
console.log('Parsed JSON:', json);
} catch (e) {
console.error('Error parsing JSON:', e.message);
} finally {
span.end();
}
});
});
// ポート8124でリッスン
server.listen(8124, () => {
console.log('Server listening on port 8124');
});
最初のシェルでサーバーを実行します。
$ node server.js
Server listening on port 8124
次に、二つ目のシェルでクライアントを実行します。
node client.js
クライアントはすぐに終了し、サーバーは以下を出力するはずです。
Parsed JSON: { key: 'value' }
この例はこれまでOpenTelemetry APIにのみ依存していたため、すべての呼び出しはno-op命令であり、クライアントとサーバーはOpenTelemetryが使用されていないかのように動作します。
これは、サーバーとクライアントコードがライブラリである場合に特に重要です。 ライブラリはOpenTelemetry APIのみを使用するべきだからです。 その理由を理解するには、ライブラリに計装を追加する方法のコンセプトページを確認してください。
OpenTelemetryを有効にし、実際のコンテキスト伝搬を確認するために、以下の内容でinstrumentation.js
という追加ファイルを作成します。
// instrumentation.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const {
ConsoleSpanExporter,
SimpleSpanProcessor,
} = require('@opentelemetry/sdk-trace-node');
const sdk = new NodeSDK({
spanProcessors: [new SimpleSpanProcessor(new ConsoleSpanExporter())],
});
sdk.start();
このファイルを使用して、計装を有効にしてサーバーとクライアントの両方を実行します。
$ node -r ./instrumentation.js server.js
Server listening on port 8124
および
node -r ./instrumentation client.js
クライアントがサーバーにデータを送信して終了した後、両方のシェルのコンソール出力にスパンが表示されるはずです。
クライアントの出力は以下のようになります。
{
resource: {
attributes: {
// ...
}
},
traceId: '4b5367d540726a70afdbaf49240e6597',
parentId: undefined,
traceState: undefined,
name: 'send',
id: '92f125fa335505ec',
kind: 1,
timestamp: 1718879823424000,
duration: 1054.583,
// ...
}
サーバーの出力は以下のようになります。
{
resource: {
attributes: {
// ...
}
},
traceId: '4b5367d540726a70afdbaf49240e6597',
parentId: '92f125fa335505ec',
traceState: undefined,
name: 'receive',
id: '53da0c5f03cb36e5',
kind: 1,
timestamp: 1718879823426000,
duration: 959.541,
// ...
}
手動例と同様に、スパンはtraceId
とid
/parentId
を使用して接続されています。
次のステップ
伝搬についてさらに学ぶには、Propagators API仕様を確認してください。
フィードバック
このページは役に立ちましたか?
Thank you. Your feedback is appreciated!
Please let us know how we can improve this page. Your feedback is appreciated!