import { projectTelemetryEvent } from "../../contracts/telemetry.js"; export const noOpTelemetry = Object.freeze({ emit: () => {}, }); /** * @param {{ * enabled: boolean, * endpoint?: string, * fetcher?: typeof fetch, * maxQueue?: number, * schedule?: (callback: () => void) => void * }} options */ export function createTelemetryAdapter(options) { if (!options.enabled || !options.endpoint) { return Object.freeze({ ...noOpTelemetry, flush: async () => {}, pendingCount: () => 0, droppedCount: () => 0, }); } const endpoint = /** @type {string} */ (options.endpoint); const fetcher = options.fetcher ?? fetch; const maxQueue = options.maxQueue ?? 100; const schedule = options.schedule ?? queueMicrotask; const queue = /** @type {Array<{eventName: string, attributes: Readonly>}>} */ ( [] ); let scheduled = false; let flushing = false; let dropped = 0; /** @param {string} eventName @param {Record} attributes */ function emit(eventName, attributes) { const projected = projectTelemetryEvent(eventName, attributes); if (!projected.success) { dropped += 1; return; } if (queue.length >= maxQueue) { queue.shift(); dropped += 1; } queue.push(projected.event); if (!scheduled) { scheduled = true; schedule(() => { scheduled = false; void flush(); }); } } async function flush() { if (flushing || queue.length === 0) return; flushing = true; const batch = queue.splice(0, queue.length); try { const response = await fetcher(endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ events: batch }), keepalive: true, }); if (!response.ok) dropped += batch.length; } catch { dropped += batch.length; } finally { flushing = false; } } return Object.freeze({ emit, flush, pendingCount: () => queue.length, droppedCount: () => dropped, }); } /** * Propagates only a structurally valid W3C traceparent. Invalid/raw headers are * discarded rather than logged or surfaced. * * @param {string | null | undefined} traceparent */ export function safeTraceparent(traceparent) { return typeof traceparent === "string" && /^00-[0-9a-f]{32}-[0-9a-f]{16}-0[01]$/i.test(traceparent) ? traceparent.toLowerCase() : null; }