@iii-dev/observability 0.13.0-next.1

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.
package/dist/index.cjs ADDED
@@ -0,0 +1,1547 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ let _opentelemetry_api_logs = require("@opentelemetry/api-logs");
3
+ let _opentelemetry_resources = require("@opentelemetry/resources");
4
+ let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
5
+ let node_crypto = require("node:crypto");
6
+ let _opentelemetry_api = require("@opentelemetry/api");
7
+ let _opentelemetry_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
8
+ let _opentelemetry_sdk_metrics = require("@opentelemetry/sdk-metrics");
9
+ let _opentelemetry_core = require("@opentelemetry/core");
10
+ let _opentelemetry_sdk_trace_node = require("@opentelemetry/sdk-trace-node");
11
+ let _opentelemetry_instrumentation = require("@opentelemetry/instrumentation");
12
+ let _opentelemetry_sdk_logs = require("@opentelemetry/sdk-logs");
13
+ let ws = require("ws");
14
+ let _opentelemetry_otlp_transformer = require("@opentelemetry/otlp-transformer");
15
+ let node_perf_hooks = require("node:perf_hooks");
16
+
17
+ //#region src/telemetry-system/baggage-span-processor.ts
18
+ /** DEFAULT_ALLOWLIST drift across languages would break worker chains;
19
+ * lockstep tests in each SDK pin this constant at CI time. */
20
+ const DEFAULT_ALLOWLIST = [
21
+ "iii.session.id",
22
+ "iii.message.id",
23
+ "iii.function.id"
24
+ ];
25
+ var BaggageSpanProcessor = class {
26
+ allowlist;
27
+ constructor(allowlist = DEFAULT_ALLOWLIST) {
28
+ this.allowlist = allowlist;
29
+ }
30
+ onStart(span, parentContext) {
31
+ if (!span.isRecording()) return;
32
+ const baggage = _opentelemetry_api.propagation.getBaggage(parentContext);
33
+ if (!baggage) return;
34
+ for (const key of this.allowlist) {
35
+ const entry = baggage.getEntry(key);
36
+ if (entry) span.setAttribute(key, entry.value);
37
+ }
38
+ }
39
+ onEnd(_span) {}
40
+ async shutdown() {}
41
+ async forceFlush() {}
42
+ };
43
+
44
+ //#endregion
45
+ //#region src/telemetry-system/types.ts
46
+ const ATTR_SERVICE_VERSION = "service.version";
47
+ const ATTR_SERVICE_NAMESPACE = "service.namespace";
48
+ const ATTR_SERVICE_INSTANCE_ID = "service.instance.id";
49
+ /** Magic prefixes for binary frames over WebSocket */
50
+ const PREFIX_TRACES = "OTLP";
51
+ const PREFIX_METRICS = "MTRC";
52
+ const PREFIX_LOGS = "LOGS";
53
+ /** Default reconnection configuration */
54
+ const DEFAULT_RECONNECTION_CONFIG = {
55
+ initialDelayMs: 1e3,
56
+ maxDelayMs: 3e4,
57
+ backoffMultiplier: 2,
58
+ jitterFactor: .3,
59
+ maxRetries: -1
60
+ };
61
+ /** Default configuration values for OpenTelemetry initialization. */
62
+ const DEFAULT_OTEL_CONFIG = {
63
+ enabled: true,
64
+ serviceName: "iii-node",
65
+ serviceVersion: "unknown",
66
+ engineWsUrl: "ws://localhost:49134",
67
+ metricsEnabled: true,
68
+ metricsExportIntervalMs: 6e4,
69
+ logsFlushIntervalMs: 100,
70
+ logsBatchSize: 1,
71
+ fetchInstrumentationEnabled: true
72
+ };
73
+ /** Parse a boolean environment variable, recognizing 'false', '0', 'no', 'off' as false. */
74
+ function parseBoolEnv(value, defaultValue) {
75
+ if (value === void 0) return defaultValue;
76
+ const lower = value.toLowerCase();
77
+ return lower !== "false" && lower !== "0" && lower !== "no" && lower !== "off";
78
+ }
79
+
80
+ //#endregion
81
+ //#region src/telemetry-system/connection.ts
82
+ /**
83
+ * Shared WebSocket connection for OpenTelemetry exporters.
84
+ */
85
+ /**
86
+ * Shared WebSocket connection for all OTEL exporters (traces, metrics, logs).
87
+ * Uses a single connection with message prefixes to identify signal type.
88
+ */
89
+ var SharedEngineConnection = class SharedEngineConnection {
90
+ static MAX_PENDING_MESSAGES = 1e3;
91
+ ws = null;
92
+ wsUrl;
93
+ connecting = false;
94
+ shuttingDown = false;
95
+ pendingMessages = [];
96
+ reconnectAttempt = 0;
97
+ reconnectTimeout = null;
98
+ config;
99
+ state = "disconnected";
100
+ onConnectedCallbacks = [];
101
+ onFailedCallbacks = [];
102
+ constructor(wsUrl, config = {}) {
103
+ this.wsUrl = wsUrl;
104
+ this.config = {
105
+ ...DEFAULT_RECONNECTION_CONFIG,
106
+ ...config
107
+ };
108
+ this.connect();
109
+ }
110
+ connect() {
111
+ if (this.connecting || this.ws && this.ws.readyState === ws.WebSocket.OPEN) return;
112
+ this.connecting = true;
113
+ this.state = "connecting";
114
+ try {
115
+ this.ws = new ws.WebSocket(this.wsUrl);
116
+ this.ws.on("open", () => {
117
+ this.connecting = false;
118
+ this.state = "connected";
119
+ console.log(`[OTel] Connected to engine at ${this.wsUrl}`);
120
+ if (this.reconnectAttempt > 0) console.log("[OTel] Successfully reconnected");
121
+ this.reconnectAttempt = 0;
122
+ if (this.reconnectTimeout) {
123
+ clearTimeout(this.reconnectTimeout);
124
+ this.reconnectTimeout = null;
125
+ }
126
+ const pending = this.pendingMessages.splice(0, this.pendingMessages.length);
127
+ for (const { frame, callback } of pending) this.ws?.send(frame, (err) => callback?.(err));
128
+ for (const cb of this.onConnectedCallbacks) cb();
129
+ });
130
+ this.ws.on("close", () => {
131
+ this.connecting = false;
132
+ this.ws = null;
133
+ if (this.shuttingDown) {
134
+ this.state = "disconnected";
135
+ console.log("[OTel] Connection closed during shutdown");
136
+ return;
137
+ }
138
+ this.state = "disconnected";
139
+ console.log("[OTel] Disconnected from engine, will reconnect...");
140
+ this.scheduleReconnect();
141
+ });
142
+ this.ws.on("error", (err) => {
143
+ this.connecting = false;
144
+ if (this.shuttingDown) return;
145
+ console.error("[OTel] WebSocket error:", err.message);
146
+ });
147
+ } catch (err) {
148
+ this.connecting = false;
149
+ console.error("[OTel] Connection failed:", err);
150
+ this.scheduleReconnect();
151
+ }
152
+ }
153
+ scheduleReconnect() {
154
+ if (this.config.maxRetries !== -1 && this.reconnectAttempt >= this.config.maxRetries) {
155
+ this.state = "failed";
156
+ console.error(`[OTel] Max retries (${this.config.maxRetries}) reached, giving up`);
157
+ const pending = this.pendingMessages.splice(0, this.pendingMessages.length);
158
+ const failedError = /* @__PURE__ */ new Error("Connection failed after max retries");
159
+ for (const { callback } of pending) callback?.(failedError);
160
+ for (const cb of this.onFailedCallbacks) try {
161
+ cb();
162
+ } catch (err) {
163
+ console.error("[OTel] onFailed callback threw:", err);
164
+ }
165
+ return;
166
+ }
167
+ if (this.reconnectTimeout) return;
168
+ const exponentialDelay = this.config.initialDelayMs * this.config.backoffMultiplier ** this.reconnectAttempt;
169
+ const cappedDelay = Math.min(exponentialDelay, this.config.maxDelayMs);
170
+ const jitter = cappedDelay * this.config.jitterFactor * (2 * Math.random() - 1);
171
+ const delay = Math.max(0, Math.floor(cappedDelay + jitter));
172
+ this.state = "reconnecting";
173
+ console.log(`[OTel] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempt + 1})...`);
174
+ this.reconnectTimeout = setTimeout(() => {
175
+ this.reconnectTimeout = null;
176
+ this.reconnectAttempt++;
177
+ this.connect();
178
+ }, delay);
179
+ }
180
+ /**
181
+ * Send a message with a signal prefix.
182
+ */
183
+ send(prefix, data, callback) {
184
+ const prefixBytes = Buffer.from(prefix, "utf-8");
185
+ const frame = Buffer.concat([prefixBytes, Buffer.from(data)]);
186
+ if (this.ws && this.ws.readyState === ws.WebSocket.OPEN) this.ws.send(frame, callback);
187
+ else {
188
+ if (this.pendingMessages.length >= SharedEngineConnection.MAX_PENDING_MESSAGES) {
189
+ console.warn("[OTel] Pending message queue full, dropping oldest message");
190
+ this.pendingMessages.shift()?.callback?.(/* @__PURE__ */ new Error("Message dropped due to queue overflow"));
191
+ }
192
+ this.pendingMessages.push({
193
+ frame,
194
+ callback
195
+ });
196
+ this.connect();
197
+ }
198
+ }
199
+ /**
200
+ * Register a callback to be called when connected.
201
+ */
202
+ onConnected(callback) {
203
+ this.onConnectedCallbacks.push(callback);
204
+ if (this.state === "connected") callback();
205
+ }
206
+ /**
207
+ * Register a callback to be called when the connection enters the failed
208
+ * terminal state (max retries reached). Exporters use this to drain their
209
+ * own pending queues so in-flight forceFlush() calls do not hang.
210
+ */
211
+ onFailed(callback) {
212
+ this.onFailedCallbacks.push(callback);
213
+ if (this.state === "failed") try {
214
+ callback();
215
+ } catch (err) {
216
+ console.error("[OTel] onFailed callback threw:", err);
217
+ }
218
+ }
219
+ /**
220
+ * Get the current connection state.
221
+ */
222
+ getState() {
223
+ return this.state;
224
+ }
225
+ /**
226
+ * Shutdown the connection.
227
+ */
228
+ async shutdown() {
229
+ this.shuttingDown = true;
230
+ if (this.reconnectTimeout) {
231
+ clearTimeout(this.reconnectTimeout);
232
+ this.reconnectTimeout = null;
233
+ }
234
+ if (this.ws) {
235
+ this.ws.close();
236
+ this.ws = null;
237
+ }
238
+ const pending = this.pendingMessages.splice(0, this.pendingMessages.length);
239
+ const shutdownError = /* @__PURE__ */ new Error("Connection shutdown before message could be sent");
240
+ for (const { callback } of pending) callback?.(shutdownError);
241
+ this.onConnectedCallbacks = [];
242
+ this.onFailedCallbacks = [];
243
+ this.state = "disconnected";
244
+ }
245
+ };
246
+
247
+ //#endregion
248
+ //#region src/telemetry-system/span-exporter.ts
249
+ /**
250
+ * Span exporter for the III Engine.
251
+ */
252
+ /**
253
+ * Span exporter using the shared WebSocket connection.
254
+ */
255
+ var EngineSpanExporter = class EngineSpanExporter {
256
+ static MAX_PENDING_EXPORTS = 100;
257
+ connection;
258
+ pendingExports = [];
259
+ constructor(connection) {
260
+ this.connection = connection;
261
+ this.connection.onConnected(() => this.flushPending());
262
+ this.connection.onFailed(() => this.failPending());
263
+ }
264
+ flushPending() {
265
+ const pending = this.pendingExports.splice(0, this.pendingExports.length);
266
+ for (const { spans, resultCallback } of pending) this.sendExport(spans, resultCallback);
267
+ }
268
+ failPending() {
269
+ const pending = this.pendingExports.splice(0, this.pendingExports.length);
270
+ const error = /* @__PURE__ */ new Error("Connection failed: dropping queued spans");
271
+ for (const { resultCallback } of pending) resultCallback?.({
272
+ code: _opentelemetry_core.ExportResultCode.FAILED,
273
+ error
274
+ });
275
+ }
276
+ sendExport(spans, resultCallback) {
277
+ try {
278
+ const serialized = _opentelemetry_otlp_transformer.JsonTraceSerializer.serializeRequest(spans);
279
+ if (!serialized) {
280
+ resultCallback?.({ code: _opentelemetry_core.ExportResultCode.SUCCESS });
281
+ return;
282
+ }
283
+ this.connection.send(PREFIX_TRACES, serialized, (err) => {
284
+ if (err) {
285
+ console.error("[OTel] Failed to send spans:", err.message);
286
+ resultCallback?.({
287
+ code: _opentelemetry_core.ExportResultCode.FAILED,
288
+ error: err
289
+ });
290
+ } else resultCallback?.({ code: _opentelemetry_core.ExportResultCode.SUCCESS });
291
+ });
292
+ } catch (err) {
293
+ console.error("[OTel] Error exporting spans:", err);
294
+ resultCallback?.({
295
+ code: _opentelemetry_core.ExportResultCode.FAILED,
296
+ error: err
297
+ });
298
+ }
299
+ }
300
+ doExport(spans, resultCallback) {
301
+ const state = this.connection.getState();
302
+ if (state === "failed") {
303
+ resultCallback({
304
+ code: _opentelemetry_core.ExportResultCode.FAILED,
305
+ error: /* @__PURE__ */ new Error("Connection failed: dropping spans")
306
+ });
307
+ return;
308
+ }
309
+ if (state !== "connected") {
310
+ if (this.pendingExports.length >= EngineSpanExporter.MAX_PENDING_EXPORTS) {
311
+ this.pendingExports.shift()?.resultCallback?.({
312
+ code: _opentelemetry_core.ExportResultCode.FAILED,
313
+ error: /* @__PURE__ */ new Error("Queue overflow")
314
+ });
315
+ console.warn("[OTel] Spans export queue full, dropped oldest entry");
316
+ }
317
+ this.pendingExports.push({
318
+ spans,
319
+ resultCallback
320
+ });
321
+ return;
322
+ }
323
+ this.sendExport(spans, resultCallback);
324
+ }
325
+ export(spans, resultCallback) {
326
+ this.doExport(spans, resultCallback);
327
+ }
328
+ async shutdown() {
329
+ const pending = this.pendingExports.splice(0, this.pendingExports.length);
330
+ const shutdownError = /* @__PURE__ */ new Error("Exporter shutdown before export completed");
331
+ for (const { resultCallback } of pending) resultCallback?.({
332
+ code: _opentelemetry_core.ExportResultCode.FAILED,
333
+ error: shutdownError
334
+ });
335
+ }
336
+ async forceFlush() {}
337
+ };
338
+
339
+ //#endregion
340
+ //#region src/telemetry-system/metrics-exporter.ts
341
+ /**
342
+ * Metrics exporter for the III Engine.
343
+ */
344
+ /**
345
+ * Metrics exporter using the shared WebSocket connection.
346
+ */
347
+ var EngineMetricsExporter = class EngineMetricsExporter {
348
+ static MAX_PENDING_EXPORTS = 100;
349
+ connection;
350
+ pendingExports = [];
351
+ constructor(connection) {
352
+ this.connection = connection;
353
+ this.connection.onConnected(() => this.flushPending());
354
+ this.connection.onFailed(() => this.failPending());
355
+ }
356
+ flushPending() {
357
+ const pending = this.pendingExports.splice(0, this.pendingExports.length);
358
+ for (const { metrics, resultCallback } of pending) this.sendExport(metrics, resultCallback);
359
+ }
360
+ failPending() {
361
+ const pending = this.pendingExports.splice(0, this.pendingExports.length);
362
+ const error = /* @__PURE__ */ new Error("Connection failed: dropping queued metrics");
363
+ for (const { resultCallback } of pending) resultCallback?.({
364
+ code: _opentelemetry_core.ExportResultCode.FAILED,
365
+ error
366
+ });
367
+ }
368
+ sendExport(metricsData, resultCallback) {
369
+ try {
370
+ const serialized = _opentelemetry_otlp_transformer.JsonMetricsSerializer.serializeRequest(metricsData);
371
+ if (!serialized) {
372
+ resultCallback?.({ code: _opentelemetry_core.ExportResultCode.SUCCESS });
373
+ return;
374
+ }
375
+ this.connection.send(PREFIX_METRICS, serialized, (err) => {
376
+ if (err) {
377
+ console.error("[OTel] Failed to send metrics:", err.message);
378
+ resultCallback?.({
379
+ code: _opentelemetry_core.ExportResultCode.FAILED,
380
+ error: err
381
+ });
382
+ } else resultCallback?.({ code: _opentelemetry_core.ExportResultCode.SUCCESS });
383
+ });
384
+ } catch (err) {
385
+ console.error("[OTel] Error exporting metrics:", err);
386
+ resultCallback?.({
387
+ code: _opentelemetry_core.ExportResultCode.FAILED,
388
+ error: err
389
+ });
390
+ }
391
+ }
392
+ doExport(metricsData, resultCallback) {
393
+ const state = this.connection.getState();
394
+ if (state === "failed") {
395
+ resultCallback({
396
+ code: _opentelemetry_core.ExportResultCode.FAILED,
397
+ error: /* @__PURE__ */ new Error("Connection failed: dropping metrics")
398
+ });
399
+ return;
400
+ }
401
+ if (state !== "connected") {
402
+ if (this.pendingExports.length >= EngineMetricsExporter.MAX_PENDING_EXPORTS) {
403
+ this.pendingExports.shift()?.resultCallback?.({
404
+ code: _opentelemetry_core.ExportResultCode.FAILED,
405
+ error: /* @__PURE__ */ new Error("Queue overflow")
406
+ });
407
+ console.warn("[OTel] Metrics export queue full, dropped oldest entry");
408
+ }
409
+ this.pendingExports.push({
410
+ metrics: metricsData,
411
+ resultCallback
412
+ });
413
+ return;
414
+ }
415
+ this.sendExport(metricsData, resultCallback);
416
+ }
417
+ export(metrics, resultCallback) {
418
+ this.doExport(metrics, resultCallback);
419
+ }
420
+ async shutdown() {
421
+ const pending = this.pendingExports.splice(0, this.pendingExports.length);
422
+ const shutdownError = /* @__PURE__ */ new Error("Exporter shutdown before export completed");
423
+ for (const { resultCallback } of pending) resultCallback?.({
424
+ code: _opentelemetry_core.ExportResultCode.FAILED,
425
+ error: shutdownError
426
+ });
427
+ }
428
+ async forceFlush() {}
429
+ };
430
+
431
+ //#endregion
432
+ //#region src/telemetry-system/log-exporter.ts
433
+ /**
434
+ * Log exporter for the III Engine.
435
+ */
436
+ /**
437
+ * Log exporter using the shared WebSocket connection.
438
+ */
439
+ var EngineLogExporter = class EngineLogExporter {
440
+ static MAX_PENDING_EXPORTS = 100;
441
+ connection;
442
+ pendingExports = [];
443
+ constructor(connection) {
444
+ this.connection = connection;
445
+ this.connection.onConnected(() => this.flushPending());
446
+ this.connection.onFailed(() => this.failPending());
447
+ }
448
+ flushPending() {
449
+ const pending = this.pendingExports.splice(0, this.pendingExports.length);
450
+ for (const { logs, callback } of pending) this.doExport(logs, callback);
451
+ }
452
+ failPending() {
453
+ const pending = this.pendingExports.splice(0, this.pendingExports.length);
454
+ const error = /* @__PURE__ */ new Error("Connection failed: dropping queued logs");
455
+ for (const { callback } of pending) callback({
456
+ code: _opentelemetry_core.ExportResultCode.FAILED,
457
+ error
458
+ });
459
+ }
460
+ doExport(logs, resultCallback) {
461
+ const state = this.connection.getState();
462
+ if (state === "failed") {
463
+ resultCallback({
464
+ code: _opentelemetry_core.ExportResultCode.FAILED,
465
+ error: /* @__PURE__ */ new Error("Connection failed: dropping logs")
466
+ });
467
+ return;
468
+ }
469
+ if (state !== "connected") {
470
+ if (this.pendingExports.length >= EngineLogExporter.MAX_PENDING_EXPORTS) {
471
+ this.pendingExports.shift()?.callback({
472
+ code: _opentelemetry_core.ExportResultCode.FAILED,
473
+ error: /* @__PURE__ */ new Error("Logs export queue full")
474
+ });
475
+ console.warn("[OTel] Logs export queue full, dropped oldest entry");
476
+ }
477
+ this.pendingExports.push({
478
+ logs,
479
+ callback: resultCallback
480
+ });
481
+ return;
482
+ }
483
+ try {
484
+ const serialized = _opentelemetry_otlp_transformer.JsonLogsSerializer.serializeRequest(logs);
485
+ if (!serialized) {
486
+ resultCallback({ code: _opentelemetry_core.ExportResultCode.SUCCESS });
487
+ return;
488
+ }
489
+ this.connection.send(PREFIX_LOGS, serialized, (err) => {
490
+ if (err) {
491
+ console.error("[OTel] Failed to send logs:", err.message);
492
+ resultCallback({
493
+ code: _opentelemetry_core.ExportResultCode.FAILED,
494
+ error: err
495
+ });
496
+ } else resultCallback({ code: _opentelemetry_core.ExportResultCode.SUCCESS });
497
+ });
498
+ } catch (err) {
499
+ console.error("[OTel] Error exporting logs:", err);
500
+ resultCallback({
501
+ code: _opentelemetry_core.ExportResultCode.FAILED,
502
+ error: err
503
+ });
504
+ }
505
+ }
506
+ export(logs, resultCallback) {
507
+ this.doExport(logs, resultCallback);
508
+ }
509
+ async forceFlush() {}
510
+ async shutdown() {
511
+ for (const { callback } of this.pendingExports) callback({
512
+ code: _opentelemetry_core.ExportResultCode.FAILED,
513
+ error: /* @__PURE__ */ new Error("Exporter shutdown")
514
+ });
515
+ this.pendingExports = [];
516
+ }
517
+ };
518
+
519
+ //#endregion
520
+ //#region src/telemetry-system/context.ts
521
+ /**
522
+ * Trace context and baggage propagation utilities.
523
+ */
524
+ /**
525
+ * Extract the current trace ID from the active span context.
526
+ */
527
+ function currentTraceId() {
528
+ const span = _opentelemetry_api.trace.getActiveSpan();
529
+ if (span) {
530
+ const spanContext = span.spanContext();
531
+ if (spanContext.traceId && spanContext.traceId !== "00000000000000000000000000000000") return spanContext.traceId;
532
+ }
533
+ }
534
+ /**
535
+ * Extract the current span ID from the active span context.
536
+ */
537
+ function currentSpanId() {
538
+ const span = _opentelemetry_api.trace.getActiveSpan();
539
+ if (span) {
540
+ const spanContext = span.spanContext();
541
+ if (spanContext.spanId && spanContext.spanId !== "0000000000000000") return spanContext.spanId;
542
+ }
543
+ }
544
+ /**
545
+ * Inject the current trace context into a W3C traceparent header string.
546
+ */
547
+ function injectTraceparent() {
548
+ const carrier = {};
549
+ _opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), carrier);
550
+ return carrier.traceparent;
551
+ }
552
+ /**
553
+ * Extract a trace context from a W3C traceparent header string.
554
+ */
555
+ function extractTraceparent(traceparent) {
556
+ const carrier = { traceparent };
557
+ return _opentelemetry_api.propagation.extract(_opentelemetry_api.context.active(), carrier);
558
+ }
559
+ /**
560
+ * Inject the current baggage into a W3C baggage header string.
561
+ */
562
+ function injectBaggage() {
563
+ const carrier = {};
564
+ _opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), carrier);
565
+ return carrier.baggage;
566
+ }
567
+ /**
568
+ * Extract baggage from a W3C baggage header string.
569
+ */
570
+ function extractBaggage(baggage) {
571
+ const carrier = { baggage };
572
+ return _opentelemetry_api.propagation.extract(_opentelemetry_api.context.active(), carrier);
573
+ }
574
+ /**
575
+ * Extract both trace context and baggage from their respective headers.
576
+ */
577
+ function extractContext(traceparent, baggage) {
578
+ const carrier = {};
579
+ if (traceparent) carrier.traceparent = traceparent;
580
+ if (baggage) carrier.baggage = baggage;
581
+ return _opentelemetry_api.propagation.extract(_opentelemetry_api.context.active(), carrier);
582
+ }
583
+ /**
584
+ * Get a baggage entry from the current context.
585
+ */
586
+ function getBaggageEntry(key) {
587
+ return _opentelemetry_api.propagation.getBaggage(_opentelemetry_api.context.active())?.getEntry(key)?.value;
588
+ }
589
+ /**
590
+ * Set a baggage entry in the current context.
591
+ */
592
+ function setBaggageEntry(key, value) {
593
+ let bag = _opentelemetry_api.propagation.getBaggage(_opentelemetry_api.context.active()) ?? _opentelemetry_api.propagation.createBaggage();
594
+ bag = bag.setEntry(key, { value });
595
+ return _opentelemetry_api.propagation.setBaggage(_opentelemetry_api.context.active(), bag);
596
+ }
597
+ /**
598
+ * Remove a baggage entry from the current context.
599
+ */
600
+ function removeBaggageEntry(key) {
601
+ const bag = _opentelemetry_api.propagation.getBaggage(_opentelemetry_api.context.active());
602
+ if (!bag) return _opentelemetry_api.context.active();
603
+ const newBag = bag.removeEntry(key);
604
+ return _opentelemetry_api.propagation.setBaggage(_opentelemetry_api.context.active(), newBag);
605
+ }
606
+ /**
607
+ * Get all baggage entries from the current context.
608
+ */
609
+ function getAllBaggage() {
610
+ const bag = _opentelemetry_api.propagation.getBaggage(_opentelemetry_api.context.active());
611
+ if (!bag) return {};
612
+ const entries = {};
613
+ for (const [key, entry] of bag.getAllEntries()) entries[key] = entry.value;
614
+ return entries;
615
+ }
616
+
617
+ //#endregion
618
+ //#region src/telemetry-system/fetch-instrumentation.ts
619
+ /**
620
+ * Global fetch auto-instrumentation for the III Node SDK.
621
+ *
622
+ * Patches globalThis.fetch to create OTel CLIENT spans for every HTTP request.
623
+ * Works on all runtimes (Bun, Node.js, Deno) unlike UndiciInstrumentation
624
+ * which only works when fetch is backed by Node.js's undici.
625
+ */
626
+ const textEncoder = new TextEncoder();
627
+ function getBodyByteSize(body) {
628
+ if (body == null) return void 0;
629
+ if (typeof body === "string") return textEncoder.encode(body).byteLength;
630
+ if (body instanceof ArrayBuffer) return body.byteLength;
631
+ if (ArrayBuffer.isView(body)) return body.byteLength;
632
+ if (body instanceof Blob) return body.size;
633
+ if (body instanceof URLSearchParams) return new TextEncoder().encode(body.toString()).byteLength;
634
+ }
635
+ const SAFE_REQUEST_HEADERS$1 = ["content-type", "accept"];
636
+ const SAFE_RESPONSE_HEADERS$1 = ["content-type"];
637
+ let originalFetch = null;
638
+ /**
639
+ * Patch globalThis.fetch to create OTel CLIENT spans for every HTTP request.
640
+ */
641
+ function patchGlobalFetch(tracer) {
642
+ if (originalFetch) return;
643
+ originalFetch = globalThis.fetch;
644
+ const capturedFetch = originalFetch;
645
+ globalThis.fetch = async (input, init) => {
646
+ const url = input instanceof Request ? input.url : String(input);
647
+ const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
648
+ let host;
649
+ let scheme;
650
+ let path;
651
+ let port;
652
+ let query;
653
+ try {
654
+ const parsed = new URL(url);
655
+ host = parsed.hostname;
656
+ scheme = parsed.protocol.replace(":", "");
657
+ path = parsed.pathname;
658
+ port = parsed.port ? parseInt(parsed.port, 10) : void 0;
659
+ query = parsed.search ? parsed.search.slice(1) : void 0;
660
+ } catch {}
661
+ const spanAttributes = {
662
+ "http.request.method": method,
663
+ "url.full": url
664
+ };
665
+ if (host) spanAttributes["server.address"] = host;
666
+ if (scheme) {
667
+ spanAttributes["url.scheme"] = scheme;
668
+ spanAttributes["network.protocol.name"] = "http";
669
+ }
670
+ if (path) spanAttributes["url.path"] = path;
671
+ if (port) spanAttributes["server.port"] = port;
672
+ if (query) spanAttributes["url.query"] = query;
673
+ const spanName = path ? `${method} ${path}` : method;
674
+ return tracer.startActiveSpan(spanName, {
675
+ kind: _opentelemetry_api.SpanKind.CLIENT,
676
+ attributes: spanAttributes
677
+ }, _opentelemetry_api.context.active(), async (span) => {
678
+ try {
679
+ const carrier = {};
680
+ _opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), carrier);
681
+ const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
682
+ for (const [key, value] of Object.entries(carrier)) headers.set(key, value);
683
+ for (const name of SAFE_REQUEST_HEADERS$1) {
684
+ const value = headers.get(name);
685
+ if (value !== null) span.setAttribute(`http.request.header.${name}`, value);
686
+ }
687
+ const requestBodySize = getBodyByteSize(init?.body ?? (input instanceof Request ? input.body : void 0));
688
+ if (requestBodySize !== void 0) span.setAttribute("http.request.body.size", requestBodySize);
689
+ const response = await capturedFetch(input, {
690
+ ...init,
691
+ headers
692
+ });
693
+ span.setAttribute("http.response.status_code", response.status);
694
+ const contentLength = response.headers.get("content-length");
695
+ if (contentLength !== null) {
696
+ const size = parseInt(contentLength, 10);
697
+ if (!Number.isNaN(size)) span.setAttribute("http.response.body.size", size);
698
+ }
699
+ for (const name of SAFE_RESPONSE_HEADERS$1) {
700
+ const value = response.headers.get(name);
701
+ if (value !== null) span.setAttribute(`http.response.header.${name}`, value);
702
+ }
703
+ if (response.status >= 400) {
704
+ span.setAttribute("error.type", String(response.status));
705
+ span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
706
+ } else span.setStatus({ code: _opentelemetry_api.SpanStatusCode.OK });
707
+ return response;
708
+ } catch (error) {
709
+ span.setAttribute("error.type", error.name ?? "Error");
710
+ span.setStatus({
711
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
712
+ message: error.message
713
+ });
714
+ span.recordException(error);
715
+ throw error;
716
+ } finally {
717
+ span.end();
718
+ }
719
+ });
720
+ };
721
+ }
722
+ /**
723
+ * Restore globalThis.fetch to its original implementation.
724
+ */
725
+ function unpatchGlobalFetch() {
726
+ if (originalFetch) {
727
+ globalThis.fetch = originalFetch;
728
+ originalFetch = null;
729
+ }
730
+ }
731
+
732
+ //#endregion
733
+ //#region src/telemetry-system/utils.ts
734
+ /**
735
+ * Parse a numeric environment variable with optional minimum bound.
736
+ */
737
+ function parseNumberEnv(value, minimum = 0) {
738
+ if (value === void 0) return void 0;
739
+ const parsed = Number(value);
740
+ if (!Number.isFinite(parsed) || parsed < minimum) return void 0;
741
+ return parsed;
742
+ }
743
+ /**
744
+ * Parse an integer environment variable with optional minimum bound.
745
+ */
746
+ function parseIntegerEnv(value, minimum = 0) {
747
+ const parsed = parseNumberEnv(value, minimum);
748
+ if (parsed === void 0 || !Number.isInteger(parsed)) return void 0;
749
+ return parsed;
750
+ }
751
+
752
+ //#endregion
753
+ //#region src/telemetry-system/span-ops.ts
754
+ /** High-level span operations so consumers don't need `@opentelemetry/api`. */
755
+ /** Returns `false` when there is no active span or the sampler dropped it. */
756
+ function currentSpanIsRecording() {
757
+ const span = _opentelemetry_api.trace.getActiveSpan();
758
+ return span ? span.isRecording() : false;
759
+ }
760
+ /** No-op when the current span is not recording. */
761
+ function setCurrentSpanAttribute(key, value) {
762
+ const span = _opentelemetry_api.trace.getActiveSpan();
763
+ if (!span || !span.isRecording()) return;
764
+ span.setAttribute(key, value);
765
+ }
766
+ /** No-op when there is no active span. */
767
+ function setCurrentSpanError(message) {
768
+ const span = _opentelemetry_api.trace.getActiveSpan();
769
+ if (!span) return;
770
+ span.setStatus({
771
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
772
+ message
773
+ });
774
+ }
775
+ /** No-op when the current span is not recording. */
776
+ function recordSpanEvent(name, attrs) {
777
+ const span = _opentelemetry_api.trace.getActiveSpan();
778
+ if (!span || !span.isRecording()) return;
779
+ span.addEvent(name, attrs);
780
+ }
781
+
782
+ //#endregion
783
+ //#region src/telemetry-system/payload.ts
784
+ /** Payload redaction + truncation for invocation event capture. */
785
+ const REDACTED_PLACEHOLDER = "[REDACTED]";
786
+ const TRUNCATION_MARKER = "...\"[TRUNCATED]\"";
787
+ function resolveMaxBytesFromEnv() {
788
+ const raw = process.env.III_TRACE_PAYLOAD_MAX_BYTES;
789
+ if (raw === void 0) return null;
790
+ const trimmed = raw.trim();
791
+ if (trimmed === "" || trimmed.toLowerCase() === "unlimited") return null;
792
+ if (!/^\d+$/.test(trimmed)) return null;
793
+ const parsed = Number(trimmed);
794
+ if (parsed <= 0) return null;
795
+ return parsed;
796
+ }
797
+ const SENSITIVE_FRAGMENTS = [
798
+ "api_key",
799
+ "apikey",
800
+ "api-key",
801
+ "password",
802
+ "secret",
803
+ "credential",
804
+ "authorization",
805
+ "auth_token",
806
+ "access_token",
807
+ "refresh_token",
808
+ "bearer",
809
+ "private_key",
810
+ "client_secret"
811
+ ];
812
+ function isSensitiveKey(key) {
813
+ const lower = key.toLowerCase();
814
+ if (SENSITIVE_FRAGMENTS.some((fragment) => lower.includes(fragment))) return true;
815
+ return lower === "token" || lower.endsWith("_token") || lower.endsWith("-token");
816
+ }
817
+ /** Recursively redact values of sensitive keys. Returns a new value. */
818
+ function redact(value) {
819
+ if (value === null || value === void 0) return value;
820
+ if (Array.isArray(value)) return value.map(redact);
821
+ if (typeof value === "object") {
822
+ const out = {};
823
+ for (const [k, v] of Object.entries(value)) out[k] = isSensitiveKey(k) ? REDACTED_PLACEHOLDER : redact(v);
824
+ return out;
825
+ }
826
+ return value;
827
+ }
828
+ /** Redact then serialize to JSON, optionally capped at `maxBytes`. */
829
+ function redactAndTruncate(value, maxBytes = null) {
830
+ const redacted = redact(value);
831
+ let serialized;
832
+ try {
833
+ serialized = JSON.stringify(redacted) ?? "null";
834
+ } catch {
835
+ serialized = "null";
836
+ }
837
+ if (maxBytes === null || maxBytes === void 0 || maxBytes <= 0) return {
838
+ json: serialized,
839
+ truncated: false
840
+ };
841
+ if (Buffer.byteLength(serialized, "utf8") <= maxBytes) return {
842
+ json: serialized,
843
+ truncated: false
844
+ };
845
+ const markerLen = Buffer.byteLength(TRUNCATION_MARKER, "utf8");
846
+ if (maxBytes <= markerLen) return {
847
+ json: TRUNCATION_MARKER.slice(0, maxBytes),
848
+ truncated: true
849
+ };
850
+ const cap = maxBytes - markerLen;
851
+ const buf = Buffer.from(serialized, "utf8");
852
+ let cut = Math.min(cap, buf.length);
853
+ while (cut > 0 && (buf[cut] & 192) === 128) cut -= 1;
854
+ return {
855
+ json: buf.subarray(0, cut).toString("utf8") + TRUNCATION_MARKER,
856
+ truncated: true
857
+ };
858
+ }
859
+
860
+ //#endregion
861
+ //#region src/telemetry-system/index.ts
862
+ /**
863
+ * OpenTelemetry initialization for the III Node SDK.
864
+ *
865
+ * This module provides trace, metrics, and log export to the III Engine
866
+ * via a shared WebSocket connection using OTLP JSON format.
867
+ */
868
+ /**
869
+ * Normalize an engine WebSocket URL into the dedicated OTEL endpoint.
870
+ * The engine exposes `/otel` for telemetry-only WS connections; routing
871
+ * there keeps this socket out of the worker registry (otherwise it shows
872
+ * up as a ghost null-metadata worker).
873
+ */
874
+ function appendOtelPath(base) {
875
+ const url = new URL(base);
876
+ const path = url.pathname.replace(/\/+$/, "");
877
+ url.pathname = path.endsWith("/otel") ? path : `${path}/otel`;
878
+ return url.toString();
879
+ }
880
+ let sharedConnection = null;
881
+ let tracerProvider = null;
882
+ let meterProvider = null;
883
+ let loggerProvider = null;
884
+ let tracer = null;
885
+ let meter = null;
886
+ let logger = null;
887
+ let serviceName = "iii-node-iii";
888
+ /**
889
+ * Initialize OpenTelemetry with the given configuration.
890
+ * This should be called once at application startup.
891
+ */
892
+ function initOtel(config = {}) {
893
+ if (!(config.enabled ?? parseBoolEnv(process.env.OTEL_ENABLED, DEFAULT_OTEL_CONFIG.enabled))) {
894
+ console.debug("[OTel] OpenTelemetry is disabled. To enable, remove OTEL_ENABLED=false or set enabled: true in config.");
895
+ return;
896
+ }
897
+ serviceName = config.serviceName ?? process.env.OTEL_SERVICE_NAME ?? DEFAULT_OTEL_CONFIG.serviceName;
898
+ const serviceVersion = config.serviceVersion ?? process.env.SERVICE_VERSION ?? DEFAULT_OTEL_CONFIG.serviceVersion;
899
+ const serviceNamespace = config.serviceNamespace ?? process.env.SERVICE_NAMESPACE;
900
+ const serviceInstanceId = config.serviceInstanceId ?? process.env.SERVICE_INSTANCE_ID ?? (0, node_crypto.randomUUID)();
901
+ const engineWsUrl = config.engineWsUrl ?? process.env.III_URL ?? DEFAULT_OTEL_CONFIG.engineWsUrl;
902
+ const resourceAttributes = {
903
+ [_opentelemetry_semantic_conventions.ATTR_SERVICE_NAME]: serviceName,
904
+ [ATTR_SERVICE_VERSION]: serviceVersion,
905
+ [ATTR_SERVICE_INSTANCE_ID]: serviceInstanceId
906
+ };
907
+ if (serviceNamespace) resourceAttributes[ATTR_SERVICE_NAMESPACE] = serviceNamespace;
908
+ const resource = new _opentelemetry_resources.Resource(resourceAttributes);
909
+ sharedConnection = new SharedEngineConnection(appendOtelPath(engineWsUrl), config.reconnectionConfig);
910
+ const spanExporter = new EngineSpanExporter(sharedConnection);
911
+ tracerProvider = new _opentelemetry_sdk_trace_node.NodeTracerProvider({
912
+ resource,
913
+ spanProcessors: [new BaggageSpanProcessor(), new _opentelemetry_sdk_trace_base.BatchSpanProcessor(spanExporter)]
914
+ });
915
+ _opentelemetry_api.propagation.setGlobalPropagator(new _opentelemetry_core.CompositePropagator({ propagators: [new _opentelemetry_core.W3CTraceContextPropagator(), new _opentelemetry_core.W3CBaggagePropagator()] }));
916
+ tracerProvider.register();
917
+ tracer = _opentelemetry_api.trace.getTracer(serviceName);
918
+ console.debug(`[OTel] Traces initialized: engine=${engineWsUrl}, service=${serviceName}`);
919
+ if (config.metricsEnabled ?? parseBoolEnv(process.env.OTEL_METRICS_ENABLED, DEFAULT_OTEL_CONFIG.metricsEnabled)) {
920
+ const metricsExporter = new EngineMetricsExporter(sharedConnection);
921
+ const exportIntervalMs = config.metricsExportIntervalMs ?? DEFAULT_OTEL_CONFIG.metricsExportIntervalMs;
922
+ meterProvider = new _opentelemetry_sdk_metrics.MeterProvider({
923
+ resource,
924
+ readers: [new _opentelemetry_sdk_metrics.PeriodicExportingMetricReader({
925
+ exporter: metricsExporter,
926
+ exportIntervalMillis: exportIntervalMs
927
+ })]
928
+ });
929
+ _opentelemetry_api.metrics.setGlobalMeterProvider(meterProvider);
930
+ meter = meterProvider.getMeter(serviceName);
931
+ console.debug(`[OTel] Metrics initialized: interval=${exportIntervalMs}ms`);
932
+ }
933
+ const instrumentations = [...config.instrumentations ?? []];
934
+ if (instrumentations.length > 0) {
935
+ (0, _opentelemetry_instrumentation.registerInstrumentations)({
936
+ instrumentations,
937
+ tracerProvider,
938
+ meterProvider: meterProvider ?? void 0
939
+ });
940
+ console.debug(`[OTel] Instrumentations registered: ${instrumentations.length} total`);
941
+ }
942
+ if (config.fetchInstrumentationEnabled ?? DEFAULT_OTEL_CONFIG.fetchInstrumentationEnabled) {
943
+ patchGlobalFetch(tracer);
944
+ console.debug("[OTel] Global fetch instrumentation enabled");
945
+ }
946
+ const logExporter = new EngineLogExporter(sharedConnection);
947
+ const logsScheduledDelayMillis = config.logsFlushIntervalMs ?? parseNumberEnv(process.env.OTEL_LOGS_FLUSH_INTERVAL_MS, 0) ?? DEFAULT_OTEL_CONFIG.logsFlushIntervalMs;
948
+ const logsMaxExportBatchSize = config.logsBatchSize ?? parseIntegerEnv(process.env.OTEL_LOGS_BATCH_SIZE, 1) ?? DEFAULT_OTEL_CONFIG.logsBatchSize;
949
+ loggerProvider = new _opentelemetry_sdk_logs.LoggerProvider({ resource });
950
+ loggerProvider.addLogRecordProcessor(new _opentelemetry_sdk_logs.BatchLogRecordProcessor(logExporter, {
951
+ scheduledDelayMillis: logsScheduledDelayMillis,
952
+ maxExportBatchSize: logsMaxExportBatchSize
953
+ }));
954
+ logger = loggerProvider.getLogger(serviceName);
955
+ console.debug(`[OTel] Logs initialized: delay=${logsScheduledDelayMillis}ms, batch=${logsMaxExportBatchSize}`);
956
+ }
957
+ /**
958
+ * Shutdown OpenTelemetry, flushing any pending data.
959
+ */
960
+ async function shutdownOtel() {
961
+ if (tracerProvider) {
962
+ await tracerProvider.forceFlush();
963
+ await tracerProvider.shutdown();
964
+ tracerProvider = null;
965
+ }
966
+ if (meterProvider) {
967
+ await meterProvider.forceFlush();
968
+ await meterProvider.shutdown();
969
+ meterProvider = null;
970
+ }
971
+ if (loggerProvider) {
972
+ await loggerProvider.forceFlush();
973
+ await loggerProvider.shutdown();
974
+ loggerProvider = null;
975
+ }
976
+ if (sharedConnection) {
977
+ await sharedConnection.shutdown();
978
+ sharedConnection = null;
979
+ }
980
+ unpatchGlobalFetch();
981
+ tracer = null;
982
+ meter = null;
983
+ logger = null;
984
+ }
985
+ /**
986
+ * Force-flush all OTel providers without tearing them down.
987
+ *
988
+ * Counterpart to {@link shutdownOtel}. Use before short-lived process exits
989
+ * where you want pending spans/metrics/logs delivered but plan to keep using
990
+ * OTel afterwards.
991
+ */
992
+ async function flushOtel() {
993
+ await Promise.all([
994
+ tracerProvider?.forceFlush(),
995
+ meterProvider?.forceFlush(),
996
+ loggerProvider?.forceFlush()
997
+ ].filter(Boolean));
998
+ }
999
+ /**
1000
+ * Get the OpenTelemetry tracer instance.
1001
+ */
1002
+ function getTracer() {
1003
+ return tracer;
1004
+ }
1005
+ /**
1006
+ * Get the OpenTelemetry meter instance.
1007
+ */
1008
+ function getMeter() {
1009
+ return meter;
1010
+ }
1011
+ /**
1012
+ * Get the OpenTelemetry logger instance.
1013
+ */
1014
+ function getLogger() {
1015
+ return logger;
1016
+ }
1017
+ /**
1018
+ * Start a new span with the given name and run the callback within it.
1019
+ */
1020
+ async function withSpan(name, options, fn) {
1021
+ if (!tracer) {
1022
+ const noopSpan = {
1023
+ spanContext: () => ({
1024
+ traceId: "",
1025
+ spanId: "",
1026
+ traceFlags: 0
1027
+ }),
1028
+ setAttribute: () => noopSpan,
1029
+ setAttributes: () => noopSpan,
1030
+ addEvent: () => noopSpan,
1031
+ addLink: () => noopSpan,
1032
+ setStatus: () => noopSpan,
1033
+ updateName: () => noopSpan,
1034
+ end: () => {},
1035
+ isRecording: () => false,
1036
+ recordException: () => {},
1037
+ addLinks: () => noopSpan
1038
+ };
1039
+ return fn(noopSpan);
1040
+ }
1041
+ const parentContext = options.traceparent ? extractTraceparent(options.traceparent) : _opentelemetry_api.context.active();
1042
+ return tracer.startActiveSpan(name, { kind: options.kind ?? _opentelemetry_api.SpanKind.INTERNAL }, parentContext, async (span) => {
1043
+ try {
1044
+ const result = await fn(span);
1045
+ span.setStatus({ code: _opentelemetry_api.SpanStatusCode.OK });
1046
+ return result;
1047
+ } catch (error) {
1048
+ span.setStatus({
1049
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
1050
+ message: error.message
1051
+ });
1052
+ span.recordException(error);
1053
+ throw error;
1054
+ } finally {
1055
+ span.end();
1056
+ }
1057
+ });
1058
+ }
1059
+
1060
+ //#endregion
1061
+ //#region src/logger.ts
1062
+ /**
1063
+ * Structured logger that emits logs as OpenTelemetry LogRecords.
1064
+ *
1065
+ * Every log call automatically captures the active trace and span context,
1066
+ * correlating your logs with distributed traces without any manual wiring.
1067
+ * When OTel is not initialized, Logger gracefully falls back to `console.*`.
1068
+ *
1069
+ * Pass structured data as the second argument to any log method. Using an
1070
+ * object of key-value pairs (instead of string interpolation) lets you
1071
+ * filter, aggregate, and build dashboards in your observability backend.
1072
+ *
1073
+ * @example
1074
+ * ```typescript
1075
+ * import { Logger } from 'iii-sdk'
1076
+ *
1077
+ * const logger = new Logger()
1078
+ *
1079
+ * // Basic logging — trace context is injected automatically
1080
+ * logger.info('Worker connected')
1081
+ *
1082
+ * // Structured context for dashboards and alerting
1083
+ * logger.info('Order processed', { orderId: 'ord_123', amount: 49.99, currency: 'USD' })
1084
+ * logger.warn('Retry attempt', { attempt: 3, maxRetries: 5, endpoint: '/api/charge' })
1085
+ * logger.error('Payment failed', { orderId: 'ord_123', gateway: 'stripe', errorCode: 'card_declined' })
1086
+ * ```
1087
+ */
1088
+ var Logger = class {
1089
+ _otelLogger = null;
1090
+ get otelLogger() {
1091
+ if (!this._otelLogger) this._otelLogger = getLogger();
1092
+ return this._otelLogger;
1093
+ }
1094
+ constructor(traceId, serviceName, spanId) {
1095
+ this.traceId = traceId;
1096
+ this.serviceName = serviceName;
1097
+ this.spanId = spanId;
1098
+ }
1099
+ emit(message, severity, data) {
1100
+ const attributes = {};
1101
+ const traceId = this.traceId ?? currentTraceId();
1102
+ const spanId = this.spanId ?? currentSpanId();
1103
+ if (traceId) attributes.trace_id = traceId;
1104
+ if (spanId) attributes.span_id = spanId;
1105
+ if (this.serviceName) attributes["service.name"] = this.serviceName;
1106
+ if (data !== void 0) attributes["log.data"] = data;
1107
+ if (this.otelLogger) this.otelLogger.emit({
1108
+ severityNumber: severity,
1109
+ body: message,
1110
+ attributes: Object.keys(attributes).length > 0 ? attributes : void 0
1111
+ });
1112
+ else switch (severity) {
1113
+ case _opentelemetry_api_logs.SeverityNumber.DEBUG:
1114
+ console.debug(message, data);
1115
+ break;
1116
+ case _opentelemetry_api_logs.SeverityNumber.INFO:
1117
+ console.info(message, data);
1118
+ break;
1119
+ case _opentelemetry_api_logs.SeverityNumber.WARN:
1120
+ console.warn(message, data);
1121
+ break;
1122
+ case _opentelemetry_api_logs.SeverityNumber.ERROR:
1123
+ console.error(message, data);
1124
+ break;
1125
+ default: console.log(message, data);
1126
+ }
1127
+ }
1128
+ /**
1129
+ * Log an info-level message.
1130
+ *
1131
+ * @param message - Human-readable log message.
1132
+ * @param data - Structured context attached as OTel log attributes.
1133
+ * Use key-value objects to enable filtering and aggregation in your
1134
+ * observability backend (e.g. Grafana, Datadog, New Relic).
1135
+ *
1136
+ * @example
1137
+ * ```typescript
1138
+ * logger.info('Order processed', { orderId: 'ord_123', status: 'completed' })
1139
+ * ```
1140
+ */
1141
+ info(message, data) {
1142
+ this.emit(message, _opentelemetry_api_logs.SeverityNumber.INFO, data);
1143
+ }
1144
+ /**
1145
+ * Log a warning-level message.
1146
+ *
1147
+ * @param message - Human-readable log message.
1148
+ * @param data - Structured context attached as OTel log attributes.
1149
+ * Use key-value objects to enable filtering and aggregation in your
1150
+ * observability backend (e.g. Grafana, Datadog, New Relic).
1151
+ *
1152
+ * @example
1153
+ * ```typescript
1154
+ * logger.warn('Retry attempt', { attempt: 3, maxRetries: 5, endpoint: '/api/charge' })
1155
+ * ```
1156
+ */
1157
+ warn(message, data) {
1158
+ this.emit(message, _opentelemetry_api_logs.SeverityNumber.WARN, data);
1159
+ }
1160
+ /**
1161
+ * Log an error-level message.
1162
+ *
1163
+ * @param message - Human-readable log message.
1164
+ * @param data - Structured context attached as OTel log attributes.
1165
+ * Use key-value objects to enable filtering and aggregation in your
1166
+ * observability backend (e.g. Grafana, Datadog, New Relic).
1167
+ *
1168
+ * @example
1169
+ * ```typescript
1170
+ * logger.error('Payment failed', { orderId: 'ord_123', gateway: 'stripe', errorCode: 'card_declined' })
1171
+ * ```
1172
+ */
1173
+ error(message, data) {
1174
+ this.emit(message, _opentelemetry_api_logs.SeverityNumber.ERROR, data);
1175
+ }
1176
+ /**
1177
+ * Log a debug-level message.
1178
+ *
1179
+ * @param message - Human-readable log message.
1180
+ * @param data - Structured context attached as OTel log attributes.
1181
+ * Use key-value objects to enable filtering and aggregation in your
1182
+ * observability backend (e.g. Grafana, Datadog, New Relic).
1183
+ *
1184
+ * @example
1185
+ * ```typescript
1186
+ * logger.debug('Cache lookup', { key: 'user:42', hit: false })
1187
+ * ```
1188
+ */
1189
+ debug(message, data) {
1190
+ this.emit(message, _opentelemetry_api_logs.SeverityNumber.DEBUG, data);
1191
+ }
1192
+ };
1193
+
1194
+ //#endregion
1195
+ //#region src/http-instrumentation.ts
1196
+ const SAFE_REQUEST_HEADERS = ["content-type", "accept"];
1197
+ const SAFE_RESPONSE_HEADERS = ["content-type"];
1198
+ /**
1199
+ * Execute a fetch request inside an OTel CLIENT span.
1200
+ *
1201
+ * Mirrors the Rust execute_traced_request shape: injects W3C traceparent into
1202
+ * outgoing headers, records HTTP semantic-convention attributes, and sets
1203
+ * ERROR span status for HTTP responses with status >= 400 or network errors.
1204
+ */
1205
+ async function executeTracedRequest(input, init) {
1206
+ const tracer = init?.tracer ?? _opentelemetry_api.trace.getTracer("iii-node-sdk");
1207
+ const rawUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
1208
+ let url;
1209
+ try {
1210
+ url = new URL(rawUrl);
1211
+ } catch {
1212
+ url = null;
1213
+ }
1214
+ const method = (init?.method ?? (typeof input === "object" && "method" in input ? input.method : "GET") ?? "GET").toUpperCase();
1215
+ const name = url?.pathname ? `${method} ${url.pathname}` : method;
1216
+ return tracer.startActiveSpan(name, {
1217
+ kind: _opentelemetry_api.SpanKind.CLIENT,
1218
+ attributes: {
1219
+ "http.request.method": method,
1220
+ "url.full": url?.toString() ?? rawUrl,
1221
+ "network.protocol.name": "http",
1222
+ ...url ? {
1223
+ "server.address": url.hostname,
1224
+ "url.scheme": url.protocol.replace(":", ""),
1225
+ "url.path": url.pathname
1226
+ } : {},
1227
+ ...url?.port ? { "server.port": Number(url.port) } : {},
1228
+ ...url?.search ? { "url.query": url.search.slice(1) } : {}
1229
+ }
1230
+ }, async (span) => {
1231
+ try {
1232
+ const baseHeaders = typeof input === "object" && "headers" in input ? input.headers : void 0;
1233
+ const headers = new Headers(baseHeaders);
1234
+ if (init?.headers) for (const [k, v] of new Headers(init.headers).entries()) headers.set(k, v);
1235
+ const carrier = {};
1236
+ _opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), carrier);
1237
+ for (const [k, v] of Object.entries(carrier)) headers.set(k, v);
1238
+ for (const h of SAFE_REQUEST_HEADERS) {
1239
+ const v = headers.get(h);
1240
+ if (v) span.setAttribute(`http.request.header.${h}`, v);
1241
+ }
1242
+ const response = await fetch(input, {
1243
+ ...init,
1244
+ headers
1245
+ });
1246
+ span.setAttribute("http.response.status_code", response.status);
1247
+ const cl = response.headers.get("content-length");
1248
+ if (cl) span.setAttribute("http.response.body.size", Number(cl));
1249
+ for (const h of SAFE_RESPONSE_HEADERS) {
1250
+ const v = response.headers.get(h);
1251
+ if (v) span.setAttribute(`http.response.header.${h}`, v);
1252
+ }
1253
+ if (response.status >= 400) {
1254
+ span.setStatus({
1255
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
1256
+ message: String(response.status)
1257
+ });
1258
+ span.setAttribute("error.type", String(response.status));
1259
+ } else span.setStatus({ code: _opentelemetry_api.SpanStatusCode.OK });
1260
+ return response;
1261
+ } catch (err) {
1262
+ const error = err;
1263
+ span.recordException(error);
1264
+ span.setStatus({
1265
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
1266
+ message: error.message
1267
+ });
1268
+ span.setAttribute("error.type", error.name);
1269
+ throw err;
1270
+ } finally {
1271
+ span.end();
1272
+ }
1273
+ });
1274
+ }
1275
+
1276
+ //#endregion
1277
+ //#region src/worker-metrics.ts
1278
+ /**
1279
+ * Worker metrics collection for the III Node SDK.
1280
+ *
1281
+ * Collects CPU, memory, and event loop metrics for worker health monitoring.
1282
+ * Uses the Node.js built-in `monitorEventLoopDelay` API for accurate
1283
+ * event loop lag measurements.
1284
+ */
1285
+ /**
1286
+ * Collects worker resource metrics including CPU, memory, and event loop lag.
1287
+ *
1288
+ * Uses the Node.js `monitorEventLoopDelay` API for high-precision event loop
1289
+ * delay measurements instead of manual `setImmediate` timing.
1290
+ *
1291
+ * @example
1292
+ * ```typescript
1293
+ * const collector = new WorkerMetricsCollector()
1294
+ *
1295
+ * // Collect metrics periodically
1296
+ * setInterval(() => {
1297
+ * const metrics = collector.collect()
1298
+ * console.log('CPU:', metrics.cpu_percent, '%')
1299
+ * console.log('Event Loop Lag:', metrics.event_loop_lag_ms, 'ms')
1300
+ * }, 5000)
1301
+ *
1302
+ * // Clean up when done
1303
+ * collector.stopMonitoring()
1304
+ * ```
1305
+ */
1306
+ var WorkerMetricsCollector = class {
1307
+ startTime;
1308
+ lastCpuUsage;
1309
+ lastCpuTime;
1310
+ eventLoopHistogram = null;
1311
+ /**
1312
+ * Creates a new WorkerMetricsCollector instance.
1313
+ *
1314
+ * @param options - Configuration options
1315
+ */
1316
+ constructor(options = {}) {
1317
+ this.startTime = Date.now();
1318
+ this.lastCpuUsage = process.cpuUsage();
1319
+ this.lastCpuTime = node_perf_hooks.performance.now();
1320
+ this.startEventLoopMonitoring(options.eventLoopResolutionMs ?? 20);
1321
+ }
1322
+ /**
1323
+ * Starts the event loop delay histogram monitoring.
1324
+ *
1325
+ * @param resolutionMs - Histogram resolution in milliseconds
1326
+ */
1327
+ startEventLoopMonitoring(resolutionMs) {
1328
+ this.eventLoopHistogram = (0, node_perf_hooks.monitorEventLoopDelay)({ resolution: Number.isFinite(resolutionMs) && resolutionMs > 0 ? Math.max(1, Math.floor(resolutionMs)) : 20 });
1329
+ this.eventLoopHistogram.enable();
1330
+ }
1331
+ /**
1332
+ * Stops the event loop monitoring and releases resources.
1333
+ * Should be called when the collector is no longer needed.
1334
+ */
1335
+ stopMonitoring() {
1336
+ if (this.eventLoopHistogram) {
1337
+ this.eventLoopHistogram.disable();
1338
+ this.eventLoopHistogram = null;
1339
+ }
1340
+ }
1341
+ /**
1342
+ * Collects current worker metrics.
1343
+ *
1344
+ * This method calculates CPU usage since the last collection,
1345
+ * reads memory usage, and gets event loop delay statistics.
1346
+ * The event loop histogram is reset after each collection for
1347
+ * accurate per-interval measurements.
1348
+ *
1349
+ * @returns Current worker metrics snapshot
1350
+ */
1351
+ collect() {
1352
+ const memoryUsage = process.memoryUsage();
1353
+ const cpuUsage = process.cpuUsage();
1354
+ const now = node_perf_hooks.performance.now();
1355
+ const cpuDelta = {
1356
+ user: cpuUsage.user - this.lastCpuUsage.user,
1357
+ system: cpuUsage.system - this.lastCpuUsage.system
1358
+ };
1359
+ const timeDelta = (now - this.lastCpuTime) * 1e3;
1360
+ const cpuPercent = timeDelta > 0 ? (cpuDelta.user + cpuDelta.system) / timeDelta * 100 : 0;
1361
+ this.lastCpuUsage = cpuUsage;
1362
+ this.lastCpuTime = now;
1363
+ let eventLoopLagMs = 0;
1364
+ if (this.eventLoopHistogram) {
1365
+ eventLoopLagMs = this.eventLoopHistogram.mean / 1e6;
1366
+ this.eventLoopHistogram.reset();
1367
+ }
1368
+ return {
1369
+ memory_heap_used: memoryUsage.heapUsed,
1370
+ memory_heap_total: memoryUsage.heapTotal,
1371
+ memory_rss: memoryUsage.rss,
1372
+ memory_external: memoryUsage.external,
1373
+ cpu_user_micros: cpuUsage.user,
1374
+ cpu_system_micros: cpuUsage.system,
1375
+ cpu_percent: Math.min(cpuPercent, 100),
1376
+ event_loop_lag_ms: eventLoopLagMs,
1377
+ uptime_seconds: Math.floor((Date.now() - this.startTime) / 1e3),
1378
+ timestamp_ms: Date.now(),
1379
+ runtime: "node"
1380
+ };
1381
+ }
1382
+ };
1383
+
1384
+ //#endregion
1385
+ //#region src/otel-worker-gauges.ts
1386
+ let registeredGauges = false;
1387
+ let metricsCollector = null;
1388
+ let registeredMeter = null;
1389
+ let registeredBatchCallback = null;
1390
+ let registeredObservables = [];
1391
+ function registerWorkerGauges(meter, options) {
1392
+ if (registeredGauges) return;
1393
+ const { workerId, workerName } = options;
1394
+ const baseAttributes = {
1395
+ "worker.id": workerId,
1396
+ ...workerName && { "worker.name": workerName }
1397
+ };
1398
+ metricsCollector = new WorkerMetricsCollector();
1399
+ const memoryHeapUsed = meter.createObservableGauge("iii.worker.memory.heap_used", {
1400
+ description: "Worker heap memory used in bytes",
1401
+ unit: "bytes"
1402
+ });
1403
+ const memoryHeapTotal = meter.createObservableGauge("iii.worker.memory.heap_total", {
1404
+ description: "Worker total heap memory in bytes",
1405
+ unit: "bytes"
1406
+ });
1407
+ const memoryRss = meter.createObservableGauge("iii.worker.memory.rss", {
1408
+ description: "Worker resident set size in bytes",
1409
+ unit: "bytes"
1410
+ });
1411
+ const memoryExternal = meter.createObservableGauge("iii.worker.memory.external", {
1412
+ description: "Worker external memory in bytes",
1413
+ unit: "bytes"
1414
+ });
1415
+ const cpuPercent = meter.createObservableGauge("iii.worker.cpu.percent", {
1416
+ description: "Worker CPU usage percentage",
1417
+ unit: "%"
1418
+ });
1419
+ const cpuUserMicros = meter.createObservableGauge("iii.worker.cpu.user_micros", {
1420
+ description: "Worker CPU user time in microseconds",
1421
+ unit: "us"
1422
+ });
1423
+ const cpuSystemMicros = meter.createObservableGauge("iii.worker.cpu.system_micros", {
1424
+ description: "Worker CPU system time in microseconds",
1425
+ unit: "us"
1426
+ });
1427
+ const eventLoopLag = meter.createObservableGauge("iii.worker.event_loop.lag_ms", {
1428
+ description: "Worker event loop lag in milliseconds",
1429
+ unit: "ms"
1430
+ });
1431
+ const uptimeSeconds = meter.createObservableGauge("iii.worker.uptime_seconds", {
1432
+ description: "Worker uptime in seconds",
1433
+ unit: "s"
1434
+ });
1435
+ const batchCallback = (observableResult) => {
1436
+ if (!metricsCollector) return;
1437
+ const metrics = metricsCollector.collect();
1438
+ if (metrics.memory_heap_used !== void 0) observableResult.observe(memoryHeapUsed, metrics.memory_heap_used, baseAttributes);
1439
+ if (metrics.memory_heap_total !== void 0) observableResult.observe(memoryHeapTotal, metrics.memory_heap_total, baseAttributes);
1440
+ if (metrics.memory_rss !== void 0) observableResult.observe(memoryRss, metrics.memory_rss, baseAttributes);
1441
+ if (metrics.memory_external !== void 0) observableResult.observe(memoryExternal, metrics.memory_external, baseAttributes);
1442
+ if (metrics.cpu_percent !== void 0) observableResult.observe(cpuPercent, metrics.cpu_percent, baseAttributes);
1443
+ if (metrics.cpu_user_micros !== void 0) observableResult.observe(cpuUserMicros, metrics.cpu_user_micros, baseAttributes);
1444
+ if (metrics.cpu_system_micros !== void 0) observableResult.observe(cpuSystemMicros, metrics.cpu_system_micros, baseAttributes);
1445
+ if (metrics.event_loop_lag_ms !== void 0) observableResult.observe(eventLoopLag, metrics.event_loop_lag_ms, baseAttributes);
1446
+ if (metrics.uptime_seconds !== void 0) observableResult.observe(uptimeSeconds, metrics.uptime_seconds, baseAttributes);
1447
+ };
1448
+ meter.addBatchObservableCallback(batchCallback, [
1449
+ memoryHeapUsed,
1450
+ memoryHeapTotal,
1451
+ memoryRss,
1452
+ memoryExternal,
1453
+ cpuPercent,
1454
+ cpuUserMicros,
1455
+ cpuSystemMicros,
1456
+ eventLoopLag,
1457
+ uptimeSeconds
1458
+ ]);
1459
+ registeredMeter = meter;
1460
+ registeredBatchCallback = batchCallback;
1461
+ registeredObservables = [
1462
+ memoryHeapUsed,
1463
+ memoryHeapTotal,
1464
+ memoryRss,
1465
+ memoryExternal,
1466
+ cpuPercent,
1467
+ cpuUserMicros,
1468
+ cpuSystemMicros,
1469
+ eventLoopLag,
1470
+ uptimeSeconds
1471
+ ];
1472
+ registeredGauges = true;
1473
+ }
1474
+ function stopWorkerGauges() {
1475
+ if (registeredMeter && registeredBatchCallback) registeredMeter.removeBatchObservableCallback(registeredBatchCallback, registeredObservables);
1476
+ if (metricsCollector) {
1477
+ metricsCollector.stopMonitoring();
1478
+ metricsCollector = null;
1479
+ }
1480
+ registeredMeter = null;
1481
+ registeredBatchCallback = null;
1482
+ registeredObservables = [];
1483
+ registeredGauges = false;
1484
+ }
1485
+
1486
+ //#endregion
1487
+ //#region src/utils.ts
1488
+ /**
1489
+ * Safely stringify a value, handling circular references, BigInt, and other edge cases.
1490
+ * Returns "[unserializable]" if serialization fails for any reason.
1491
+ */
1492
+ function safeStringify(value) {
1493
+ const seen = /* @__PURE__ */ new WeakSet();
1494
+ try {
1495
+ return JSON.stringify(value, (_key, val) => {
1496
+ if (typeof val === "bigint") return val.toString();
1497
+ if (val !== null && typeof val === "object") {
1498
+ if (seen.has(val)) return "[Circular]";
1499
+ seen.add(val);
1500
+ }
1501
+ return val;
1502
+ }) ?? "[unserializable]";
1503
+ } catch {
1504
+ return "[unserializable]";
1505
+ }
1506
+ }
1507
+
1508
+ //#endregion
1509
+ exports.BaggageSpanProcessor = BaggageSpanProcessor;
1510
+ exports.DEFAULT_ALLOWLIST = DEFAULT_ALLOWLIST;
1511
+ exports.Logger = Logger;
1512
+ exports.REDACTED_PLACEHOLDER = REDACTED_PLACEHOLDER;
1513
+ exports.SeverityNumber = _opentelemetry_api_logs.SeverityNumber;
1514
+ exports.SpanKind = _opentelemetry_api.SpanKind;
1515
+ exports.WorkerMetricsCollector = WorkerMetricsCollector;
1516
+ exports.currentSpanId = currentSpanId;
1517
+ exports.currentSpanIsRecording = currentSpanIsRecording;
1518
+ exports.currentTraceId = currentTraceId;
1519
+ exports.executeTracedRequest = executeTracedRequest;
1520
+ exports.extractBaggage = extractBaggage;
1521
+ exports.extractContext = extractContext;
1522
+ exports.extractTraceparent = extractTraceparent;
1523
+ exports.flushOtel = flushOtel;
1524
+ exports.getAllBaggage = getAllBaggage;
1525
+ exports.getBaggageEntry = getBaggageEntry;
1526
+ exports.getLogger = getLogger;
1527
+ exports.getMeter = getMeter;
1528
+ exports.getTracer = getTracer;
1529
+ exports.initOtel = initOtel;
1530
+ exports.injectBaggage = injectBaggage;
1531
+ exports.injectTraceparent = injectTraceparent;
1532
+ exports.patchGlobalFetch = patchGlobalFetch;
1533
+ exports.recordSpanEvent = recordSpanEvent;
1534
+ exports.redact = redact;
1535
+ exports.redactAndTruncate = redactAndTruncate;
1536
+ exports.registerWorkerGauges = registerWorkerGauges;
1537
+ exports.removeBaggageEntry = removeBaggageEntry;
1538
+ exports.resolveMaxBytesFromEnv = resolveMaxBytesFromEnv;
1539
+ exports.safeStringify = safeStringify;
1540
+ exports.setBaggageEntry = setBaggageEntry;
1541
+ exports.setCurrentSpanAttribute = setCurrentSpanAttribute;
1542
+ exports.setCurrentSpanError = setCurrentSpanError;
1543
+ exports.shutdownOtel = shutdownOtel;
1544
+ exports.stopWorkerGauges = stopWorkerGauges;
1545
+ exports.unpatchGlobalFetch = unpatchGlobalFetch;
1546
+ exports.withSpan = withSpan;
1547
+ //# sourceMappingURL=index.cjs.map