autotel-devtools 0.1.0

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.
Files changed (43) hide show
  1. package/README.md +156 -0
  2. package/dist/cli.cjs +889 -0
  3. package/dist/cli.cjs.map +1 -0
  4. package/dist/cli.d.cts +1 -0
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +886 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/error-aggregator-BkO0l8ak.d.ts +147 -0
  9. package/dist/error-aggregator-CtZmjm-k.d.cts +147 -0
  10. package/dist/exporter-qIQPDw29.d.cts +159 -0
  11. package/dist/exporter-qIQPDw29.d.ts +159 -0
  12. package/dist/index.cjs +1242 -0
  13. package/dist/index.cjs.map +1 -0
  14. package/dist/index.d.cts +29 -0
  15. package/dist/index.d.ts +29 -0
  16. package/dist/index.js +1234 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/server/exporter.cjs +154 -0
  19. package/dist/server/exporter.cjs.map +1 -0
  20. package/dist/server/exporter.d.cts +4 -0
  21. package/dist/server/exporter.d.ts +4 -0
  22. package/dist/server/exporter.js +152 -0
  23. package/dist/server/exporter.js.map +1 -0
  24. package/dist/server/index.cjs +1237 -0
  25. package/dist/server/index.cjs.map +1 -0
  26. package/dist/server/index.d.cts +38 -0
  27. package/dist/server/index.d.ts +38 -0
  28. package/dist/server/index.js +1222 -0
  29. package/dist/server/index.js.map +1 -0
  30. package/dist/server/log-exporter.cjs +111 -0
  31. package/dist/server/log-exporter.cjs.map +1 -0
  32. package/dist/server/log-exporter.d.cts +50 -0
  33. package/dist/server/log-exporter.d.ts +50 -0
  34. package/dist/server/log-exporter.js +109 -0
  35. package/dist/server/log-exporter.js.map +1 -0
  36. package/dist/server/remote-exporter.cjs +219 -0
  37. package/dist/server/remote-exporter.cjs.map +1 -0
  38. package/dist/server/remote-exporter.d.cts +87 -0
  39. package/dist/server/remote-exporter.d.ts +87 -0
  40. package/dist/server/remote-exporter.js +217 -0
  41. package/dist/server/remote-exporter.js.map +1 -0
  42. package/dist/widget.global.js +2 -0
  43. package/package.json +96 -0
@@ -0,0 +1,1237 @@
1
+ 'use strict';
2
+
3
+ var ws = require('ws');
4
+ var http = require('http');
5
+ var core = require('@opentelemetry/core');
6
+ var fs = require('fs');
7
+ var path = require('path');
8
+ var url = require('url');
9
+
10
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
11
+ // src/server/server.ts
12
+
13
+ // src/server/error-aggregator.ts
14
+ var ErrorAggregator = class {
15
+ errorGroups = /* @__PURE__ */ new Map();
16
+ options;
17
+ constructor(options = {}) {
18
+ this.options = {
19
+ maxGroups: options.maxGroups ?? 100,
20
+ maxAffectedTraces: options.maxAffectedTraces ?? 10,
21
+ maxAffectedSpans: options.maxAffectedSpans ?? 5,
22
+ stackFramesForFingerprint: options.stackFramesForFingerprint ?? 5
23
+ };
24
+ }
25
+ /**
26
+ * Add an error occurrence to the aggregator
27
+ */
28
+ addError(occurrence) {
29
+ const fingerprint = this.generateFingerprint(occurrence);
30
+ const existing = this.errorGroups.get(fingerprint);
31
+ if (existing) {
32
+ existing.count++;
33
+ existing.lastSeen = occurrence.timestamp;
34
+ if (!existing.affectedTraces.includes(occurrence.traceId)) {
35
+ existing.affectedTraces.push(occurrence.traceId);
36
+ if (existing.affectedTraces.length > this.options.maxAffectedTraces) {
37
+ existing.affectedTraces.shift();
38
+ }
39
+ }
40
+ if (!existing.affectedSpans.includes(occurrence.spanName)) {
41
+ existing.affectedSpans.push(occurrence.spanName);
42
+ if (existing.affectedSpans.length > this.options.maxAffectedSpans) {
43
+ existing.affectedSpans.shift();
44
+ }
45
+ }
46
+ return existing;
47
+ }
48
+ const newGroup = {
49
+ fingerprint,
50
+ type: occurrence.error.type,
51
+ message: occurrence.error.message,
52
+ stackTrace: this.normalizeStackTrace(occurrence.error.stackTrace),
53
+ count: 1,
54
+ firstSeen: occurrence.timestamp,
55
+ lastSeen: occurrence.timestamp,
56
+ affectedTraces: [occurrence.traceId],
57
+ affectedSpans: [occurrence.spanName],
58
+ service: occurrence.service,
59
+ attributes: occurrence.attributes
60
+ };
61
+ if (this.errorGroups.size >= this.options.maxGroups) {
62
+ this.evictOldestGroup();
63
+ }
64
+ this.errorGroups.set(fingerprint, newGroup);
65
+ return newGroup;
66
+ }
67
+ /**
68
+ * Extract errors from a trace and add them to the aggregator
69
+ */
70
+ addErrorsFromTrace(trace) {
71
+ const addedGroups = [];
72
+ for (const span of trace.spans) {
73
+ if (span.status.code === "ERROR") {
74
+ const occurrence = this.extractErrorFromSpan(span, trace);
75
+ if (occurrence) {
76
+ const group = this.addError(occurrence);
77
+ addedGroups.push(group);
78
+ }
79
+ }
80
+ }
81
+ return addedGroups;
82
+ }
83
+ /**
84
+ * Extract error occurrence from a span
85
+ */
86
+ extractErrorFromSpan(span, trace) {
87
+ const exceptionEvent = span.events?.find((e) => e.name === "exception");
88
+ const errorType = span.attributes["exception.type"] || span.attributes["error.type"] || exceptionEvent?.attributes?.["exception.type"] || "Error";
89
+ const errorMessage = span.status.message || span.attributes["exception.message"] || span.attributes["error.message"] || "Unknown error";
90
+ const stackTrace = span.attributes["exception.stacktrace"] || span.attributes["exception.stack"] || this.extractStackFromEvents(span);
91
+ return {
92
+ traceId: trace.traceId,
93
+ spanId: span.spanId,
94
+ spanName: span.name,
95
+ service: trace.service,
96
+ timestamp: span.endTime,
97
+ error: {
98
+ type: errorType,
99
+ message: errorMessage,
100
+ stackTrace
101
+ },
102
+ attributes: this.extractRelevantAttributes(span.attributes)
103
+ };
104
+ }
105
+ /**
106
+ * Extract stack trace from span events (exception events)
107
+ */
108
+ extractStackFromEvents(span) {
109
+ if (!span.events) return void 0;
110
+ const exceptionEvent = span.events.find((e) => e.name === "exception");
111
+ if (exceptionEvent?.attributes) {
112
+ return exceptionEvent.attributes["exception.stacktrace"] || exceptionEvent.attributes["exception.stack"];
113
+ }
114
+ return void 0;
115
+ }
116
+ /**
117
+ * Extract relevant attributes for error context
118
+ */
119
+ extractRelevantAttributes(attributes) {
120
+ const relevant = {};
121
+ const keepKeys = [
122
+ "http.method",
123
+ "http.url",
124
+ "http.route",
125
+ "http.status_code",
126
+ "db.system",
127
+ "db.operation",
128
+ "rpc.method",
129
+ "rpc.service",
130
+ "code.function",
131
+ "code.filepath",
132
+ "user.id",
133
+ "operation.name"
134
+ ];
135
+ for (const key of keepKeys) {
136
+ if (key in attributes) {
137
+ relevant[key] = attributes[key];
138
+ }
139
+ }
140
+ return relevant;
141
+ }
142
+ /**
143
+ * Generate a fingerprint for error grouping
144
+ *
145
+ * Uses error type + first N stack frames (normalized)
146
+ */
147
+ generateFingerprint(occurrence) {
148
+ const parts = [occurrence.error.type];
149
+ if (occurrence.error.stackTrace) {
150
+ const frames = this.extractStackFrames(
151
+ occurrence.error.stackTrace,
152
+ this.options.stackFramesForFingerprint
153
+ );
154
+ parts.push(...frames);
155
+ } else {
156
+ parts.push(this.normalizeMessage(occurrence.error.message));
157
+ }
158
+ return this.simpleHash(parts.join("|"));
159
+ }
160
+ /**
161
+ * Extract and normalize stack frames from a stack trace
162
+ */
163
+ extractStackFrames(stackTrace, count) {
164
+ const lines = stackTrace.split("\n");
165
+ const frames = [];
166
+ for (const line of lines) {
167
+ if (frames.length >= count) break;
168
+ const trimmed = line.trim();
169
+ const nodeMatch = trimmed.match(/^at\s+(.+?)\s+\((.+?):(\d+):\d+\)$/);
170
+ if (nodeMatch) {
171
+ frames.push(`${nodeMatch[1]}@${this.normalizeFilePath(nodeMatch[2])}`);
172
+ continue;
173
+ }
174
+ const anonMatch = trimmed.match(/^at\s+(.+?):(\d+):\d+$/);
175
+ if (anonMatch) {
176
+ frames.push(`anonymous@${this.normalizeFilePath(anonMatch[1])}`);
177
+ continue;
178
+ }
179
+ const browserMatch = trimmed.match(/^(.+?)@(.+?):(\d+):\d+$/);
180
+ if (browserMatch) {
181
+ frames.push(
182
+ `${browserMatch[1]}@${this.normalizeFilePath(browserMatch[2])}`
183
+ );
184
+ continue;
185
+ }
186
+ }
187
+ return frames;
188
+ }
189
+ /**
190
+ * Normalize file path by removing absolute path prefixes and node_modules paths
191
+ */
192
+ normalizeFilePath(filePath) {
193
+ const nodeModulesMatch = filePath.match(
194
+ /node_modules\/(@[^/]+\/[^/]+|[^/]+)/
195
+ );
196
+ if (nodeModulesMatch) {
197
+ return `[npm]/${nodeModulesMatch[1]}`;
198
+ }
199
+ return filePath.replace(/^.*?\/src\//, "src/").replace(/^.*?\/dist\//, "dist/").replace(/^.*?\/lib\//, "lib/").replace(/^file:\/\//, "");
200
+ }
201
+ /**
202
+ * Normalize error message by removing dynamic parts
203
+ */
204
+ normalizeMessage(message) {
205
+ return message.replaceAll(
206
+ /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi,
207
+ "[UUID]"
208
+ ).replaceAll(/\b[0-9a-f]{16,}\b/gi, "[ID]").replaceAll(/\b\d+\b/g, "[N]").replaceAll(/"[^"]*"/g, '"[STR]"').replaceAll(/'[^']*'/g, "'[STR]'").slice(0, 200);
209
+ }
210
+ /**
211
+ * Normalize stack trace for display
212
+ */
213
+ normalizeStackTrace(stackTrace) {
214
+ if (!stackTrace) return void 0;
215
+ const lines = stackTrace.split("\n").slice(0, 10);
216
+ return lines.join("\n");
217
+ }
218
+ /**
219
+ * Simple hash function for fingerprinting
220
+ */
221
+ simpleHash(str) {
222
+ let hash = 0;
223
+ for (let i = 0; i < str.length; i++) {
224
+ const char = str.charCodeAt(i);
225
+ hash = (hash << 5) - hash + char;
226
+ hash = hash & hash;
227
+ }
228
+ return Math.abs(hash).toString(16).padStart(8, "0");
229
+ }
230
+ /**
231
+ * Evict the oldest error group
232
+ */
233
+ evictOldestGroup() {
234
+ let oldest = null;
235
+ for (const [fingerprint, group] of this.errorGroups) {
236
+ if (!oldest || group.lastSeen < oldest.lastSeen) {
237
+ oldest = { fingerprint, lastSeen: group.lastSeen };
238
+ }
239
+ }
240
+ if (oldest) {
241
+ this.errorGroups.delete(oldest.fingerprint);
242
+ }
243
+ }
244
+ /**
245
+ * Get all error groups, sorted by most recent
246
+ */
247
+ getErrorGroups() {
248
+ return [...this.errorGroups.values()].sort(
249
+ (a, b) => b.lastSeen - a.lastSeen
250
+ );
251
+ }
252
+ /**
253
+ * Get error groups sorted by count (most frequent first)
254
+ */
255
+ getErrorGroupsByFrequency() {
256
+ return [...this.errorGroups.values()].sort(
257
+ (a, b) => b.count - a.count
258
+ );
259
+ }
260
+ /**
261
+ * Get a specific error group by fingerprint
262
+ */
263
+ getErrorGroup(fingerprint) {
264
+ return this.errorGroups.get(fingerprint);
265
+ }
266
+ /**
267
+ * Get error groups for a specific service
268
+ */
269
+ getErrorGroupsByService(service) {
270
+ return this.getErrorGroups().filter((g) => g.service === service);
271
+ }
272
+ /**
273
+ * Get total error count across all groups
274
+ */
275
+ getTotalErrorCount() {
276
+ let total = 0;
277
+ for (const group of this.errorGroups.values()) {
278
+ total += group.count;
279
+ }
280
+ return total;
281
+ }
282
+ /**
283
+ * Get error statistics
284
+ */
285
+ getStats() {
286
+ const now = Date.now();
287
+ const oneHourAgo = now - 60 * 60 * 1e3;
288
+ let recentErrors = 0;
289
+ const typeCount = /* @__PURE__ */ new Map();
290
+ for (const group of this.errorGroups.values()) {
291
+ if (group.lastSeen > oneHourAgo) {
292
+ recentErrors += group.count;
293
+ }
294
+ typeCount.set(group.type, (typeCount.get(group.type) || 0) + group.count);
295
+ }
296
+ const topErrorTypes = [...typeCount.entries()].map(([type, count]) => ({ type, count })).sort((a, b) => b.count - a.count).slice(0, 5);
297
+ return {
298
+ totalGroups: this.errorGroups.size,
299
+ totalErrors: this.getTotalErrorCount(),
300
+ recentErrors,
301
+ topErrorTypes
302
+ };
303
+ }
304
+ /**
305
+ * Clear all error groups
306
+ */
307
+ clear() {
308
+ this.errorGroups.clear();
309
+ }
310
+ /**
311
+ * Clear old error groups (not seen in given time window)
312
+ */
313
+ clearOlderThan(maxAgeMs) {
314
+ const cutoff = Date.now() - maxAgeMs;
315
+ let cleared = 0;
316
+ for (const [fingerprint, group] of this.errorGroups) {
317
+ if (group.lastSeen < cutoff) {
318
+ this.errorGroups.delete(fingerprint);
319
+ cleared++;
320
+ }
321
+ }
322
+ return cleared;
323
+ }
324
+ };
325
+
326
+ // src/server/telemetry-limits.ts
327
+ var defaultLimit = 100;
328
+ function parseLimit(value) {
329
+ if (!value) return void 0;
330
+ const parsed = Number.parseInt(value, 10);
331
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
332
+ }
333
+ function resolveTelemetryLimits(args = {}) {
334
+ const env = args.env ?? process.env;
335
+ const fallback = args.maxHistory ?? defaultLimit;
336
+ return {
337
+ maxTraceCount: args.maxTraceCount ?? parseLimit(env.AUTOTEL_MAX_TRACE_COUNT) ?? fallback,
338
+ maxLogCount: args.maxLogCount ?? parseLimit(env.AUTOTEL_MAX_LOG_COUNT) ?? fallback,
339
+ maxMetricCount: args.maxMetricCount ?? parseLimit(env.AUTOTEL_MAX_METRIC_COUNT) ?? fallback
340
+ };
341
+ }
342
+ function appendWithLimit(items, item, limit) {
343
+ if (limit <= 0) return [];
344
+ const next = [...items, item];
345
+ return next.length > limit ? next.slice(next.length - limit) : next;
346
+ }
347
+ function appendManyWithLimit(items, incoming, limit) {
348
+ if (limit <= 0 || incoming.length === 0) return limit <= 0 ? [] : items;
349
+ const next = [...items, ...incoming];
350
+ return next.length > limit ? next.slice(next.length - limit) : next;
351
+ }
352
+ function applyTelemetryLimits(data, limits) {
353
+ return {
354
+ ...data,
355
+ traces: data.traces.slice(-limits.maxTraceCount),
356
+ logs: data.logs.slice(-limits.maxLogCount),
357
+ metrics: data.metrics.slice(-limits.maxMetricCount)
358
+ };
359
+ }
360
+
361
+ // src/server/server.ts
362
+ var DevtoolsServer = class {
363
+ wss;
364
+ clients = /* @__PURE__ */ new Set();
365
+ httpServer;
366
+ traces = [];
367
+ logs = [];
368
+ metrics = [];
369
+ errorAggregator = new ErrorAggregator();
370
+ limits;
371
+ verbose;
372
+ _port;
373
+ constructor(options = {}) {
374
+ this.limits = resolveTelemetryLimits(options);
375
+ this.verbose = options.verbose ?? false;
376
+ this._port = options.port ?? 4318;
377
+ this.httpServer = options.server ?? http.createServer();
378
+ this.wss = new ws.WebSocketServer({ server: this.httpServer, path: options.path ?? "/ws" });
379
+ this.wss.on("connection", (ws) => {
380
+ this.clients.add(ws);
381
+ this.log(`Client connected (${this.clients.size} total)`);
382
+ const data = this.getCurrentData();
383
+ if (data.traces.length > 0 || data.logs.length > 0 || data.errors.length > 0) {
384
+ ws.send(JSON.stringify(data));
385
+ }
386
+ ws.on("close", () => {
387
+ this.clients.delete(ws);
388
+ this.log(`Client disconnected (${this.clients.size} total)`);
389
+ });
390
+ });
391
+ if (!options.server) {
392
+ this.httpServer.listen(this._port, () => {
393
+ const addr = this.httpServer.address();
394
+ if (addr && typeof addr === "object") this._port = addr.port;
395
+ this.log(`WebSocket server listening on port ${this._port}`);
396
+ });
397
+ }
398
+ }
399
+ get port() {
400
+ const addr = this.httpServer.address();
401
+ if (addr && typeof addr === "object") return addr.port;
402
+ return this._port;
403
+ }
404
+ get clientCount() {
405
+ return this.clients.size;
406
+ }
407
+ addTrace(trace) {
408
+ const existing = this.traces.find((t) => t.traceId === trace.traceId);
409
+ if (existing) {
410
+ const existingSpanIds = new Set(existing.spans.map((s) => s.spanId));
411
+ for (const span of trace.spans) {
412
+ if (!existingSpanIds.has(span.spanId)) {
413
+ existing.spans.push(span);
414
+ }
415
+ }
416
+ existing.startTime = Math.min(existing.startTime, trace.startTime);
417
+ existing.endTime = Math.max(existing.endTime, trace.endTime);
418
+ existing.duration = existing.endTime - existing.startTime;
419
+ if (trace.status === "ERROR") existing.status = "ERROR";
420
+ } else {
421
+ this.traces = appendWithLimit(
422
+ this.traces,
423
+ trace,
424
+ this.limits.maxTraceCount
425
+ );
426
+ }
427
+ this.errorAggregator.addErrorsFromTrace(trace);
428
+ this.broadcast({ traces: [trace], metrics: [], logs: [], errors: this.errorAggregator.getErrorGroups() });
429
+ }
430
+ addTraces(traces) {
431
+ for (const trace of traces) this.addTrace(trace);
432
+ }
433
+ addLog(log) {
434
+ this.logs = appendWithLimit(this.logs, log, this.limits.maxLogCount);
435
+ this.broadcast({ traces: [], metrics: [], logs: [log], errors: [] });
436
+ }
437
+ addLogs(logs) {
438
+ this.logs = appendManyWithLimit(this.logs, logs, this.limits.maxLogCount);
439
+ this.broadcast({ traces: [], metrics: [], logs, errors: [] });
440
+ }
441
+ addMetric(metric) {
442
+ this.metrics = appendWithLimit(
443
+ this.metrics,
444
+ metric,
445
+ this.limits.maxMetricCount
446
+ );
447
+ this.broadcast({ traces: [], metrics: [metric], logs: [], errors: [] });
448
+ }
449
+ getCurrentData() {
450
+ return {
451
+ traces: this.traces,
452
+ metrics: this.metrics,
453
+ logs: this.logs,
454
+ errors: this.errorAggregator.getErrorGroups()
455
+ };
456
+ }
457
+ clearData() {
458
+ this.traces = [];
459
+ this.logs = [];
460
+ this.metrics = [];
461
+ this.errorAggregator.clear();
462
+ }
463
+ broadcast(data) {
464
+ const msg = JSON.stringify(data);
465
+ for (const client of this.clients) {
466
+ if (client.readyState === ws.WebSocket.OPEN) {
467
+ client.send(msg);
468
+ }
469
+ }
470
+ }
471
+ log(message) {
472
+ if (this.verbose) console.log(`[autotel-devtools] ${message}`);
473
+ }
474
+ async close() {
475
+ for (const client of this.clients) client.close();
476
+ this.clients.clear();
477
+ this.wss.close();
478
+ await new Promise((resolve2) => this.httpServer.close(() => resolve2()));
479
+ }
480
+ };
481
+
482
+ // src/server/exporter.ts
483
+ var DevtoolsSpanExporter = class {
484
+ server;
485
+ serviceName;
486
+ constructor(server, serviceName = "unknown-service") {
487
+ this.server = server;
488
+ this.serviceName = serviceName;
489
+ }
490
+ /**
491
+ * Export spans to the WebSocket server
492
+ */
493
+ async export(spans, resultCallback) {
494
+ resultCallback({ code: 0 });
495
+ Promise.resolve().then(() => {
496
+ try {
497
+ console.log(`[Autotel Exporter] Exporting ${spans.length} span(s)`);
498
+ const traceMap = /* @__PURE__ */ new Map();
499
+ for (const span of spans) {
500
+ const traceId = span.spanContext().traceId;
501
+ if (!traceMap.has(traceId)) {
502
+ traceMap.set(traceId, []);
503
+ }
504
+ traceMap.get(traceId).push(span);
505
+ }
506
+ for (const [traceId, traceSpans] of traceMap) {
507
+ const trace = this.convertToTraceData(traceId, traceSpans);
508
+ console.log(
509
+ `[Autotel Exporter] Adding trace ${traceId.slice(0, 16)} with ${traceSpans.length} spans`
510
+ );
511
+ this.server.addTrace(trace);
512
+ }
513
+ } catch (error) {
514
+ console.error("[Autotel Exporter] Export error:", error);
515
+ }
516
+ });
517
+ }
518
+ /**
519
+ * Shutdown the exporter
520
+ */
521
+ async shutdown() {
522
+ }
523
+ /**
524
+ * Force flush any buffered spans
525
+ */
526
+ async forceFlush() {
527
+ }
528
+ /**
529
+ * Convert OpenTelemetry spans to TraceData
530
+ */
531
+ convertToTraceData(traceId, spans) {
532
+ const spanData = spans.map((span) => this.convertSpan(span));
533
+ const rootSpan = spanData.find((s) => !s.parentSpanId) || spanData[0];
534
+ spanData.sort((a, b) => a.startTime - b.startTime);
535
+ const startTime = Math.min(...spanData.map((s) => s.startTime));
536
+ const endTime = Math.max(...spanData.map((s) => s.endTime));
537
+ const hasError = spanData.some((s) => s.status.code === "ERROR");
538
+ const status = hasError ? "ERROR" : "OK";
539
+ return {
540
+ traceId,
541
+ correlationId: traceId.slice(0, 16),
542
+ // First 16 chars
543
+ rootSpan,
544
+ spans: spanData,
545
+ startTime,
546
+ endTime,
547
+ duration: endTime - startTime,
548
+ status,
549
+ service: this.serviceName
550
+ };
551
+ }
552
+ /**
553
+ * Convert OpenTelemetry span to SpanData
554
+ */
555
+ convertSpan(span) {
556
+ const spanContext = span.spanContext();
557
+ const startTime = span.startTime[0] * 1e3 + span.startTime[1] / 1e6;
558
+ const endTime = span.endTime[0] * 1e3 + span.endTime[1] / 1e6;
559
+ const attributes = {};
560
+ for (const [key, value] of Object.entries(span.attributes)) {
561
+ attributes[key] = value;
562
+ }
563
+ const statusCode = span.status.code;
564
+ let status;
565
+ switch (statusCode) {
566
+ case 0: {
567
+ status = "UNSET";
568
+ break;
569
+ }
570
+ case 1: {
571
+ status = "OK";
572
+ break;
573
+ }
574
+ case 2: {
575
+ status = "ERROR";
576
+ break;
577
+ }
578
+ default: {
579
+ status = "UNSET";
580
+ }
581
+ }
582
+ const events = span.events.map((event) => ({
583
+ name: event.name,
584
+ timestamp: event.time[0] * 1e3 + event.time[1] / 1e6,
585
+ attributes: event.attributes ? Object.fromEntries(Object.entries(event.attributes)) : void 0
586
+ }));
587
+ return {
588
+ traceId: spanContext.traceId,
589
+ spanId: spanContext.spanId,
590
+ parentSpanId: span.parentSpanId,
591
+ name: span.name,
592
+ kind: this.convertSpanKind(span.kind),
593
+ startTime,
594
+ endTime,
595
+ duration: endTime - startTime,
596
+ attributes,
597
+ status: {
598
+ code: status,
599
+ message: span.status.message
600
+ },
601
+ events: events.length > 0 ? events : void 0
602
+ };
603
+ }
604
+ /**
605
+ * Convert OpenTelemetry SpanKind to string
606
+ */
607
+ convertSpanKind(kind) {
608
+ switch (kind) {
609
+ case 0: {
610
+ return "INTERNAL";
611
+ }
612
+ case 1: {
613
+ return "SERVER";
614
+ }
615
+ case 2: {
616
+ return "CLIENT";
617
+ }
618
+ case 3: {
619
+ return "PRODUCER";
620
+ }
621
+ case 4: {
622
+ return "CONSUMER";
623
+ }
624
+ default: {
625
+ return "INTERNAL";
626
+ }
627
+ }
628
+ }
629
+ };
630
+
631
+ // src/server/resource-utils.ts
632
+ function getResourceName(resource, fallback = "unknown") {
633
+ if (!resource) return fallback;
634
+ const candidates = [
635
+ resource["service.name"],
636
+ resource["service.namespace"],
637
+ resource["deployment.environment.name"],
638
+ resource["host.name"],
639
+ resource["container.name"],
640
+ resource["process.executable.name"]
641
+ ];
642
+ for (const candidate of candidates) {
643
+ if (typeof candidate === "string" && candidate.trim().length > 0) {
644
+ return candidate;
645
+ }
646
+ }
647
+ return fallback;
648
+ }
649
+
650
+ // src/server/log-exporter.ts
651
+ var defaultTimeout = 5e3;
652
+ function hrTimeToMs(hrTime) {
653
+ return hrTime[0] * 1e3 + hrTime[1] / 1e6;
654
+ }
655
+ function bodyToPayload(body) {
656
+ if (body === void 0) return "";
657
+ if (typeof body === "string") return body;
658
+ if (typeof body === "object" && body !== null) return body;
659
+ return String(body);
660
+ }
661
+ function recordToLogData(record, index) {
662
+ const id = `log-${Date.now()}-${index}-${Math.random().toString(36).slice(2, 9)}`;
663
+ const timestamp = hrTimeToMs(record.hrTime);
664
+ const body = bodyToPayload(record.body);
665
+ const attributes = record.attributes && Object.keys(record.attributes).length > 0 ? record.attributes : void 0;
666
+ const resource = record.resource?.attributes && Object.keys(record.resource.attributes).length > 0 ? record.resource.attributes : void 0;
667
+ const log = {
668
+ id,
669
+ resourceName: getResourceName(resource),
670
+ severityText: record.severityText,
671
+ severityNumber: record.severityNumber,
672
+ body,
673
+ timestamp,
674
+ attributes,
675
+ resource
676
+ };
677
+ if (record.spanContext) {
678
+ log.traceId = record.spanContext.traceId;
679
+ log.spanId = record.spanContext.spanId;
680
+ }
681
+ return log;
682
+ }
683
+ var DevtoolsLogExporter = class {
684
+ endpoint;
685
+ apiKey;
686
+ timeout;
687
+ isShutdown = false;
688
+ constructor(options) {
689
+ this.endpoint = options.endpoint.replace(/\/$/, "");
690
+ this.apiKey = options.apiKey ?? "";
691
+ this.timeout = options.timeout ?? defaultTimeout;
692
+ }
693
+ export(logs, resultCallback) {
694
+ if (this.isShutdown || logs.length === 0) {
695
+ resultCallback({ code: core.ExportResultCode.SUCCESS });
696
+ return;
697
+ }
698
+ const payload = { logs: logs.map((r, i) => recordToLogData(r, i)) };
699
+ const url = `${this.endpoint}/ingest/logs`;
700
+ const headers = {
701
+ "Content-Type": "application/json"
702
+ };
703
+ if (this.apiKey) {
704
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
705
+ }
706
+ const controller = new AbortController();
707
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
708
+ fetch(url, {
709
+ method: "POST",
710
+ headers,
711
+ body: JSON.stringify(payload),
712
+ signal: controller.signal
713
+ }).then((res) => {
714
+ clearTimeout(timeoutId);
715
+ if (!res.ok) {
716
+ throw new Error(`Devtools log ingest failed: ${res.status} ${res.statusText}`);
717
+ }
718
+ resultCallback({ code: core.ExportResultCode.SUCCESS });
719
+ }).catch((err) => {
720
+ clearTimeout(timeoutId);
721
+ resultCallback({
722
+ code: core.ExportResultCode.FAILED,
723
+ error: err instanceof Error ? err : new Error(String(err))
724
+ });
725
+ });
726
+ }
727
+ shutdown() {
728
+ this.isShutdown = true;
729
+ return Promise.resolve();
730
+ }
731
+ };
732
+
733
+ // src/server/remote-exporter.ts
734
+ var DevtoolsRemoteExporter = class {
735
+ options;
736
+ pendingExports = [];
737
+ constructor(options) {
738
+ this.options = {
739
+ endpoint: options.endpoint.replace(/\/$/, ""),
740
+ // Remove trailing slash
741
+ apiKey: options.apiKey ?? "",
742
+ serviceName: options.serviceName ?? "unknown-service",
743
+ timeout: options.timeout ?? 5e3,
744
+ retry: options.retry ?? true,
745
+ retryCount: options.retryCount ?? 3,
746
+ retryDelay: options.retryDelay ?? 1e3,
747
+ verbose: options.verbose ?? false
748
+ };
749
+ }
750
+ /**
751
+ * Export spans to the remote server
752
+ */
753
+ async export(spans, resultCallback) {
754
+ const exportPromise = this.doExport(spans).then(() => {
755
+ resultCallback({ code: 0 });
756
+ }).catch((error) => {
757
+ this.log(`Export failed: ${error.message}`);
758
+ resultCallback({ code: 1 });
759
+ });
760
+ this.pendingExports.push(exportPromise);
761
+ exportPromise.finally(() => {
762
+ const index = this.pendingExports.indexOf(exportPromise);
763
+ if (index !== -1) {
764
+ this.pendingExports.splice(index, 1);
765
+ }
766
+ });
767
+ }
768
+ async doExport(spans) {
769
+ if (spans.length === 0) return;
770
+ this.log(`Exporting ${spans.length} span(s) to ${this.options.endpoint}`);
771
+ const traceMap = /* @__PURE__ */ new Map();
772
+ for (const span of spans) {
773
+ const traceId = span.spanContext().traceId;
774
+ if (!traceMap.has(traceId)) {
775
+ traceMap.set(traceId, []);
776
+ }
777
+ traceMap.get(traceId).push(span);
778
+ }
779
+ const traces = [];
780
+ for (const [traceId, traceSpans] of traceMap) {
781
+ traces.push(this.convertToTraceData(traceId, traceSpans));
782
+ }
783
+ await this.sendWithRetry({ traces });
784
+ }
785
+ async sendWithRetry(payload) {
786
+ let lastError = null;
787
+ const maxAttempts = this.options.retry ? this.options.retryCount : 1;
788
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
789
+ try {
790
+ await this.send(payload);
791
+ return;
792
+ } catch (error) {
793
+ lastError = error instanceof Error ? error : new Error(String(error));
794
+ this.log(
795
+ `Attempt ${attempt}/${maxAttempts} failed: ${lastError.message}`
796
+ );
797
+ if (attempt < maxAttempts) {
798
+ await this.sleep(this.options.retryDelay * attempt);
799
+ }
800
+ }
801
+ }
802
+ throw lastError || new Error("Export failed");
803
+ }
804
+ async send(payload) {
805
+ const controller = new AbortController();
806
+ const timeoutId = setTimeout(
807
+ () => controller.abort(),
808
+ this.options.timeout
809
+ );
810
+ try {
811
+ const headers = {
812
+ "Content-Type": "application/json"
813
+ };
814
+ if (this.options.apiKey) {
815
+ headers["Authorization"] = `Bearer ${this.options.apiKey}`;
816
+ }
817
+ const response = await fetch(`${this.options.endpoint}/ingest/traces`, {
818
+ method: "POST",
819
+ headers,
820
+ body: JSON.stringify(payload),
821
+ signal: controller.signal
822
+ });
823
+ if (!response.ok) {
824
+ const text = await response.text();
825
+ throw new Error(`HTTP ${response.status}: ${text}`);
826
+ }
827
+ const result = await response.json();
828
+ this.log(`Successfully sent ${result.processed} trace(s)`);
829
+ } finally {
830
+ clearTimeout(timeoutId);
831
+ }
832
+ }
833
+ /**
834
+ * Shutdown the exporter, waiting for pending exports
835
+ */
836
+ async shutdown() {
837
+ this.log("Shutting down, waiting for pending exports...");
838
+ await Promise.allSettled(this.pendingExports);
839
+ this.log("Shutdown complete");
840
+ }
841
+ /**
842
+ * Force flush pending exports
843
+ */
844
+ async forceFlush() {
845
+ await Promise.allSettled(this.pendingExports);
846
+ }
847
+ convertToTraceData(traceId, spans) {
848
+ const spanData = spans.map((span) => this.convertSpan(span));
849
+ const rootSpan = spanData.find((s) => !s.parentSpanId) || spanData[0];
850
+ spanData.sort((a, b) => a.startTime - b.startTime);
851
+ const startTime = Math.min(...spanData.map((s) => s.startTime));
852
+ const endTime = Math.max(...spanData.map((s) => s.endTime));
853
+ const hasError = spanData.some((s) => s.status.code === "ERROR");
854
+ const status = hasError ? "ERROR" : "OK";
855
+ return {
856
+ traceId,
857
+ correlationId: traceId.slice(0, 16),
858
+ rootSpan,
859
+ spans: spanData,
860
+ startTime,
861
+ endTime,
862
+ duration: endTime - startTime,
863
+ status,
864
+ service: this.options.serviceName
865
+ };
866
+ }
867
+ convertSpan(span) {
868
+ const spanContext = span.spanContext();
869
+ const startTime = span.startTime[0] * 1e3 + span.startTime[1] / 1e6;
870
+ const endTime = span.endTime[0] * 1e3 + span.endTime[1] / 1e6;
871
+ const attributes = {};
872
+ for (const [key, value] of Object.entries(span.attributes)) {
873
+ attributes[key] = value;
874
+ }
875
+ let status;
876
+ switch (span.status.code) {
877
+ case 0: {
878
+ status = "UNSET";
879
+ break;
880
+ }
881
+ case 1: {
882
+ status = "OK";
883
+ break;
884
+ }
885
+ case 2: {
886
+ status = "ERROR";
887
+ break;
888
+ }
889
+ default: {
890
+ status = "UNSET";
891
+ }
892
+ }
893
+ const events = span.events.map((event) => ({
894
+ name: event.name,
895
+ timestamp: event.time[0] * 1e3 + event.time[1] / 1e6,
896
+ attributes: event.attributes ? Object.fromEntries(Object.entries(event.attributes)) : void 0
897
+ }));
898
+ return {
899
+ traceId: spanContext.traceId,
900
+ spanId: spanContext.spanId,
901
+ parentSpanId: span.parentSpanId,
902
+ name: span.name,
903
+ kind: this.convertSpanKind(span.kind),
904
+ startTime,
905
+ endTime,
906
+ duration: endTime - startTime,
907
+ attributes,
908
+ status: {
909
+ code: status,
910
+ message: span.status.message
911
+ },
912
+ events: events.length > 0 ? events : void 0
913
+ };
914
+ }
915
+ convertSpanKind(kind) {
916
+ switch (kind) {
917
+ case 0: {
918
+ return "INTERNAL";
919
+ }
920
+ case 1: {
921
+ return "SERVER";
922
+ }
923
+ case 2: {
924
+ return "CLIENT";
925
+ }
926
+ case 3: {
927
+ return "PRODUCER";
928
+ }
929
+ case 4: {
930
+ return "CONSUMER";
931
+ }
932
+ default: {
933
+ return "INTERNAL";
934
+ }
935
+ }
936
+ }
937
+ sleep(ms) {
938
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
939
+ }
940
+ log(message) {
941
+ if (this.options.verbose) {
942
+ console.log(`[Devtools Remote Exporter] ${message}`);
943
+ }
944
+ }
945
+ };
946
+
947
+ // src/server/otlp.ts
948
+ function resolveOtlpValue(v) {
949
+ if (!v) return void 0;
950
+ if (v.stringValue !== void 0) return v.stringValue;
951
+ if (v.boolValue !== void 0) return v.boolValue;
952
+ if (v.intValue !== void 0) return typeof v.intValue === "string" ? Number(v.intValue) : v.intValue;
953
+ if (v.doubleValue !== void 0) return v.doubleValue;
954
+ if (v.bytesValue !== void 0) return v.bytesValue;
955
+ if (v.arrayValue?.values) return v.arrayValue.values.map(resolveOtlpValue);
956
+ if (v.kvlistValue?.values) return flattenAttributes(v.kvlistValue.values);
957
+ return void 0;
958
+ }
959
+ function flattenAttributes(attrs) {
960
+ const out = {};
961
+ if (!attrs) return out;
962
+ for (const { key, value } of attrs) {
963
+ out[key] = resolveOtlpValue(value);
964
+ }
965
+ return out;
966
+ }
967
+ function nanoToMs(nano) {
968
+ if (!nano) return 0;
969
+ return Number(BigInt(nano) / 1000000n);
970
+ }
971
+ var SPAN_KIND_MAP = {
972
+ 0: "INTERNAL",
973
+ 1: "INTERNAL",
974
+ 2: "SERVER",
975
+ 3: "CLIENT",
976
+ 4: "PRODUCER",
977
+ 5: "CONSUMER",
978
+ SPAN_KIND_INTERNAL: "INTERNAL",
979
+ SPAN_KIND_SERVER: "SERVER",
980
+ SPAN_KIND_CLIENT: "CLIENT",
981
+ SPAN_KIND_PRODUCER: "PRODUCER",
982
+ SPAN_KIND_CONSUMER: "CONSUMER"
983
+ };
984
+ function normalizeHexId(id) {
985
+ if (!id) return "";
986
+ const isBase64Like = /^[A-Za-z0-9+/=]+$/.test(id) && !/^[0-9a-f]+$/i.test(id);
987
+ const isLikelyBase64Id = isBase64Like && (id.length === 24 || id.length === 28 || id.length === 44 || id.length === 48);
988
+ if (isLikelyBase64Id) {
989
+ try {
990
+ const bytes = Buffer.from(id, "base64");
991
+ return bytes.toString("hex");
992
+ } catch {
993
+ }
994
+ }
995
+ return id;
996
+ }
997
+ function parseOtlpTraces(payload) {
998
+ if (!payload || typeof payload !== "object") return [];
999
+ const { resourceSpans } = payload;
1000
+ if (!Array.isArray(resourceSpans) || resourceSpans.length === 0) return [];
1001
+ const traceMap = /* @__PURE__ */ new Map();
1002
+ for (const rs of resourceSpans) {
1003
+ const resourceAttrs = flattenAttributes(rs.resource?.attributes);
1004
+ const service = String(resourceAttrs["service.name"] || "unknown");
1005
+ const scopeSpans = rs.scopeSpans || [];
1006
+ for (const ss of scopeSpans) {
1007
+ for (const span of ss.spans || []) {
1008
+ const traceId = normalizeHexId(span.traceId);
1009
+ if (!traceId) continue;
1010
+ const startMs = nanoToMs(span.startTimeUnixNano);
1011
+ const endMs = nanoToMs(span.endTimeUnixNano);
1012
+ const statusCode = span.status?.code;
1013
+ let status = "UNSET";
1014
+ if (statusCode === 1 || statusCode === "STATUS_CODE_OK") status = "OK";
1015
+ if (statusCode === 2 || statusCode === "STATUS_CODE_ERROR") status = "ERROR";
1016
+ const spanData = {
1017
+ traceId,
1018
+ spanId: normalizeHexId(span.spanId),
1019
+ parentSpanId: normalizeHexId(span.parentSpanId) || void 0,
1020
+ name: span.name || "unknown",
1021
+ kind: SPAN_KIND_MAP[span.kind ?? 0] || "INTERNAL",
1022
+ startTime: startMs,
1023
+ endTime: endMs,
1024
+ duration: endMs - startMs,
1025
+ attributes: { ...resourceAttrs, ...flattenAttributes(span.attributes) },
1026
+ status: { code: status, message: span.status?.message },
1027
+ events: (span.events || []).map((e) => ({
1028
+ name: e.name || "",
1029
+ timestamp: nanoToMs(e.timeUnixNano),
1030
+ attributes: flattenAttributes(e.attributes)
1031
+ }))
1032
+ };
1033
+ const existing = traceMap.get(traceId);
1034
+ if (existing) {
1035
+ existing.spans.push(spanData);
1036
+ } else {
1037
+ traceMap.set(traceId, { spans: [spanData], service });
1038
+ }
1039
+ }
1040
+ }
1041
+ }
1042
+ const traces = [];
1043
+ for (const [traceId, { spans, service }] of traceMap) {
1044
+ const sorted = spans.sort((a, b) => a.startTime - b.startTime);
1045
+ const rootSpan = sorted.find((s) => !s.parentSpanId) || sorted[0];
1046
+ const startTime = Math.min(...sorted.map((s) => s.startTime));
1047
+ const endTime = Math.max(...sorted.map((s) => s.endTime));
1048
+ const hasError = sorted.some((s) => s.status.code === "ERROR");
1049
+ traces.push({
1050
+ traceId,
1051
+ correlationId: traceId.slice(0, 16),
1052
+ rootSpan,
1053
+ spans: sorted,
1054
+ startTime,
1055
+ endTime,
1056
+ duration: endTime - startTime,
1057
+ status: hasError ? "ERROR" : "OK",
1058
+ service
1059
+ });
1060
+ }
1061
+ return traces;
1062
+ }
1063
+ function parseOtlpLogs(payload) {
1064
+ if (!payload || typeof payload !== "object") return [];
1065
+ const { resourceLogs } = payload;
1066
+ if (!Array.isArray(resourceLogs)) return [];
1067
+ const logs = [];
1068
+ for (const rl of resourceLogs) {
1069
+ const resourceAttrs = flattenAttributes(rl.resource?.attributes);
1070
+ for (const sl of rl.scopeLogs || []) {
1071
+ for (const rec of sl.logRecords || []) {
1072
+ const timestamp = nanoToMs(rec.timeUnixNano || rec.observedTimeUnixNano);
1073
+ const traceId = normalizeHexId(rec.traceId) || void 0;
1074
+ const spanId = normalizeHexId(rec.spanId) || void 0;
1075
+ const body = rec.body ? resolveOtlpValue(rec.body) : "";
1076
+ logs.push({
1077
+ id: `${traceId || "no-trace"}:${spanId || "no-span"}:${timestamp}:${rec.severityNumber || 0}`,
1078
+ traceId,
1079
+ spanId,
1080
+ resourceName: getResourceName(resourceAttrs),
1081
+ severityText: rec.severityText,
1082
+ severityNumber: rec.severityNumber,
1083
+ body: typeof body === "string" ? body : body,
1084
+ timestamp,
1085
+ attributes: flattenAttributes(rec.attributes),
1086
+ resource: resourceAttrs
1087
+ });
1088
+ }
1089
+ }
1090
+ }
1091
+ return logs;
1092
+ }
1093
+ function countOtlpMetrics(payload) {
1094
+ if (!payload || typeof payload !== "object") return 0;
1095
+ const { resourceMetrics } = payload;
1096
+ if (!Array.isArray(resourceMetrics)) return 0;
1097
+ let count = 0;
1098
+ for (const rm of resourceMetrics) {
1099
+ for (const sm of rm.scopeMetrics || []) {
1100
+ count += (sm.metrics || []).length;
1101
+ }
1102
+ }
1103
+ return count;
1104
+ }
1105
+ async function readJsonBody(req) {
1106
+ return new Promise((resolve2, reject) => {
1107
+ const chunks = [];
1108
+ req.on("data", (chunk) => chunks.push(chunk));
1109
+ req.on("end", () => {
1110
+ try {
1111
+ resolve2(JSON.parse(Buffer.concat(chunks).toString()));
1112
+ } catch {
1113
+ reject(new Error("Invalid JSON"));
1114
+ }
1115
+ });
1116
+ req.on("error", reject);
1117
+ });
1118
+ }
1119
+ function sendJson(res, status, data) {
1120
+ const body = JSON.stringify(data);
1121
+ res.writeHead(status, { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) });
1122
+ res.end(body);
1123
+ }
1124
+
1125
+ // src/server/http.ts
1126
+ function findPackageRoot() {
1127
+ let dir = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
1128
+ for (let i = 0; i < 5; i++) {
1129
+ if (fs.existsSync(path.resolve(dir, "package.json"))) return dir;
1130
+ dir = path.dirname(dir);
1131
+ }
1132
+ return dir;
1133
+ }
1134
+ var FULLPAGE_HTML = `<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>autotel-devtools</title><style>*{margin:0;padding:0;box-sizing:border-box}html,body{height:100%;width:100%;overflow:hidden}</style></head><body><script src="/widget.js?mode=fullpage"></script></body></html>`;
1135
+ var cachedWidgetJs = null;
1136
+ function getWidgetJs() {
1137
+ if (!cachedWidgetJs) {
1138
+ const pkgRoot = findPackageRoot();
1139
+ const candidates = [
1140
+ path.resolve(pkgRoot, "dist", "widget.global.js"),
1141
+ path.resolve(pkgRoot, "widget.global.js")
1142
+ ];
1143
+ for (const candidate of candidates) {
1144
+ try {
1145
+ cachedWidgetJs = fs.readFileSync(candidate, "utf8");
1146
+ break;
1147
+ } catch {
1148
+ }
1149
+ }
1150
+ if (!cachedWidgetJs) {
1151
+ cachedWidgetJs = "// widget bundle not found - run pnpm build first";
1152
+ }
1153
+ }
1154
+ return cachedWidgetJs;
1155
+ }
1156
+ function attachDevtoolsRoutes(httpServer, devtools) {
1157
+ httpServer.on("request", async (req, res) => {
1158
+ res.setHeader("Access-Control-Allow-Origin", "*");
1159
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
1160
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
1161
+ if (req.method === "OPTIONS") {
1162
+ res.writeHead(204);
1163
+ res.end();
1164
+ return;
1165
+ }
1166
+ const url = req.url || "/";
1167
+ if (req.method === "GET" && url === "/") {
1168
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Content-Length": Buffer.byteLength(FULLPAGE_HTML) });
1169
+ res.end(FULLPAGE_HTML);
1170
+ return;
1171
+ }
1172
+ if (req.method === "GET" && url.startsWith("/widget.js")) {
1173
+ const js = getWidgetJs();
1174
+ res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Content-Length": Buffer.byteLength(js) });
1175
+ res.end(js);
1176
+ return;
1177
+ }
1178
+ if (req.method === "GET" && url === "/healthz") {
1179
+ sendJson(res, 200, { ok: true, clients: devtools.clientCount });
1180
+ return;
1181
+ }
1182
+ if (req.method === "POST" && url === "/v1/traces") {
1183
+ try {
1184
+ const payload = await readJsonBody(req);
1185
+ const traces = parseOtlpTraces(payload);
1186
+ devtools.addTraces(traces);
1187
+ sendJson(res, 200, { acceptedTraces: traces.length });
1188
+ } catch (e) {
1189
+ sendJson(res, 400, { error: "Invalid OTLP JSON", message: e instanceof Error ? e.message : String(e) });
1190
+ }
1191
+ return;
1192
+ }
1193
+ if (req.method === "POST" && url === "/v1/logs") {
1194
+ try {
1195
+ const payload = await readJsonBody(req);
1196
+ const logs = parseOtlpLogs(payload);
1197
+ devtools.addLogs(logs);
1198
+ sendJson(res, 200, { acceptedLogs: logs.length });
1199
+ } catch (e) {
1200
+ sendJson(res, 400, { error: "Invalid OTLP JSON", message: e instanceof Error ? e.message : String(e) });
1201
+ }
1202
+ return;
1203
+ }
1204
+ if (req.method === "POST" && url === "/v1/metrics") {
1205
+ try {
1206
+ const payload = await readJsonBody(req);
1207
+ const count = countOtlpMetrics(payload);
1208
+ sendJson(res, 200, { acceptedMetrics: count });
1209
+ } catch (e) {
1210
+ sendJson(res, 400, { error: "Invalid OTLP JSON", message: e instanceof Error ? e.message : String(e) });
1211
+ }
1212
+ return;
1213
+ }
1214
+ sendJson(res, 404, { error: "Not found" });
1215
+ });
1216
+ }
1217
+ function createDevtoolsHttpServer(devtools, _options = {}) {
1218
+ const server = http.createServer();
1219
+ attachDevtoolsRoutes(server, devtools);
1220
+ return server;
1221
+ }
1222
+
1223
+ exports.DevtoolsLogExporter = DevtoolsLogExporter;
1224
+ exports.DevtoolsRemoteExporter = DevtoolsRemoteExporter;
1225
+ exports.DevtoolsServer = DevtoolsServer;
1226
+ exports.DevtoolsSpanExporter = DevtoolsSpanExporter;
1227
+ exports.ErrorAggregator = ErrorAggregator;
1228
+ exports.appendManyWithLimit = appendManyWithLimit;
1229
+ exports.appendWithLimit = appendWithLimit;
1230
+ exports.applyTelemetryLimits = applyTelemetryLimits;
1231
+ exports.attachDevtoolsRoutes = attachDevtoolsRoutes;
1232
+ exports.createDevtoolsHttpServer = createDevtoolsHttpServer;
1233
+ exports.parseOtlpLogs = parseOtlpLogs;
1234
+ exports.parseOtlpTraces = parseOtlpTraces;
1235
+ exports.resolveTelemetryLimits = resolveTelemetryLimits;
1236
+ //# sourceMappingURL=index.cjs.map
1237
+ //# sourceMappingURL=index.cjs.map