UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

256 lines 8.51 kB
/** * @beignet/core/tracing * * W3C trace context primitives used to correlate Beignet activity across * requests, use cases, providers, and instrumentation sinks. * * This module is dependency-free so client bundles that import app context * types stay clean. */ const TRACEPARENT_PATTERN = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/; const MAX_TRACESTATE_LENGTH = 512; const MAX_TRACESTATE_MEMBERS = 32; const TRACESTATE_SIMPLE_KEY_PATTERN = /^[a-z][a-z0-9_*/-]{0,255}$/; const TRACESTATE_VENDOR_KEY_PATTERN = /^[a-z0-9][a-z0-9_*/-]{0,240}@[a-z][a-z0-9_*/-]{0,13}$/; const TRACESTATE_VALUE_PATTERN = /^[\x20-\x2b\x2d-\x3c\x3e-\x7e]{0,255}[\x21-\x2b\x2d-\x3c\x3e-\x7e]$/; /** Current version of Beignet's durable trace carrier. */ export const TRACE_CARRIER_VERSION = 1; function isObject(value) { return typeof value === "object" && value !== null; } /** Return whether a value implements `TracingPort`. */ export function isTracingPort(value) { return (isObject(value) && "current" in value && "startActiveSpan" in value && typeof value.current === "function" && typeof value.startActiveSpan === "function"); } /** Resolve a tracing port from a direct port, ports object, or app context. */ export function resolveTracingPort(target) { if (!target) return undefined; if (isTracingPort(target)) return target; if (!isObject(target)) return undefined; const direct = "tracing" in target ? target.tracing : undefined; if (isTracingPort(direct)) return direct; const ports = "ports" in target ? target.ports : undefined; if (!ports || ports === target) return undefined; return resolveTracingPort(ports); } function validTracestate(value) { if (typeof value !== "string") return undefined; const normalized = value.trim(); if (normalized.length === 0 || normalized.length > MAX_TRACESTATE_LENGTH) { return undefined; } const members = normalized.split(","); if (members.length > MAX_TRACESTATE_MEMBERS) return undefined; const keys = new Set(); for (const rawMember of members) { const member = rawMember.trim(); const separator = member.indexOf("="); if (separator <= 0) return undefined; const key = member.slice(0, separator).trim(); const memberValue = member.slice(separator + 1).trimStart(); if ((!TRACESTATE_SIMPLE_KEY_PATTERN.test(key) && !TRACESTATE_VENDOR_KEY_PATTERN.test(key)) || !TRACESTATE_VALUE_PATTERN.test(memberValue) || keys.has(key)) { return undefined; } keys.add(key); } return normalized; } /** * Parse an untrusted durable trace carrier. * * Invalid carriers return `undefined`; trace metadata must never prevent * message delivery. */ export function parseTraceCarrier(value) { if (!isObject(value) || value.version !== TRACE_CARRIER_VERSION) { return undefined; } const parsed = parseTraceparent(typeof value.traceparent === "string" ? value.traceparent : undefined); if (!parsed) return undefined; if (value.tracestate !== undefined) { const tracestate = validTracestate(value.tracestate); if (!tracestate) return undefined; return { version: TRACE_CARRIER_VERSION, traceparent: parsed.traceparent, tracestate, }; } return { version: TRACE_CARRIER_VERSION, traceparent: parsed.traceparent, }; } /** * Capture the current trace context from a tracing port, ports object, app * context, or explicit trace context. Returns `undefined` when no valid * context is available. */ export function captureTraceCarrier(target) { let context; try { context = resolveTraceContextInput(target); } catch { // Context-like inputs may be proxy-backed and reject unknown properties. } try { context = resolveTracingPort(target)?.current() ?? context; } catch { // Tracing is best-effort and must not block the owning operation. } try { if (!context?.traceparent) return undefined; return parseTraceCarrier({ version: TRACE_CARRIER_VERSION, traceparent: context.traceparent, ...(context.tracestate ? { tracestate: context.tracestate } : {}), }); } catch { // Trace contexts are app-provided and remain best-effort inputs. return undefined; } } /** Resolve trace fields from a trace context or context-like object. */ export function resolveTraceContextInput(target) { if (!isObject(target)) return undefined; const context = target; if (context.traceId || context.spanId || context.parentSpanId || context.traceparent || context.tracestate) { return { ...(context.traceId ? { traceId: context.traceId } : {}), ...(context.spanId ? { spanId: context.spanId } : {}), ...(context.parentSpanId ? { parentSpanId: context.parentSpanId } : {}), ...(context.traceparent ? { traceparent: context.traceparent } : {}), ...(context.tracestate ? { tracestate: context.tracestate } : {}), }; } const traceContext = "trace" in target ? target.trace : undefined; if (!traceContext || traceContext === target) return undefined; return resolveTraceContextInput(traceContext); } /** Run a callback inside a tracing span when an app tracing port is installed. */ export function runWithTracing(target, operation, run) { const tracing = resolveTracingPort(target); if (!tracing) return run(); return tracing.startActiveSpan(operation, (span) => run(span)); } function createHexId(length) { const bytes = new Uint8Array(length / 2); if (typeof crypto !== "undefined" && "getRandomValues" in crypto) { crypto.getRandomValues(bytes); return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(""); } let value = ""; while (value.length < length) { value += Math.floor(Math.random() * 16).toString(16); } return value.slice(0, length); } function isNonZeroHex(value) { return !/^0+$/.test(value); } /** * Create a non-zero W3C trace ID. */ export function createTraceId() { let traceId = createHexId(32); while (!isNonZeroHex(traceId)) { traceId = createHexId(32); } return traceId; } /** * Create a non-zero W3C span ID. */ export function createSpanId() { let spanId = createHexId(16); while (!isNonZeroHex(spanId)) { spanId = createHexId(16); } return spanId; } /** * Create a W3C traceparent header value. */ export function createTraceparent(args) { return `00-${args.traceId}-${args.spanId}-${args.traceFlags ?? "01"}`; } /** * Parse and validate a W3C traceparent header value. */ export function parseTraceparent(value) { if (!value) return undefined; const normalized = value.trim().toLowerCase(); const match = TRACEPARENT_PATTERN.exec(normalized); if (!match) return undefined; const [, traceId, spanId, traceFlags] = match; if (!isNonZeroHex(traceId) || !isNonZeroHex(spanId)) return undefined; return { traceId, spanId, traceFlags, traceparent: normalized, }; } /** * Create trace context from explicit IDs or an existing traceparent. */ export function createTraceContext(input = {}) { const parsed = parseTraceparent(input.traceparent); const traceId = input.traceId ?? parsed?.traceId ?? createTraceId(); const parentSpanId = input.parentSpanId ?? parsed?.spanId; const spanId = input.spanId ?? createSpanId(); return { traceId, spanId, ...(parentSpanId ? { parentSpanId } : {}), traceparent: createTraceparent({ traceId, spanId, traceFlags: parsed?.traceFlags, }), ...(input.tracestate ? { tracestate: input.tracestate } : {}), }; } /** * Create child trace context from a parent context. */ export function createChildTraceContext(parent) { return createTraceContext({ traceId: parent.traceId, parentSpanId: parent.spanId, traceparent: parent.traceparent, tracestate: parent.tracestate, }); } //# sourceMappingURL=index.js.map