UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

435 lines (387 loc) 11.9 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 as const; /** * Vendor-neutral trace context stored in durable messages and transport * envelopes. Unknown versions and malformed values are ignored by consumers. */ export interface TraceCarrier { /** Carrier schema version. */ readonly version: typeof TRACE_CARRIER_VERSION; /** W3C traceparent value captured at the producing boundary. */ readonly traceparent: string; /** Optional W3C tracestate value captured at the producing boundary. */ readonly tracestate?: string; } /** * Trace context used to correlate related activity. */ export interface TraceContext { /** * W3C trace ID. */ traceId: string; /** * Current span ID. */ spanId: string; /** * Parent span ID when available. */ parentSpanId?: string; /** * W3C traceparent header value. */ traceparent: string; /** * Optional W3C tracestate value associated with this context. */ tracestate?: string; } /** * Parsed W3C traceparent header. */ export interface ParsedTraceparent { /** * W3C trace ID. */ traceId: string; /** * Span ID from the traceparent header. */ spanId: string; /** * Trace flags from the traceparent header. */ traceFlags: string; /** * Normalized traceparent header value. */ traceparent: string; } /** * Input accepted when creating trace context. */ export interface TraceContextInput { traceId?: string; spanId?: string; parentSpanId?: string; traceparent?: string; tracestate?: string; } /** Values accepted as tracing attributes. */ export type TraceAttributeValue = | string | number | boolean | readonly string[] | readonly number[] | readonly boolean[]; /** Attributes attached to a traced Beignet operation. */ export type TraceAttributes = Readonly<Record<string, TraceAttributeValue>>; /** Span kinds understood by tracing providers. */ export type TraceSpanKind = | "internal" | "server" | "client" | "producer" | "consumer"; /** Stable Beignet operation categories used by tracing and metrics providers. */ export type TraceOperationType = | "request" | "useCase" | "agentCapability" | "listener" | "job" | "schedule" | "task" | "outbox" | "provider"; /** Description of one operation to run inside an active trace span. */ export interface TraceOperation { name: string; type: TraceOperationType; kind?: TraceSpanKind; parent?: TraceContextInput; /** Attributes attached to the operation span. */ attributes?: TraceAttributes; /** * Bounded attributes attached to operation metrics. Values must describe a * stable operation dimension, never a request, actor, tenant, or payload. */ metricAttributes?: TraceAttributes; } /** Framework-neutral span handle exposed to Beignet execution wrappers. */ export interface TraceSpan { readonly context: TraceContext; setAttribute(name: string, value: TraceAttributeValue): void; setAttributes(attributes: TraceAttributes): void; addEvent(name: string, attributes?: TraceAttributes): void; setStatus(status: "ok" | "error"): void; recordError(error: unknown): void; } /** Optional app port implemented by production tracing integrations. */ export interface TracingPort { current(): TraceContext | undefined; startActiveSpan<T>(operation: TraceOperation, run: (span: TraceSpan) => T): T; } function isObject(value: unknown): value is Record<string, unknown> { return typeof value === "object" && value !== null; } /** Return whether a value implements `TracingPort`. */ export function isTracingPort(value: unknown): value is TracingPort { 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: unknown): TracingPort | undefined { if (!target) return undefined; if (isTracingPort(target)) return target; if (!isObject(target)) return undefined; const direct = "tracing" in target ? (target.tracing as unknown) : 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: unknown): string | undefined { 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<string>(); 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: unknown): TraceCarrier | undefined { 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: unknown): TraceCarrier | undefined { let context: TraceContextInput | undefined; 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: unknown, ): TraceContextInput | undefined { if (!isObject(target)) return undefined; const context = target as TraceContextInput; 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<T>( target: unknown, operation: TraceOperation, run: (span?: TraceSpan) => T, ): T { const tracing = resolveTracingPort(target); if (!tracing) return run(); return tracing.startActiveSpan(operation, (span) => run(span)); } function createHexId(length: number): string { 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: string): boolean { return !/^0+$/.test(value); } /** * Create a non-zero W3C trace ID. */ export function createTraceId(): string { let traceId = createHexId(32); while (!isNonZeroHex(traceId)) { traceId = createHexId(32); } return traceId; } /** * Create a non-zero W3C span ID. */ export function createSpanId(): string { let spanId = createHexId(16); while (!isNonZeroHex(spanId)) { spanId = createHexId(16); } return spanId; } /** * Create a W3C traceparent header value. */ export function createTraceparent(args: { traceId: string; spanId: string; traceFlags?: string; }): string { return `00-${args.traceId}-${args.spanId}-${args.traceFlags ?? "01"}`; } /** * Parse and validate a W3C traceparent header value. */ export function parseTraceparent( value: string | null | undefined, ): ParsedTraceparent | undefined { 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: TraceContextInput = {}, ): TraceContext { 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: TraceContextInput, ): TraceContext { return createTraceContext({ traceId: parent.traceId, parentSpanId: parent.spanId, traceparent: parent.traceparent, tracestate: parent.tracestate, }); }