@reliantlabs/forge-web-runtime 0.10.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,235 @@
1
+ // Part of @reliantlabs/forge-web-runtime — the web twin of forge/pkg.
2
+ //
3
+ // OTLP/HTTP with JSON bodies, for the `/node` entry: the encoding, the
4
+ // exporter and the batching queue. Node built-ins only (global fetch,
5
+ // timers), so it runs unchanged under Electron's main process.
6
+ //
7
+ // ── Why not the OpenTelemetry JS SDK ─────────────────────────────────────
8
+ //
9
+ // The SDK would be six more peer packages (sdk-logs, sdk-trace, resources,
10
+ // two exporters, context-async-hooks) whose major lines move together and
11
+ // fast — 3.0 landed with a new package layout and a hard @opentelemetry/api
12
+ // 1.10 floor. Every consumer of this package would have to match that set
13
+ // exactly. What `/node` needs from it is small and stable: the OTLP/JSON wire
14
+ // format (OTLP 1.x, frozen), a batch, a flush. So it is written here, against
15
+ // the wire format, and the only OpenTelemetry package involved is the
16
+ // @opentelemetry/api this runtime already peers on.
17
+ //
18
+ // ── The wire format ──────────────────────────────────────────────────────
19
+ //
20
+ // OTLP/JSON differs from the protobuf JSON mapping in two places that matter:
21
+ // traceId and spanId are lowercase HEX (not base64), and enum fields are
22
+ // integers. 64-bit integers (timestamps, intValue) are decimal strings.
23
+ /** Most attributes on one record or span; the rest are dropped. */
24
+ export const MAX_ATTRIBUTES = 128;
25
+ /** Longest string attribute value. */
26
+ export const MAX_ATTRIBUTE_CHARS = 4096;
27
+ function scalar(v) {
28
+ switch (typeof v) {
29
+ case "string":
30
+ return {
31
+ stringValue: v.length > MAX_ATTRIBUTE_CHARS ? v.slice(0, MAX_ATTRIBUTE_CHARS) : v,
32
+ };
33
+ case "boolean":
34
+ return { boolValue: v };
35
+ case "number":
36
+ if (!Number.isFinite(v)) {
37
+ return null;
38
+ }
39
+ return Number.isSafeInteger(v)
40
+ ? { intValue: String(v) }
41
+ : { doubleValue: v };
42
+ default:
43
+ return null;
44
+ }
45
+ }
46
+ /** OTLP KeyValues for `attrs`: bounded, unencodable values dropped. */
47
+ export function encodeAttributes(attrs) {
48
+ const out = [];
49
+ if (!attrs) {
50
+ return out;
51
+ }
52
+ for (const [key, v] of Object.entries(attrs)) {
53
+ if (out.length >= MAX_ATTRIBUTES) {
54
+ break;
55
+ }
56
+ if (v === undefined) {
57
+ continue;
58
+ }
59
+ if (Array.isArray(v)) {
60
+ const values = v.map(scalar).filter((x) => x !== null);
61
+ out.push({ key, value: { arrayValue: { values } } });
62
+ continue;
63
+ }
64
+ const value = scalar(v);
65
+ if (value) {
66
+ out.push({ key, value });
67
+ }
68
+ }
69
+ return out;
70
+ }
71
+ /** Nanoseconds since the epoch for `ms` (fractional ms allowed), as OTLP's decimal string. */
72
+ export function unixNanos(ms) {
73
+ const whole = Math.floor(ms);
74
+ const frac = Math.round((ms - whole) * 1e6);
75
+ return (BigInt(whole) * 1000000n + BigInt(frac)).toString();
76
+ }
77
+ /** Now, in epoch milliseconds with sub-millisecond precision. */
78
+ export function nowMs() {
79
+ return performance.timeOrigin + performance.now();
80
+ }
81
+ /** OTel severity numbers (logs data model). */
82
+ export const SEVERITY = {
83
+ DEBUG: 5,
84
+ INFO: 9,
85
+ WARN: 13,
86
+ ERROR: 17,
87
+ };
88
+ /** The ExportLogsServiceRequest body for one batch. */
89
+ export function logsPayload(resource, scope, records) {
90
+ return {
91
+ resourceLogs: [
92
+ {
93
+ resource: { attributes: resource },
94
+ scopeLogs: [{ scope, logRecords: records }],
95
+ },
96
+ ],
97
+ };
98
+ }
99
+ /** The ExportTraceServiceRequest body for one batch, spans grouped by scope. */
100
+ export function tracesPayload(resource, spans) {
101
+ const byScope = new Map();
102
+ for (const { scope, ...span } of spans) {
103
+ const key = `${scope.name}\u001f${scope.version ?? ""}`;
104
+ let group = byScope.get(key);
105
+ if (!group) {
106
+ group = { scope, spans: [] };
107
+ byScope.set(key, group);
108
+ }
109
+ group.spans.push(span);
110
+ }
111
+ return {
112
+ resourceSpans: [
113
+ {
114
+ resource: { attributes: resource },
115
+ scopeSpans: [...byScope.values()],
116
+ },
117
+ ],
118
+ };
119
+ }
120
+ /** Longest backoff between attempts, whatever Retry-After asks for. */
121
+ export const MAX_RETRY_DELAY_MS = 5_000;
122
+ const RETRYABLE = new Set([429, 502, 503, 504]);
123
+ function defaultSleep(ms) {
124
+ return new Promise((resolve) => {
125
+ const t = setTimeout(resolve, ms);
126
+ t.unref?.();
127
+ });
128
+ }
129
+ function retryDelay(attempt, retryAfter) {
130
+ const seconds = retryAfter === null ? NaN : Number(retryAfter);
131
+ const base = Number.isFinite(seconds) && seconds >= 0
132
+ ? seconds * 1000
133
+ : 250 * 2 ** attempt;
134
+ return Math.min(MAX_RETRY_DELAY_MS, base);
135
+ }
136
+ /**
137
+ * Posts one OTLP/JSON body to `${endpoint}/v1/<signal>`. Resolves true when
138
+ * the collector accepted it, false when it was dropped (after the retries);
139
+ * never rejects.
140
+ */
141
+ export async function exportSignal(opts, signal, body) {
142
+ const url = `${opts.endpoint}/v1/${signal}`;
143
+ const payload = JSON.stringify(body);
144
+ const sleep = opts.sleep ?? defaultSleep;
145
+ let last = null;
146
+ for (let attempt = 0; attempt <= opts.retries; attempt++) {
147
+ let retryAfter = null;
148
+ try {
149
+ const res = await opts.fetch(url, {
150
+ method: "POST",
151
+ headers: { ...opts.headers, "content-type": "application/json" },
152
+ body: payload,
153
+ signal: AbortSignal.timeout(opts.timeoutMs),
154
+ });
155
+ if (res.status >= 200 && res.status < 300) {
156
+ return true;
157
+ }
158
+ last = new Error(`OTLP ${signal} export: HTTP ${res.status}`);
159
+ if (!RETRYABLE.has(res.status)) {
160
+ break;
161
+ }
162
+ retryAfter = res.headers.get("retry-after");
163
+ }
164
+ catch (err) {
165
+ last = err instanceof Error ? err : new Error(String(err));
166
+ }
167
+ if (attempt < opts.retries) {
168
+ await sleep(retryDelay(attempt, retryAfter));
169
+ }
170
+ }
171
+ try {
172
+ opts.onError?.(last ?? new Error(`OTLP ${signal} export failed`));
173
+ }
174
+ catch {
175
+ // A diagnostics hook must not break telemetry.
176
+ }
177
+ return false;
178
+ }
179
+ /**
180
+ * A bounded queue that exports in batches: when a batch fills, when the delay
181
+ * after the first queued item elapses, or on flush(). Its timer is unref'd, so
182
+ * a pending batch never keeps a process alive by itself.
183
+ */
184
+ export class BatchQueue {
185
+ opts;
186
+ items = [];
187
+ timer = null;
188
+ inflight = new Set();
189
+ /** Items refused because the queue was full. */
190
+ dropped = 0;
191
+ constructor(opts) {
192
+ this.opts = opts;
193
+ }
194
+ get size() {
195
+ return this.items.length;
196
+ }
197
+ push(item) {
198
+ if (this.items.length >= this.opts.maxQueue) {
199
+ this.dropped++;
200
+ return;
201
+ }
202
+ this.items.push(item);
203
+ if (this.items.length >= this.opts.maxBatch) {
204
+ this.drain();
205
+ }
206
+ else if (!this.timer) {
207
+ this.timer = setTimeout(() => {
208
+ this.timer = null;
209
+ this.drain();
210
+ }, this.opts.delayMs);
211
+ this.timer.unref?.();
212
+ }
213
+ }
214
+ /** Start exporting everything queued now, without waiting for it. */
215
+ drain() {
216
+ if (this.timer) {
217
+ clearTimeout(this.timer);
218
+ this.timer = null;
219
+ }
220
+ while (this.items.length > 0) {
221
+ const batch = this.items.splice(0, this.opts.maxBatch);
222
+ const p = this.opts.send(batch).catch(() => undefined);
223
+ this.inflight.add(p);
224
+ void p.finally(() => this.inflight.delete(p));
225
+ }
226
+ }
227
+ /** Export everything queued and wait for every export in flight. */
228
+ async flush() {
229
+ this.drain();
230
+ while (this.inflight.size > 0) {
231
+ await Promise.all([...this.inflight]);
232
+ }
233
+ }
234
+ }
235
+ //# sourceMappingURL=node-otlp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-otlp.js","sourceRoot":"","sources":["../src/node-otlp.ts"],"names":[],"mappings":"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","sourcesContent":["// Part of @reliantlabs/forge-web-runtime — the web twin of forge/pkg.\n//\n// OTLP/HTTP with JSON bodies, for the `/node` entry: the encoding, the\n// exporter and the batching queue. Node built-ins only (global fetch,\n// timers), so it runs unchanged under Electron's main process.\n//\n// ── Why not the OpenTelemetry JS SDK ─────────────────────────────────────\n//\n// The SDK would be six more peer packages (sdk-logs, sdk-trace, resources,\n// two exporters, context-async-hooks) whose major lines move together and\n// fast — 3.0 landed with a new package layout and a hard @opentelemetry/api\n// 1.10 floor. Every consumer of this package would have to match that set\n// exactly. What `/node` needs from it is small and stable: the OTLP/JSON wire\n// format (OTLP 1.x, frozen), a batch, a flush. So it is written here, against\n// the wire format, and the only OpenTelemetry package involved is the\n// @opentelemetry/api this runtime already peers on.\n//\n// ── The wire format ──────────────────────────────────────────────────────\n//\n// OTLP/JSON differs from the protobuf JSON mapping in two places that matter:\n// traceId and spanId are lowercase HEX (not base64), and enum fields are\n// integers. 64-bit integers (timestamps, intValue) are decimal strings.\n\n/** An attribute value a record or span may carry. */\nexport type AttributeValue =\n | string\n | number\n | boolean\n | ReadonlyArray<string>\n | ReadonlyArray<number>\n | ReadonlyArray<boolean>;\nexport type Attributes = Record<string, AttributeValue | undefined>;\n\n/** Most attributes on one record or span; the rest are dropped. */\nexport const MAX_ATTRIBUTES = 128;\n/** Longest string attribute value. */\nexport const MAX_ATTRIBUTE_CHARS = 4096;\n\ninterface AnyValue {\n stringValue?: string;\n boolValue?: boolean;\n intValue?: string;\n doubleValue?: number;\n arrayValue?: { values: AnyValue[] };\n}\n\nexport interface KeyValue {\n key: string;\n value: AnyValue;\n}\n\nfunction scalar(v: unknown): AnyValue | null {\n switch (typeof v) {\n case \"string\":\n return {\n stringValue:\n v.length > MAX_ATTRIBUTE_CHARS ? v.slice(0, MAX_ATTRIBUTE_CHARS) : v,\n };\n case \"boolean\":\n return { boolValue: v };\n case \"number\":\n if (!Number.isFinite(v)) {\n return null;\n }\n return Number.isSafeInteger(v)\n ? { intValue: String(v) }\n : { doubleValue: v };\n default:\n return null;\n }\n}\n\n/** OTLP KeyValues for `attrs`: bounded, unencodable values dropped. */\nexport function encodeAttributes(attrs: Attributes | undefined): KeyValue[] {\n const out: KeyValue[] = [];\n if (!attrs) {\n return out;\n }\n for (const [key, v] of Object.entries(attrs)) {\n if (out.length >= MAX_ATTRIBUTES) {\n break;\n }\n if (v === undefined) {\n continue;\n }\n if (Array.isArray(v)) {\n const values = v.map(scalar).filter((x): x is AnyValue => x !== null);\n out.push({ key, value: { arrayValue: { values } } });\n continue;\n }\n const value = scalar(v);\n if (value) {\n out.push({ key, value });\n }\n }\n return out;\n}\n\n/** Nanoseconds since the epoch for `ms` (fractional ms allowed), as OTLP's decimal string. */\nexport function unixNanos(ms: number): string {\n const whole = Math.floor(ms);\n const frac = Math.round((ms - whole) * 1e6);\n return (BigInt(whole) * 1_000_000n + BigInt(frac)).toString();\n}\n\n/** Now, in epoch milliseconds with sub-millisecond precision. */\nexport function nowMs(): number {\n return performance.timeOrigin + performance.now();\n}\n\n/** OTel severity numbers (logs data model). */\nexport const SEVERITY = {\n DEBUG: 5,\n INFO: 9,\n WARN: 13,\n ERROR: 17,\n} as const;\nexport type SeverityText = keyof typeof SEVERITY;\n\n/** One log record, as OTLP/JSON. */\nexport interface LogRecord {\n timeUnixNano: string;\n observedTimeUnixNano: string;\n severityNumber: number;\n severityText: SeverityText;\n body: { stringValue: string };\n attributes: KeyValue[];\n traceId?: string;\n spanId?: string;\n flags?: number;\n}\n\n/** One finished span, as OTLP/JSON. */\nexport interface SpanRecord {\n traceId: string;\n spanId: string;\n parentSpanId?: string;\n name: string;\n kind: number;\n startTimeUnixNano: string;\n endTimeUnixNano: string;\n attributes: KeyValue[];\n droppedAttributesCount?: number;\n events: Array<{ timeUnixNano: string; name: string; attributes: KeyValue[] }>;\n links: Array<{ traceId: string; spanId: string; attributes: KeyValue[] }>;\n status: { code: number; message?: string };\n /** Grouping key for scopeSpans; not on the wire. */\n scope: Scope;\n}\n\nexport interface Scope {\n name: string;\n version?: string;\n}\n\n/** The ExportLogsServiceRequest body for one batch. */\nexport function logsPayload(\n resource: KeyValue[],\n scope: Scope,\n records: LogRecord[],\n) {\n return {\n resourceLogs: [\n {\n resource: { attributes: resource },\n scopeLogs: [{ scope, logRecords: records }],\n },\n ],\n };\n}\n\n/** The ExportTraceServiceRequest body for one batch, spans grouped by scope. */\nexport function tracesPayload(resource: KeyValue[], spans: SpanRecord[]) {\n const byScope = new Map<\n string,\n { scope: Scope; spans: Omit<SpanRecord, \"scope\">[] }\n >();\n for (const { scope, ...span } of spans) {\n const key = `${scope.name}\\u001f${scope.version ?? \"\"}`;\n let group = byScope.get(key);\n if (!group) {\n group = { scope, spans: [] };\n byScope.set(key, group);\n }\n group.spans.push(span);\n }\n return {\n resourceSpans: [\n {\n resource: { attributes: resource },\n scopeSpans: [...byScope.values()],\n },\n ],\n };\n}\n\n// ── Exporter ─────────────────────────────────────────────────────────────\n\nexport type FetchLike = (\n url: string,\n init: {\n method: string;\n headers: Record<string, string>;\n body: string;\n signal?: AbortSignal;\n },\n) => Promise<{ status: number; headers: { get(name: string): string | null } }>;\n\nexport interface ExporterOptions {\n /** The OTLP base: `${endpoint}/v1/logs`, `${endpoint}/v1/traces`. */\n endpoint: string;\n /** Sent on every request. The keyed ingest requires an allowlisted `Origin`. */\n headers: Record<string, string>;\n fetch: FetchLike;\n /** One request's budget. */\n timeoutMs: number;\n /** Retries after the first attempt, on a network error, 429 or 5xx gateway answer. */\n retries: number;\n /** Told about a batch that was dropped; never about one that landed. */\n onError?: (error: Error) => void;\n /** Test seam for the backoff wait. */\n sleep?: (ms: number) => Promise<void>;\n}\n\n/** Longest backoff between attempts, whatever Retry-After asks for. */\nexport const MAX_RETRY_DELAY_MS = 5_000;\n\nconst RETRYABLE = new Set([429, 502, 503, 504]);\n\nfunction defaultSleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const t = setTimeout(resolve, ms);\n (t as { unref?: () => void }).unref?.();\n });\n}\n\nfunction retryDelay(attempt: number, retryAfter: string | null): number {\n const seconds = retryAfter === null ? NaN : Number(retryAfter);\n const base =\n Number.isFinite(seconds) && seconds >= 0\n ? seconds * 1000\n : 250 * 2 ** attempt;\n return Math.min(MAX_RETRY_DELAY_MS, base);\n}\n\n/**\n * Posts one OTLP/JSON body to `${endpoint}/v1/<signal>`. Resolves true when\n * the collector accepted it, false when it was dropped (after the retries);\n * never rejects.\n */\nexport async function exportSignal(\n opts: ExporterOptions,\n signal: \"logs\" | \"traces\",\n body: unknown,\n): Promise<boolean> {\n const url = `${opts.endpoint}/v1/${signal}`;\n const payload = JSON.stringify(body);\n const sleep = opts.sleep ?? defaultSleep;\n let last: Error | null = null;\n for (let attempt = 0; attempt <= opts.retries; attempt++) {\n let retryAfter: string | null = null;\n try {\n const res = await opts.fetch(url, {\n method: \"POST\",\n headers: { ...opts.headers, \"content-type\": \"application/json\" },\n body: payload,\n signal: AbortSignal.timeout(opts.timeoutMs),\n });\n if (res.status >= 200 && res.status < 300) {\n return true;\n }\n last = new Error(`OTLP ${signal} export: HTTP ${res.status}`);\n if (!RETRYABLE.has(res.status)) {\n break;\n }\n retryAfter = res.headers.get(\"retry-after\");\n } catch (err) {\n last = err instanceof Error ? err : new Error(String(err));\n }\n if (attempt < opts.retries) {\n await sleep(retryDelay(attempt, retryAfter));\n }\n }\n try {\n opts.onError?.(last ?? new Error(`OTLP ${signal} export failed`));\n } catch {\n // A diagnostics hook must not break telemetry.\n }\n return false;\n}\n\n// ── Batching ─────────────────────────────────────────────────────────────\n\nexport interface BatchOptions<T> {\n /** Most items held; a push beyond it is dropped and counted. */\n maxQueue: number;\n /** Most items in one export. */\n maxBatch: number;\n /** How long an item may wait for a batch to fill. */\n delayMs: number;\n send(items: T[]): Promise<unknown>;\n}\n\n/**\n * A bounded queue that exports in batches: when a batch fills, when the delay\n * after the first queued item elapses, or on flush(). Its timer is unref'd, so\n * a pending batch never keeps a process alive by itself.\n */\nexport class BatchQueue<T> {\n private items: T[] = [];\n private timer: ReturnType<typeof setTimeout> | null = null;\n private inflight = new Set<Promise<unknown>>();\n /** Items refused because the queue was full. */\n dropped = 0;\n\n constructor(private readonly opts: BatchOptions<T>) {}\n\n get size(): number {\n return this.items.length;\n }\n\n push(item: T): void {\n if (this.items.length >= this.opts.maxQueue) {\n this.dropped++;\n return;\n }\n this.items.push(item);\n if (this.items.length >= this.opts.maxBatch) {\n this.drain();\n } else if (!this.timer) {\n this.timer = setTimeout(() => {\n this.timer = null;\n this.drain();\n }, this.opts.delayMs);\n (this.timer as { unref?: () => void }).unref?.();\n }\n }\n\n /** Start exporting everything queued now, without waiting for it. */\n drain(): void {\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n while (this.items.length > 0) {\n const batch = this.items.splice(0, this.opts.maxBatch);\n const p = this.opts.send(batch).catch(() => undefined);\n this.inflight.add(p);\n void p.finally(() => this.inflight.delete(p));\n }\n }\n\n /** Export everything queued and wait for every export in flight. */\n async flush(): Promise<void> {\n this.drain();\n while (this.inflight.size > 0) {\n await Promise.all([...this.inflight]);\n }\n }\n}\n"]}
@@ -0,0 +1,41 @@
1
+ import { type Context, type ContextManager, type TextMapGetter, type TextMapPropagator, type TextMapSetter, type TimeInput, type Tracer, type TracerProvider } from "@opentelemetry/api";
2
+ import { type Attributes, type SpanRecord } from "./node-otlp.js";
3
+ /** Most events kept on one span. */
4
+ export declare const MAX_SPAN_EVENTS = 128;
5
+ /** Most links kept on one span. */
6
+ export declare const MAX_SPAN_LINKS = 128;
7
+ /** Epoch milliseconds for an OTel TimeInput, as the SDK reads one. */
8
+ export declare function timeInputMs(t: TimeInput | undefined): number;
9
+ type Sink = (span: SpanRecord) => void;
10
+ /** The values every span carries, set once by the entry (app.client, userId). */
11
+ export type GlobalAttributes = () => Attributes;
12
+ /** The context operations a tracer runs on: the global API's, or a manager's own. */
13
+ export interface ContextOps {
14
+ active(): Context;
15
+ with<T>(ctx: Context, fn: () => T): T;
16
+ }
17
+ /** A TracerProvider whose spans go to `sink`. */
18
+ export declare class NodeTracerProvider implements TracerProvider {
19
+ private readonly sink;
20
+ private readonly globals;
21
+ private readonly ctx;
22
+ private readonly tracers;
23
+ constructor(sink: Sink, globals: GlobalAttributes, ctx: ContextOps);
24
+ getTracer(name: string, version?: string): Tracer;
25
+ }
26
+ /** The OTel ContextManager over AsyncLocalStorage: context follows async calls. */
27
+ export declare class AsyncLocalContextManager implements ContextManager {
28
+ private store;
29
+ active(): Context;
30
+ with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(context: Context, fn: F, thisArg?: ThisParameterType<F>, ...args: A): ReturnType<F>;
31
+ bind<T>(context: Context, target: T): T;
32
+ enable(): this;
33
+ disable(): this;
34
+ }
35
+ /** W3C Trace Context: `traceparent` in and out. */
36
+ export declare class TraceparentPropagator implements TextMapPropagator {
37
+ inject(context: Context, carrier: unknown, setter: TextMapSetter): void;
38
+ extract(context: Context, carrier: unknown, getter: TextMapGetter): Context;
39
+ fields(): string[];
40
+ }
41
+ export {};
@@ -0,0 +1,331 @@
1
+ // Part of @reliantlabs/forge-web-runtime — the web twin of forge/pkg.
2
+ //
3
+ // Spans for the `/node` entry: a TracerProvider behind @opentelemetry/api, an
4
+ // AsyncLocalStorage context manager, and the W3C traceparent propagator. Every
5
+ // span is recorded and sampled (a desktop app's main process opens few), and a
6
+ // finished span is handed to the exporter's queue.
7
+ //
8
+ // This is the part of the OpenTelemetry SDK `/node` needs and no more; see
9
+ // node-otlp.ts for why the SDK itself is not a dependency.
10
+ import { AsyncLocalStorage } from "node:async_hooks";
11
+ import { randomBytes } from "node:crypto";
12
+ import { ROOT_CONTEXT, SpanKind, SpanStatusCode, trace, TraceFlags, } from "@opentelemetry/api";
13
+ import { encodeAttributes, MAX_ATTRIBUTES, nowMs, unixNanos, } from "./node-otlp.js";
14
+ import { TRACEPARENT_RE } from "./trace.js";
15
+ /** Most events kept on one span. */
16
+ export const MAX_SPAN_EVENTS = 128;
17
+ /** Most links kept on one span. */
18
+ export const MAX_SPAN_LINKS = 128;
19
+ const ZERO_TRACE = "0".repeat(32);
20
+ const ZERO_SPAN = "0".repeat(16);
21
+ function hexId(bytes, zero) {
22
+ let id = randomBytes(bytes).toString("hex");
23
+ while (id === zero) {
24
+ id = randomBytes(bytes).toString("hex");
25
+ }
26
+ return id;
27
+ }
28
+ /** Epoch milliseconds for an OTel TimeInput, as the SDK reads one. */
29
+ export function timeInputMs(t) {
30
+ if (t === undefined) {
31
+ return nowMs();
32
+ }
33
+ if (t instanceof Date) {
34
+ return t.getTime();
35
+ }
36
+ if (Array.isArray(t)) {
37
+ const [s, ns] = t;
38
+ return s * 1000 + ns / 1e6;
39
+ }
40
+ // The SDK's rule: a number below the process's time origin is a
41
+ // performance.now() reading, anything else is epoch milliseconds.
42
+ return t < performance.timeOrigin ? performance.timeOrigin + t : t;
43
+ }
44
+ function isTimeInput(v) {
45
+ return (typeof v === "number" ||
46
+ v instanceof Date ||
47
+ (Array.isArray(v) && v.length === 2 && typeof v[0] === "number"));
48
+ }
49
+ class RecordingSpan {
50
+ ctx;
51
+ parentSpanId;
52
+ name;
53
+ kind;
54
+ startMs;
55
+ scope;
56
+ sink;
57
+ ended = false;
58
+ attributes = {};
59
+ droppedAttributes = 0;
60
+ events = [];
61
+ links = [];
62
+ status = { code: SpanStatusCode.UNSET };
63
+ constructor(ctx, parentSpanId, name, kind, startMs, scope, sink) {
64
+ this.ctx = ctx;
65
+ this.parentSpanId = parentSpanId;
66
+ this.name = name;
67
+ this.kind = kind;
68
+ this.startMs = startMs;
69
+ this.scope = scope;
70
+ this.sink = sink;
71
+ }
72
+ spanContext() {
73
+ return this.ctx;
74
+ }
75
+ setAttribute(key, value) {
76
+ if (this.ended || value === undefined || value === null) {
77
+ return this;
78
+ }
79
+ if (!(key in this.attributes) &&
80
+ Object.keys(this.attributes).length >= MAX_ATTRIBUTES) {
81
+ this.droppedAttributes++;
82
+ return this;
83
+ }
84
+ this.attributes[key] = value;
85
+ return this;
86
+ }
87
+ setAttributes(attributes) {
88
+ for (const [k, v] of Object.entries(attributes)) {
89
+ if (v !== undefined) {
90
+ this.setAttribute(k, v);
91
+ }
92
+ }
93
+ return this;
94
+ }
95
+ addEvent(name, attributesOrStartTime, startTime) {
96
+ if (this.ended || this.events.length >= MAX_SPAN_EVENTS) {
97
+ return this;
98
+ }
99
+ let attrs;
100
+ let time = startTime;
101
+ if (isTimeInput(attributesOrStartTime)) {
102
+ time = attributesOrStartTime;
103
+ }
104
+ else {
105
+ attrs = attributesOrStartTime;
106
+ }
107
+ this.events.push({
108
+ timeUnixNano: unixNanos(timeInputMs(time)),
109
+ name,
110
+ attributes: encodeAttributes(attrs),
111
+ });
112
+ return this;
113
+ }
114
+ addLink(link) {
115
+ if (!this.ended && this.links.length < MAX_SPAN_LINKS) {
116
+ this.links.push({
117
+ traceId: link.context.traceId,
118
+ spanId: link.context.spanId,
119
+ attributes: encodeAttributes(link.attributes),
120
+ });
121
+ }
122
+ return this;
123
+ }
124
+ addLinks(links) {
125
+ for (const l of links) {
126
+ this.addLink(l);
127
+ }
128
+ return this;
129
+ }
130
+ setStatus(status) {
131
+ if (this.ended) {
132
+ return this;
133
+ }
134
+ // OK is final, as in the SDK: a later ERROR does not overwrite it.
135
+ if (this.status.code === SpanStatusCode.OK) {
136
+ return this;
137
+ }
138
+ this.status = status;
139
+ return this;
140
+ }
141
+ updateName(name) {
142
+ if (!this.ended) {
143
+ this.name = name;
144
+ }
145
+ return this;
146
+ }
147
+ recordException(exception, time) {
148
+ const attrs = {};
149
+ if (typeof exception === "string") {
150
+ attrs["exception.message"] = exception;
151
+ }
152
+ else if (exception) {
153
+ const e = exception;
154
+ if (typeof e.name === "string")
155
+ attrs["exception.type"] = e.name;
156
+ else if (typeof e.code === "string" || typeof e.code === "number")
157
+ attrs["exception.type"] = String(e.code);
158
+ if (typeof e.message === "string")
159
+ attrs["exception.message"] = e.message;
160
+ if (typeof e.stack === "string")
161
+ attrs["exception.stacktrace"] = e.stack;
162
+ }
163
+ this.addEvent("exception", attrs, time);
164
+ }
165
+ end(endTime) {
166
+ if (this.ended) {
167
+ return;
168
+ }
169
+ this.ended = true;
170
+ const endMs = Math.max(this.startMs, timeInputMs(endTime));
171
+ this.sink({
172
+ traceId: this.ctx.traceId,
173
+ spanId: this.ctx.spanId,
174
+ parentSpanId: this.parentSpanId,
175
+ name: this.name,
176
+ // OTLP's SpanKind is the API's plus one (0 is UNSPECIFIED on the wire).
177
+ kind: this.kind + 1,
178
+ startTimeUnixNano: unixNanos(this.startMs),
179
+ endTimeUnixNano: unixNanos(endMs),
180
+ attributes: encodeAttributes(this.attributes),
181
+ droppedAttributesCount: this.droppedAttributes || undefined,
182
+ events: this.events,
183
+ links: this.links,
184
+ status: this.status.message !== undefined
185
+ ? { code: this.status.code, message: this.status.message }
186
+ : { code: this.status.code },
187
+ scope: this.scope,
188
+ });
189
+ }
190
+ isRecording() {
191
+ return !this.ended;
192
+ }
193
+ }
194
+ class NodeTracer {
195
+ scope;
196
+ sink;
197
+ globals;
198
+ ctx;
199
+ constructor(scope, sink, globals, ctx) {
200
+ this.scope = scope;
201
+ this.sink = sink;
202
+ this.globals = globals;
203
+ this.ctx = ctx;
204
+ }
205
+ startSpan(name, options = {}, ctx) {
206
+ const parentCtx = options.root
207
+ ? undefined
208
+ : trace.getSpanContext(ctx ?? this.ctx.active());
209
+ const parent = parentCtx && trace.isSpanContextValid(parentCtx) ? parentCtx : undefined;
210
+ const spanContext = {
211
+ traceId: parent?.traceId ?? hexId(16, ZERO_TRACE),
212
+ spanId: hexId(8, ZERO_SPAN),
213
+ traceFlags: TraceFlags.SAMPLED,
214
+ };
215
+ const span = new RecordingSpan(spanContext, parent?.spanId, name, options.kind ?? SpanKind.INTERNAL, timeInputMs(options.startTime), this.scope, this.sink);
216
+ for (const [k, v] of Object.entries(this.globals())) {
217
+ if (v !== undefined)
218
+ span.setAttribute(k, v);
219
+ }
220
+ if (options.attributes) {
221
+ span.setAttributes(options.attributes);
222
+ }
223
+ if (options.links) {
224
+ span.addLinks(options.links);
225
+ }
226
+ return span;
227
+ }
228
+ startActiveSpan(name, a, b, c) {
229
+ let options = {};
230
+ let parent;
231
+ let fn;
232
+ if (typeof a === "function") {
233
+ fn = a;
234
+ }
235
+ else if (typeof b === "function") {
236
+ options = a ?? {};
237
+ fn = b;
238
+ }
239
+ else {
240
+ options = a ?? {};
241
+ parent = b;
242
+ fn = c;
243
+ }
244
+ const base = parent ?? this.ctx.active();
245
+ const span = this.startSpan(name, options, base);
246
+ const ctx = trace.setSpan(base, span);
247
+ return this.ctx.with(ctx, () => fn(span));
248
+ }
249
+ }
250
+ /** A TracerProvider whose spans go to `sink`. */
251
+ export class NodeTracerProvider {
252
+ sink;
253
+ globals;
254
+ ctx;
255
+ tracers = new Map();
256
+ constructor(sink, globals, ctx) {
257
+ this.sink = sink;
258
+ this.globals = globals;
259
+ this.ctx = ctx;
260
+ }
261
+ getTracer(name, version) {
262
+ const key = `${name}\u001f${version ?? ""}`;
263
+ let t = this.tracers.get(key);
264
+ if (!t) {
265
+ t = new NodeTracer(version ? { name, version } : { name }, this.sink, this.globals, this.ctx);
266
+ this.tracers.set(key, t);
267
+ }
268
+ return t;
269
+ }
270
+ }
271
+ /** The OTel ContextManager over AsyncLocalStorage: context follows async calls. */
272
+ export class AsyncLocalContextManager {
273
+ store = new AsyncLocalStorage();
274
+ active() {
275
+ return this.store.getStore() ?? ROOT_CONTEXT;
276
+ }
277
+ with(context, fn, thisArg, ...args) {
278
+ const cb = thisArg == null ? fn : fn.bind(thisArg);
279
+ return this.store.run(context, cb, ...args);
280
+ }
281
+ bind(context, target) {
282
+ if (typeof target !== "function") {
283
+ return target;
284
+ }
285
+ const self = this;
286
+ const fn = target;
287
+ return function (...args) {
288
+ return self.with(context, () => fn.apply(this, args));
289
+ };
290
+ }
291
+ enable() {
292
+ return this;
293
+ }
294
+ disable() {
295
+ this.store.disable();
296
+ this.store = new AsyncLocalStorage();
297
+ return this;
298
+ }
299
+ }
300
+ /** W3C Trace Context: `traceparent` in and out. */
301
+ export class TraceparentPropagator {
302
+ inject(context, carrier, setter) {
303
+ const sc = trace.getSpanContext(context);
304
+ if (!sc || !trace.isSpanContextValid(sc)) {
305
+ return;
306
+ }
307
+ const flags = (sc.traceFlags & TraceFlags.SAMPLED) === TraceFlags.SAMPLED ? "01" : "00";
308
+ setter.set(carrier, "traceparent", `00-${sc.traceId}-${sc.spanId}-${flags}`);
309
+ }
310
+ extract(context, carrier, getter) {
311
+ const raw = getter.get(carrier, "traceparent");
312
+ const value = (Array.isArray(raw) ? raw[0] : raw)?.trim();
313
+ if (!value || !TRACEPARENT_RE.test(value)) {
314
+ return context;
315
+ }
316
+ const [, traceId, spanId, flags] = value.split("-");
317
+ if (!traceId || !spanId || traceId === ZERO_TRACE || spanId === ZERO_SPAN) {
318
+ return context;
319
+ }
320
+ return trace.setSpanContext(context, {
321
+ traceId,
322
+ spanId,
323
+ traceFlags: parseInt(flags ?? "00", 16) & TraceFlags.SAMPLED,
324
+ isRemote: true,
325
+ });
326
+ }
327
+ fields() {
328
+ return ["traceparent"];
329
+ }
330
+ }
331
+ //# sourceMappingURL=node-trace.js.map