@opentelemetry/sdk-trace-base編
@opentelemetry/sdk-trace-baseは@opentelemetry/apiで定義されたTrace APIの実装を提供するSDKパッケージです。主に以下の機能を提供しています。
BasicTracerProvider: TracerProviderの基本実装
Tracer: Span生成とサンプリング判定
Span: Span APIの実装
SpanProcessor: Span開始/終了時のフック処理
Sampler: トレースのサンプリング戦略
SpanExporter: Spanデータのエクスポート
BasicTracerProvider
BasicTracerProviderはデフォルトのconfigやTracer取得実装、forceFlushのベース実装がされています。
環境変数はインスタンス生成時に初期化されます。
環境変数はSpecificationに準拠しており、デフォルト値なども設定されています。
code:ts
export function loadDefaultConfig() {
return {
sampler: buildSamplerFromEnv(), // OTEL_TRACES_SAMPLER
forceFlushTimeoutMillis: 30000,
generalLimits: {
attributeValueLengthLimit: getNumberFromEnv('OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT') ?? Infinity,
attributeCountLimit: getNumberFromEnv('OTEL_ATTRIBUTE_COUNT_LIMIT') ?? 128,
},
spanLimits: {
attributeValueLengthLimit: getNumberFromEnv('OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT') ?? Infinity,
attributeCountLimit: getNumberFromEnv('OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT') ?? 128,
linkCountLimit: getNumberFromEnv('OTEL_SPAN_LINK_COUNT_LIMIT') ?? 128,
eventCountLimit: getNumberFromEnv('OTEL_SPAN_EVENT_COUNT_LIMIT') ?? 128,
// ...
},
};
}
TracerこのProvider内にキャッシュで保存され使いまわされるような実装になっています。
code:ts
const key = ${name}@${version || ''}:${options?.schemaUrl || ''};
if (!this._tracers.has(key)) {
this._tracers.set(
key,
new Tracer(
{ name, version, schemaUrl: options?.schemaUrl },
this._config,
this._resource,
this._activeSpanProcessor
)
);
}
forceFlushの実装もされていますが、これらの内部実装はspanProcessorに依存しており、Promise.allで各Processorに対して個別のタイムアウト監視を行っています。
code:ts
forceFlush(): Promise<void> {
const timeout = this._config.forceFlushTimeoutMillis; // デフォルト 30000ms
(spanProcessor: SpanProcessor) => {
return new Promise(resolve => {
let state: ForceFlushState;
// 各Processorに対して個別のタイムアウト監視
const timeoutInterval = setTimeout(() => {
resolve(new Error(Span processor did not completed within timeout period of ${timeout} ms));
state = ForceFlushState.timeout;
}, timeout);
spanProcessor.forceFlush()
.then(() => {
clearTimeout(timeoutInterval);
if (state !== ForceFlushState.timeout) { // 二重解決防止
state = ForceFlushState.resolved;
resolve(state);
}
})
.catch(error => {
clearTimeout(timeoutInterval);
state = ForceFlushState.error;
resolve(error); // reject ではなく resolve
});
});
}
);
// ...
}
Tracer
TracerはコンテキストをもとにSpanの情報を生成できるようになっています。
code:ts
// api/src/trace/context-utils.ts
// Contextの中でSpanを格納するためのキー
const SPAN_KEY = createContextKey('OpenTelemetry Context Key SPAN');
// Spanをコンテキストにセット
export function setSpan(context: Context, span: Span): Context {
return context.setValue(SPAN_KEY, span);
}
// コンテキストからSpanを取得
export function getSpan(context: Context): Span | undefined {
return (context.getValue(SPAN_KEY) as Span) || undefined;
}
生成されたSpan群はSpanImplという実装をベースに構築されます。
これはAPISpanとReadableSpanが同時に実装されたものです。内部実装自体は属性等を抽象化して扱えるようにされており、SpanExporterなどが読み取れる形式に変換されます。
SpanProcessor
さまざまな実装があります。SimpleSpanProcessorやBatchSpanProcessorなど。
基本的にはバッチ/即時/遅延などどのようにspanを扱いたいかによってこのPrcessorを使い分けることになります。
ちなみにNode.js特有の処理にこんなものがあります。unrefという見慣れないものが使われています。
これはブラウザではsetTimeoutはnumberを返すが、Node.jsではTimeoutオブジェクトを返すために参照を外しているための実装です。
code:ts
private _maybeStartTimer() {
this._timer = setTimeout(() => flush(), this._scheduledDelayMillis);
// Node.js特有の処理
if (typeof this._timer !== 'number') {
this._timer.unref(); // ← ここ!
}
}
SpanExporter
exporter組み込み実装がたくさんあります。(ConsoleSpanExporter、InMemorySpanExporter等)
本番向けにはCollector向けのExporterを使うのですが、ここでは開発やベースとなるExporterのみが実装されています。
ConsoleSpanExporterを利用すると下記のような感じでtraceのデータが取れます。
code:ts
{
resource: {
attributes: {
'service.name': 'my-service',
'telemetry.sdk.language': 'nodejs'
}
},
instrumentationScope: { name: '@opentelemetry/instrumentation-http', version: '0.52.0' },
traceId: 'd4cda95b652f4a1592b449d5929fda1b',
parentSpanContext: undefined,
name: 'HTTP GET',
id: '6e0c63257de34c92',
kind: 2,
timestamp: 1702234567890123,
duration: 15234,
attributes: { 'http.method': 'GET', 'http.url': '/api/users' },
status: { code: 1 },
events: [],
links: []
}
まとめ
Traceを扱う上での基礎実装となる部分を見ていきました。
明日は実際のweb/node.jsにおけるTrace実装を見ていきます。