UNPKG

autotel

Version:
1 lines 7.84 kB
{"version":3,"sources":["../src/test-span-collector.ts"],"names":["SpanStatusCode"],"mappings":"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","file":"test-span-collector.cjs","sourcesContent":["/**\n * TestSpanCollector — SpanExporter that groups finished spans by traceId\n * and drains per-trace for embedding in test metadata.\n *\n * @example\n * ```typescript\n * import { TestSpanCollector } from 'autotel/test-span-collector';\n * import { SimpleSpanProcessor } from 'autotel/processors';\n * import { getAutotelTracerProvider } from 'autotel';\n *\n * const collector = new TestSpanCollector();\n * const provider = getAutotelTracerProvider();\n * provider.addSpanProcessor(new SimpleSpanProcessor(collector));\n *\n * // After a test span ends:\n * const spans = collector.drainTrace(traceId, rootSpanId);\n * // spans contains only descendants of rootSpanId\n * ```\n */\n\nimport type { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport { SpanStatusCode } from '@opentelemetry/api';\n\n/** @see ExportResultCode from @opentelemetry/core */\nconst ExportResultCode = { SUCCESS: 0, FAILED: 1 } as const;\n\n/** Attribute value types that survive serialization */\ntype SerializableValue =\n | string\n | number\n | boolean\n | string[]\n | number[]\n | boolean[];\n\n/**\n * Portable serialized span for embedding in test metadata.\n * `startTimeMs` is derived from OTel HrTime — epoch-based wall-clock ms in the current SDK.\n *\n * Defined as a `type` (not `interface`) so it is assignable to\n * `Record<string, unknown>` in TypeScript 6+ strict mode.\n */\nexport type SerializedSpan = {\n spanId: string;\n parentSpanId?: string;\n name: string;\n startTimeMs: number;\n durationMs: number;\n status: 'ok' | 'error' | 'unset';\n statusMessage?: string;\n attributes?: Record<string, SerializableValue>;\n};\n\nexport class TestSpanCollector implements SpanExporter {\n private traces = new Map<string, ReadableSpan[]>();\n\n export(\n spans: ReadableSpan[],\n callback: (result: { code: number }) => void,\n ): void {\n for (const span of spans) {\n const traceId = span.spanContext().traceId;\n let list = this.traces.get(traceId);\n if (!list) {\n list = [];\n this.traces.set(traceId, list);\n }\n list.push(span);\n }\n callback({ code: ExportResultCode.SUCCESS });\n }\n\n /**\n * Drain and serialize spans that are descendants of `rootSpanId` within `traceId`.\n * Filters to the subtree rooted at the test span to prevent cross-test mixing.\n * Removes the entire traceId entry from the collector.\n */\n drainTrace(traceId: string, rootSpanId: string): SerializedSpan[] {\n const allSpans = this.traces.get(traceId);\n this.traces.delete(traceId);\n if (!allSpans?.length) return [];\n\n // Build spanId → span index for efficient parent-chain walking\n const byId = new Map<string, ReadableSpan>();\n for (const s of allSpans) byId.set(s.spanContext().spanId, s);\n\n // Filter to spans that are the root or descendants of rootSpanId\n const included = allSpans.filter((s) => {\n let id: string | undefined = s.spanContext().spanId;\n while (id) {\n if (id === rootSpanId) return true;\n const parent = byId.get(id);\n const parentId = parent?.parentSpanContext?.spanId || undefined;\n if (parentId === id) break; // cycle guard\n id = parentId;\n }\n return false;\n });\n\n return included.map((s) => serializeSpan(s));\n }\n\n shutdown(): Promise<void> {\n this.traces.clear();\n return Promise.resolve();\n }\n\n forceFlush(): Promise<void> {\n return Promise.resolve();\n }\n}\n\nfunction hrTimeToMs(hr: [number, number]): number {\n return hr[0] * 1000 + hr[1] / 1_000_000;\n}\n\nfunction isSerializable(v: unknown): v is SerializableValue {\n if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')\n return true;\n if (Array.isArray(v) && v.length > 0) {\n const t = typeof v[0];\n return (\n (t === 'string' || t === 'number' || t === 'boolean') &&\n v.every((e) => typeof e === t)\n );\n }\n return false;\n}\n\nexport function serializeSpan(span: ReadableSpan): SerializedSpan {\n const attrs: Record<string, SerializableValue> = {};\n for (const [k, v] of Object.entries(span.attributes)) {\n if (isSerializable(v)) attrs[k] = v;\n }\n return {\n spanId: span.spanContext().spanId,\n parentSpanId: span.parentSpanContext?.spanId || undefined,\n name: span.name,\n startTimeMs: hrTimeToMs(span.startTime as [number, number]),\n durationMs: hrTimeToMs(span.duration as [number, number]),\n status:\n span.status.code === SpanStatusCode.ERROR\n ? 'error'\n : span.status.code === SpanStatusCode.OK\n ? 'ok'\n : 'unset',\n statusMessage: span.status.message || undefined,\n attributes: Object.keys(attrs).length > 0 ? attrs : undefined,\n };\n}\n"]}