autotel-devtools 6.0.1 → 6.1.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/README.md +19 -0
- package/dist/cli.cjs +72 -7
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +72 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +39 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +39 -7
- package/dist/index.js.map +1 -1
- package/dist/server/index.cjs +62 -7
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +17 -1
- package/dist/server/index.d.ts +17 -1
- package/dist/server/index.js +61 -8
- package/dist/server/index.js.map +1 -1
- package/dist/widget.global.js +11 -8
- package/package.json +3 -3
package/dist/server/index.d.cts
CHANGED
|
@@ -24,6 +24,22 @@ declare function parseOtlpLogs(payload: unknown): LogData[];
|
|
|
24
24
|
*/
|
|
25
25
|
declare function isProtobufContentType(contentType?: string): boolean;
|
|
26
26
|
|
|
27
|
+
/** Value of the `x-autotel-devtools` response header and the /healthz `service` field. */
|
|
28
|
+
declare const DEVTOOLS_IDENTITY = "autotel-devtools";
|
|
29
|
+
/** Who is holding a TCP port, as far as we can tell over HTTP:
|
|
30
|
+
* - `autotel-devtools` — another instance of us (benign; the user has two running)
|
|
31
|
+
* - `foreign` — an HTTP server that is NOT us (e.g. an IDE's OTLP collector)
|
|
32
|
+
* - `none` — nothing answered HTTP (refused, timed out, or non-HTTP listener) */
|
|
33
|
+
type PortHolder = 'autotel-devtools' | 'foreign' | 'none';
|
|
34
|
+
/**
|
|
35
|
+
* Probe `host:port` over HTTP and classify what is listening. Used when our
|
|
36
|
+
* requested port is busy: it lets us tell "a stale autotel-devtools is still
|
|
37
|
+
* up" (benign) apart from "a foreign collector owns this port" — the latter is
|
|
38
|
+
* the silent footgun where apps keep exporting OTLP to the busy port and reach
|
|
39
|
+
* the wrong process, so the devtools UI stays empty and the app sees errors.
|
|
40
|
+
*/
|
|
41
|
+
declare function probePortHolder(host: string, port: number, timeoutMs?: number): Promise<PortHolder>;
|
|
42
|
+
|
|
27
43
|
/** Decode an OTLP/protobuf `ExportTraceServiceRequest` into the OTLP/JSON object shape. */
|
|
28
44
|
declare function decodeOtlpTraceRequest(body: Uint8Array): Record<string, unknown>;
|
|
29
45
|
/** Decode an OTLP/protobuf `ExportLogsServiceRequest` into the OTLP/JSON object shape. */
|
|
@@ -48,4 +64,4 @@ declare function appendWithLimit<T>(items: T[], item: T, limit: number): T[];
|
|
|
48
64
|
declare function appendManyWithLimit<T>(items: T[], incoming: T[], limit: number): T[];
|
|
49
65
|
declare function applyTelemetryLimits(data: DevtoolsData, limits: TelemetryLimits): DevtoolsData;
|
|
50
66
|
|
|
51
|
-
export { DevtoolsData, DevtoolsServer, type HttpServerOptions, LogData, type TelemetryLimits, TraceData, appendManyWithLimit, appendWithLimit, applyTelemetryLimits, attachDevtoolsRoutes, createDevtoolsHttpServer, decodeOtlpLogsRequest, decodeOtlpMetricsRequest, decodeOtlpTraceRequest, isProtobufContentType, parseOtlpLogs, parseOtlpTraces, resolveTelemetryLimits };
|
|
67
|
+
export { DEVTOOLS_IDENTITY, DevtoolsData, DevtoolsServer, type HttpServerOptions, LogData, type PortHolder, type TelemetryLimits, TraceData, appendManyWithLimit, appendWithLimit, applyTelemetryLimits, attachDevtoolsRoutes, createDevtoolsHttpServer, decodeOtlpLogsRequest, decodeOtlpMetricsRequest, decodeOtlpTraceRequest, isProtobufContentType, parseOtlpLogs, parseOtlpTraces, probePortHolder, resolveTelemetryLimits };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -24,6 +24,22 @@ declare function parseOtlpLogs(payload: unknown): LogData[];
|
|
|
24
24
|
*/
|
|
25
25
|
declare function isProtobufContentType(contentType?: string): boolean;
|
|
26
26
|
|
|
27
|
+
/** Value of the `x-autotel-devtools` response header and the /healthz `service` field. */
|
|
28
|
+
declare const DEVTOOLS_IDENTITY = "autotel-devtools";
|
|
29
|
+
/** Who is holding a TCP port, as far as we can tell over HTTP:
|
|
30
|
+
* - `autotel-devtools` — another instance of us (benign; the user has two running)
|
|
31
|
+
* - `foreign` — an HTTP server that is NOT us (e.g. an IDE's OTLP collector)
|
|
32
|
+
* - `none` — nothing answered HTTP (refused, timed out, or non-HTTP listener) */
|
|
33
|
+
type PortHolder = 'autotel-devtools' | 'foreign' | 'none';
|
|
34
|
+
/**
|
|
35
|
+
* Probe `host:port` over HTTP and classify what is listening. Used when our
|
|
36
|
+
* requested port is busy: it lets us tell "a stale autotel-devtools is still
|
|
37
|
+
* up" (benign) apart from "a foreign collector owns this port" — the latter is
|
|
38
|
+
* the silent footgun where apps keep exporting OTLP to the busy port and reach
|
|
39
|
+
* the wrong process, so the devtools UI stays empty and the app sees errors.
|
|
40
|
+
*/
|
|
41
|
+
declare function probePortHolder(host: string, port: number, timeoutMs?: number): Promise<PortHolder>;
|
|
42
|
+
|
|
27
43
|
/** Decode an OTLP/protobuf `ExportTraceServiceRequest` into the OTLP/JSON object shape. */
|
|
28
44
|
declare function decodeOtlpTraceRequest(body: Uint8Array): Record<string, unknown>;
|
|
29
45
|
/** Decode an OTLP/protobuf `ExportLogsServiceRequest` into the OTLP/JSON object shape. */
|
|
@@ -48,4 +64,4 @@ declare function appendWithLimit<T>(items: T[], item: T, limit: number): T[];
|
|
|
48
64
|
declare function appendManyWithLimit<T>(items: T[], incoming: T[], limit: number): T[];
|
|
49
65
|
declare function applyTelemetryLimits(data: DevtoolsData, limits: TelemetryLimits): DevtoolsData;
|
|
50
66
|
|
|
51
|
-
export { DevtoolsData, DevtoolsServer, type HttpServerOptions, LogData, type TelemetryLimits, TraceData, appendManyWithLimit, appendWithLimit, applyTelemetryLimits, attachDevtoolsRoutes, createDevtoolsHttpServer, decodeOtlpLogsRequest, decodeOtlpMetricsRequest, decodeOtlpTraceRequest, isProtobufContentType, parseOtlpLogs, parseOtlpTraces, resolveTelemetryLimits };
|
|
67
|
+
export { DEVTOOLS_IDENTITY, DevtoolsData, DevtoolsServer, type HttpServerOptions, LogData, type PortHolder, type TelemetryLimits, TraceData, appendManyWithLimit, appendWithLimit, applyTelemetryLimits, attachDevtoolsRoutes, createDevtoolsHttpServer, decodeOtlpLogsRequest, decodeOtlpMetricsRequest, decodeOtlpTraceRequest, isProtobufContentType, parseOtlpLogs, parseOtlpTraces, probePortHolder, resolveTelemetryLimits };
|
package/dist/server/index.js
CHANGED
|
@@ -433,13 +433,16 @@ var DevtoolsServer = class {
|
|
|
433
433
|
addTraces(traces) {
|
|
434
434
|
for (const trace of traces) this.addTrace(trace);
|
|
435
435
|
}
|
|
436
|
+
// `errors` is full-state on every broadcast (the client replaces, not appends),
|
|
437
|
+
// so non-trace broadcasts must echo the current error groups rather than `[]` —
|
|
438
|
+
// otherwise a log/metric arriving after an error would wipe it from the UI.
|
|
436
439
|
addLog(log) {
|
|
437
440
|
this.logs = appendWithLimit(this.logs, log, this.limits.maxLogCount);
|
|
438
|
-
this.broadcast({ traces: [], metrics: [], logs: [log], errors:
|
|
441
|
+
this.broadcast({ traces: [], metrics: [], logs: [log], errors: this.errorAggregator.getErrorGroups() });
|
|
439
442
|
}
|
|
440
443
|
addLogs(logs) {
|
|
441
444
|
this.logs = appendManyWithLimit(this.logs, logs, this.limits.maxLogCount);
|
|
442
|
-
this.broadcast({ traces: [], metrics: [], logs, errors:
|
|
445
|
+
this.broadcast({ traces: [], metrics: [], logs, errors: this.errorAggregator.getErrorGroups() });
|
|
443
446
|
}
|
|
444
447
|
addMetric(metric) {
|
|
445
448
|
this.metrics = appendWithLimit(
|
|
@@ -447,7 +450,7 @@ var DevtoolsServer = class {
|
|
|
447
450
|
metric,
|
|
448
451
|
this.limits.maxMetricCount
|
|
449
452
|
);
|
|
450
|
-
this.broadcast({ traces: [], metrics: [metric], logs: [], errors:
|
|
453
|
+
this.broadcast({ traces: [], metrics: [metric], logs: [], errors: this.errorAggregator.getErrorGroups() });
|
|
451
454
|
}
|
|
452
455
|
getCurrentData() {
|
|
453
456
|
return {
|
|
@@ -1386,7 +1389,38 @@ function decodeOtlpMetricsRequest(body) {
|
|
|
1386
1389
|
return decodeRequest("opentelemetry.proto.metrics.v1.ExportMetricsServiceRequest", body);
|
|
1387
1390
|
}
|
|
1388
1391
|
|
|
1392
|
+
// src/server/identity.ts
|
|
1393
|
+
var DEVTOOLS_IDENTITY = "autotel-devtools";
|
|
1394
|
+
async function probePortHolder(host, port, timeoutMs = 500) {
|
|
1395
|
+
const authority = host.includes(":") ? `[${host}]` : host;
|
|
1396
|
+
const controller = new AbortController();
|
|
1397
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1398
|
+
try {
|
|
1399
|
+
const res = await fetch(`http://${authority}:${port}/healthz`, {
|
|
1400
|
+
signal: controller.signal
|
|
1401
|
+
});
|
|
1402
|
+
if (res.headers.get("x-autotel-devtools")) return "autotel-devtools";
|
|
1403
|
+
try {
|
|
1404
|
+
const body = await res.json();
|
|
1405
|
+
if (body && body.service === DEVTOOLS_IDENTITY) return "autotel-devtools";
|
|
1406
|
+
} catch {
|
|
1407
|
+
}
|
|
1408
|
+
return "foreign";
|
|
1409
|
+
} catch {
|
|
1410
|
+
return "none";
|
|
1411
|
+
} finally {
|
|
1412
|
+
clearTimeout(timer);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1389
1416
|
// src/server/http.ts
|
|
1417
|
+
function sendOtlpError(res, req, e) {
|
|
1418
|
+
sendJson(res, 400, {
|
|
1419
|
+
error: "Invalid OTLP payload",
|
|
1420
|
+
message: e instanceof Error ? e.message : String(e),
|
|
1421
|
+
contentType: req.headers["content-type"] ?? null
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1390
1424
|
var PROTOBUF_DECODERS = {
|
|
1391
1425
|
traces: decodeOtlpTraceRequest,
|
|
1392
1426
|
logs: decodeOtlpLogsRequest,
|
|
@@ -1407,6 +1441,18 @@ function findPackageRoot() {
|
|
|
1407
1441
|
return dir;
|
|
1408
1442
|
}
|
|
1409
1443
|
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>`;
|
|
1444
|
+
var cachedVersion = null;
|
|
1445
|
+
function getVersion() {
|
|
1446
|
+
if (cachedVersion !== null) return cachedVersion;
|
|
1447
|
+
let version = "unknown";
|
|
1448
|
+
try {
|
|
1449
|
+
const pkg = JSON.parse(readFileSync(resolve(findPackageRoot(), "package.json"), "utf8"));
|
|
1450
|
+
if (typeof pkg.version === "string") version = pkg.version;
|
|
1451
|
+
} catch {
|
|
1452
|
+
}
|
|
1453
|
+
cachedVersion = version;
|
|
1454
|
+
return version;
|
|
1455
|
+
}
|
|
1410
1456
|
var cachedWidgetJs = null;
|
|
1411
1457
|
function getWidgetJs() {
|
|
1412
1458
|
if (!cachedWidgetJs) {
|
|
@@ -1433,6 +1479,8 @@ function attachDevtoolsRoutes(httpServer, devtools) {
|
|
|
1433
1479
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1434
1480
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
1435
1481
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
1482
|
+
res.setHeader("x-autotel-devtools", getVersion());
|
|
1483
|
+
res.setHeader("Access-Control-Expose-Headers", "x-autotel-devtools");
|
|
1436
1484
|
if (req.method === "OPTIONS") {
|
|
1437
1485
|
res.writeHead(204);
|
|
1438
1486
|
res.end();
|
|
@@ -1451,7 +1499,12 @@ function attachDevtoolsRoutes(httpServer, devtools) {
|
|
|
1451
1499
|
return;
|
|
1452
1500
|
}
|
|
1453
1501
|
if (req.method === "GET" && url === "/healthz") {
|
|
1454
|
-
sendJson(res, 200, {
|
|
1502
|
+
sendJson(res, 200, {
|
|
1503
|
+
ok: true,
|
|
1504
|
+
service: DEVTOOLS_IDENTITY,
|
|
1505
|
+
version: getVersion(),
|
|
1506
|
+
clients: devtools.clientCount
|
|
1507
|
+
});
|
|
1455
1508
|
return;
|
|
1456
1509
|
}
|
|
1457
1510
|
if (req.method === "GET" && url === "/v1/traces") {
|
|
@@ -1471,7 +1524,7 @@ function attachDevtoolsRoutes(httpServer, devtools) {
|
|
|
1471
1524
|
devtools.addTraces(traces);
|
|
1472
1525
|
sendJson(res, 200, { acceptedTraces: traces.length });
|
|
1473
1526
|
} catch (e) {
|
|
1474
|
-
|
|
1527
|
+
sendOtlpError(res, req, e);
|
|
1475
1528
|
}
|
|
1476
1529
|
return;
|
|
1477
1530
|
}
|
|
@@ -1482,7 +1535,7 @@ function attachDevtoolsRoutes(httpServer, devtools) {
|
|
|
1482
1535
|
devtools.addLogs(logs);
|
|
1483
1536
|
sendJson(res, 200, { acceptedLogs: logs.length });
|
|
1484
1537
|
} catch (e) {
|
|
1485
|
-
|
|
1538
|
+
sendOtlpError(res, req, e);
|
|
1486
1539
|
}
|
|
1487
1540
|
return;
|
|
1488
1541
|
}
|
|
@@ -1492,7 +1545,7 @@ function attachDevtoolsRoutes(httpServer, devtools) {
|
|
|
1492
1545
|
const count = countOtlpMetrics(payload);
|
|
1493
1546
|
sendJson(res, 200, { acceptedMetrics: count });
|
|
1494
1547
|
} catch (e) {
|
|
1495
|
-
|
|
1548
|
+
sendOtlpError(res, req, e);
|
|
1496
1549
|
}
|
|
1497
1550
|
return;
|
|
1498
1551
|
}
|
|
@@ -1505,6 +1558,6 @@ function createDevtoolsHttpServer(devtools, _options = {}) {
|
|
|
1505
1558
|
return server;
|
|
1506
1559
|
}
|
|
1507
1560
|
|
|
1508
|
-
export { DevtoolsLogExporter, DevtoolsRemoteExporter, DevtoolsServer, DevtoolsSpanExporter, ErrorAggregator, appendManyWithLimit, appendWithLimit, applyTelemetryLimits, attachDevtoolsRoutes, createDevtoolsHttpServer, decodeOtlpLogsRequest, decodeOtlpMetricsRequest, decodeOtlpTraceRequest, isProtobufContentType, parseOtlpLogs, parseOtlpTraces, resolveTelemetryLimits };
|
|
1561
|
+
export { DEVTOOLS_IDENTITY, DevtoolsLogExporter, DevtoolsRemoteExporter, DevtoolsServer, DevtoolsSpanExporter, ErrorAggregator, appendManyWithLimit, appendWithLimit, applyTelemetryLimits, attachDevtoolsRoutes, createDevtoolsHttpServer, decodeOtlpLogsRequest, decodeOtlpMetricsRequest, decodeOtlpTraceRequest, isProtobufContentType, parseOtlpLogs, parseOtlpTraces, probePortHolder, resolveTelemetryLimits };
|
|
1509
1562
|
//# sourceMappingURL=index.js.map
|
|
1510
1563
|
//# sourceMappingURL=index.js.map
|