























@@ -0,0 +1,133 @@
1+import { randomBytes } from "node:crypto";
2+3+const TRACEPARENT_VERSION = "00";
4+const DEFAULT_TRACE_FLAGS = "01";
5+const TRACE_ID_RE = /^[0-9a-f]{32}$/;
6+const SPAN_ID_RE = /^[0-9a-f]{16}$/;
7+const TRACE_FLAGS_RE = /^[0-9a-f]{2}$/;
8+9+export type DiagnosticTraceContext = {
10+/** W3C trace id, 32 lowercase hex chars. */
11+traceId: string;
12+/** Current span id, 16 lowercase hex chars. */
13+spanId?: string;
14+/** Parent span id, 16 lowercase hex chars. */
15+parentSpanId?: string;
16+/** W3C trace flags, 2 lowercase hex chars. Defaults to sampled. */
17+traceFlags?: string;
18+};
19+20+export type DiagnosticTraceContextInput = Partial<DiagnosticTraceContext> & {
21+traceparent?: string;
22+};
23+24+function randomHex(bytes: number): string {
25+return randomBytes(bytes).toString("hex");
26+}
27+28+function isNonZeroHex(value: string): boolean {
29+return !/^0+$/.test(value);
30+}
31+32+export function isValidDiagnosticTraceId(value: unknown): value is string {
33+return typeof value === "string" && TRACE_ID_RE.test(value) && isNonZeroHex(value);
34+}
35+36+export function isValidDiagnosticSpanId(value: unknown): value is string {
37+return typeof value === "string" && SPAN_ID_RE.test(value) && isNonZeroHex(value);
38+}
39+40+export function isValidDiagnosticTraceFlags(value: unknown): value is string {
41+return typeof value === "string" && TRACE_FLAGS_RE.test(value);
42+}
43+44+function normalizeTraceId(value: unknown): string | undefined {
45+if (typeof value !== "string") {
46+return undefined;
47+}
48+const normalized = value.toLowerCase();
49+return isValidDiagnosticTraceId(normalized) ? normalized : undefined;
50+}
51+52+function normalizeSpanId(value: unknown): string | undefined {
53+if (typeof value !== "string") {
54+return undefined;
55+}
56+const normalized = value.toLowerCase();
57+return isValidDiagnosticSpanId(normalized) ? normalized : undefined;
58+}
59+60+function normalizeTraceFlags(value: unknown): string | undefined {
61+if (typeof value !== "string") {
62+return undefined;
63+}
64+const normalized = value.toLowerCase();
65+return isValidDiagnosticTraceFlags(normalized) ? normalized : undefined;
66+}
67+68+export function parseDiagnosticTraceparent(
69+traceparent: string | undefined,
70+): DiagnosticTraceContext | undefined {
71+const parts = traceparent?.trim().toLowerCase().split("-");
72+if (!parts || parts.length !== 4) {
73+return undefined;
74+}
75+const [version, traceId, spanId, traceFlags] = parts;
76+if (version !== TRACEPARENT_VERSION) {
77+return undefined;
78+}
79+const normalizedTraceId = normalizeTraceId(traceId);
80+const normalizedSpanId = normalizeSpanId(spanId);
81+const normalizedTraceFlags = normalizeTraceFlags(traceFlags);
82+if (!normalizedTraceId || !normalizedSpanId || !normalizedTraceFlags) {
83+return undefined;
84+}
85+return {
86+traceId: normalizedTraceId,
87+spanId: normalizedSpanId,
88+traceFlags: normalizedTraceFlags,
89+};
90+}
91+92+export function formatDiagnosticTraceparent(
93+context: DiagnosticTraceContext | undefined,
94+): string | undefined {
95+if (!context?.spanId) {
96+return undefined;
97+}
98+const traceId = normalizeTraceId(context.traceId);
99+const spanId = normalizeSpanId(context.spanId);
100+const traceFlags = normalizeTraceFlags(context.traceFlags) ?? DEFAULT_TRACE_FLAGS;
101+if (!traceId || !spanId) {
102+return undefined;
103+}
104+return `${TRACEPARENT_VERSION}-${traceId}-${spanId}-${traceFlags}`;
105+}
106+107+export function createDiagnosticTraceContext(
108+input: DiagnosticTraceContextInput = {},
109+): DiagnosticTraceContext {
110+const parsed = parseDiagnosticTraceparent(input.traceparent);
111+const traceId = normalizeTraceId(input.traceId) ?? parsed?.traceId ?? randomHex(16);
112+const spanId = normalizeSpanId(input.spanId) ?? parsed?.spanId ?? randomHex(8);
113+const parentSpanId = normalizeSpanId(input.parentSpanId);
114+return {
115+ traceId,
116+ spanId,
117+ ...(parentSpanId && parentSpanId !== spanId ? { parentSpanId } : {}),
118+traceFlags: normalizeTraceFlags(input.traceFlags) ?? parsed?.traceFlags ?? DEFAULT_TRACE_FLAGS,
119+};
120+}
121+122+export function createChildDiagnosticTraceContext(
123+parent: DiagnosticTraceContext,
124+input: Omit<DiagnosticTraceContextInput, "traceId" | "traceparent"> = {},
125+): DiagnosticTraceContext {
126+const parentSpanId = normalizeSpanId(input.parentSpanId) ?? normalizeSpanId(parent.spanId);
127+return createDiagnosticTraceContext({
128+traceId: parent.traceId,
129+spanId: input.spanId,
130+ parentSpanId,
131+traceFlags: input.traceFlags ?? parent.traceFlags,
132+});
133+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。