1
19import {
20 context,
21 propagation,
22 trace,
23 type Context,
24 type SpanContext,
25 type TextMapGetter,
26 type TextMapPropagator,
27 type TextMapSetter
28} from "@opentelemetry/api";
29
30export const TRACE_HEADER = "traceparent";
31export const TRACESTATE_HEADER = "tracestate";
32
33const HEADER_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
34
35const INVALID_TRACE_ID = "00000000000000000000000000000000";
36const INVALID_SPAN_ID = "0000000000000000";
37
38
39export function parseTraceparent(tp: string): { traceId: string; spanId: string; flags: number } | null {
40 const m = HEADER_RE.exec(tp.trim());
41 if (!m) return null;
42 const traceId = m[1]!;
43 const spanId = m[2]!;
44 if (traceId === INVALID_TRACE_ID || spanId === INVALID_SPAN_ID) return null;
45 return { traceId, spanId, flags: Number.parseInt(m[3]!, 16) };
46}
47
48
49export function buildTraceparent(traceId: string, spanId: string, sampled = true): string {
50 return `00-${traceId}-${spanId}-${sampled ? "01" : "00"}`;
51}
52
53
63export class W3CTraceContextPropagator implements TextMapPropagator {
64 fields(): string[] {
65 return [TRACE_HEADER];
66 }
67
68 inject(ctx: Context, carrier: Record<string, string>, _setter?: TextMapSetter<Record<string, string>>): void {
69 const sc = trace.getSpanContext(ctx);
70 if (!sc || !trace.isSpanContextValid(sc)) return;
71 carrier[TRACE_HEADER] = `00-${sc.traceId}-${sc.spanId}-${sc.traceFlags.toString(16).padStart(2, "0")}`;
72 }
73
74 extract(
75 ctx: Context,
76 carrier: Record<string, string | string[] | undefined>,
77 _getter?: TextMapGetter<Record<string, string | string[] | undefined>>
78 ): Context {
79 const raw = headerGetter.get(carrier, TRACE_HEADER);
80 if (typeof raw !== "string") return ctx;
81 const parts = parseTraceparent(raw);
82 if (!parts) return ctx;
83 const sc: SpanContext = {
84 traceId: parts.traceId,
85 spanId: parts.spanId,
86 isRemote: true,
87 traceFlags: parts.flags
88 };
89 return trace.setSpanContext(ctx, sc);
90 }
91}
92
93
94const headerGetter: TextMapGetter<Record<string, string | string[] | undefined>> = {
95 keys(carrier: Record<string, string | string[] | undefined>): string[] {
96 return Object.keys(carrier);
97 },
98 get(carrier: Record<string, string | string[] | undefined>, key: string): string | undefined {
99 const v = carrier[key];
100 return Array.isArray(v) ? v.join(",") : v;
101 }
102};
103
104
110export interface W3CPropagatorLike {
111 fields(): string[];
112 inject(ctx: Context, carrier: Record<string, string>, setter?: TextMapSetter<Record<string, string>>): void;
113 extract(
114 ctx: Context,
115 carrier: Record<string, string | string[] | undefined>,
116 getter?: TextMapGetter<Record<string, string | string[] | undefined>>
117 ): Context;
118}
119
120export const traceparentPropagator: W3CPropagatorLike = new W3CTraceContextPropagator();
121
122
127export function registerTraceparentPropagator(): void {
128 propagation.setGlobalPropagator(traceparentPropagator);
129}
130
131
137export function captureTraceparent(ctx: Context = context.active()): string | undefined {
138 const carrier: Record<string, string> = {};
139 propagation.inject(ctx, carrier);
140 return carrier[TRACE_HEADER];
141}
142
143
149export function resumeTraceparent<T>(tp: string | undefined, fn: (parent: Context) => T): T {
150 if (tp) {
151 const extracted = propagation.extract(context.active(), { [TRACE_HEADER]: tp });
152 if (extracted !== context.active()) {
153 return context.with(extracted, fn, undefined, extracted);
154 }
155 }
156 return fn(context.active());
157}
158
159
165export interface TraceMetadata {
166
167 traceparent?: string;
168}
169
170
171export function metadataTraceparent(metadata: Record<string, unknown> | null | undefined): string | undefined {
172 if (!metadata) return undefined;
173 const tp = metadata["traceparent"];
174 return typeof tp === "string" && parseTraceparent(tp) !== null ? tp : undefined;
175}
176
177
183export function telemetryTraceparent(metadata: string | null | undefined): string | undefined {
184 return typeof metadata === "string" && parseTraceparent(metadata) !== null ? metadata : undefined;
185}
186