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/README.md
CHANGED
|
@@ -111,6 +111,25 @@ const myFunction = trace((ctx) => async () => {
|
|
|
111
111
|
|
|
112
112
|
- **DevtoolsServer** - WebSocket server + in-memory data store
|
|
113
113
|
- **HTTP Routes** - OTLP receivers for traces/logs/metrics (JSON + protobuf)
|
|
114
|
+
|
|
115
|
+
#### Detecting the receiver
|
|
116
|
+
|
|
117
|
+
Every response carries an `x-autotel-devtools: <version>` header, and `GET /healthz`
|
|
118
|
+
returns `{ ok, service: "autotel-devtools", version, clients }`. Use either to confirm
|
|
119
|
+
you are talking to autotel-devtools rather than another OTLP collector that happens to
|
|
120
|
+
share the port — for example before pointing an exporter at `:4318`:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { probePortHolder } from 'autotel-devtools/server'
|
|
124
|
+
|
|
125
|
+
// 'autotel-devtools' | 'foreign' | 'none'
|
|
126
|
+
const holder = await probePortHolder('127.0.0.1', 4318)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
If you start the receiver and the requested port is held by a *foreign* process (some
|
|
130
|
+
IDEs run their own OTLP collector on `:4318`), the CLI falls forward to the next free
|
|
131
|
+
port and warns that exporters still aimed at the busy port are reaching that other
|
|
132
|
+
process — point them at the bound port, or free the original.
|
|
114
133
|
- **Exporters** - OpenTelemetry span/log exporters
|
|
115
134
|
|
|
116
135
|
### Widget (Svelte 5)
|
package/dist/cli.cjs
CHANGED
|
@@ -430,13 +430,16 @@ var DevtoolsServer = class {
|
|
|
430
430
|
addTraces(traces) {
|
|
431
431
|
for (const trace of traces) this.addTrace(trace);
|
|
432
432
|
}
|
|
433
|
+
// `errors` is full-state on every broadcast (the client replaces, not appends),
|
|
434
|
+
// so non-trace broadcasts must echo the current error groups rather than `[]` —
|
|
435
|
+
// otherwise a log/metric arriving after an error would wipe it from the UI.
|
|
433
436
|
addLog(log) {
|
|
434
437
|
this.logs = appendWithLimit(this.logs, log, this.limits.maxLogCount);
|
|
435
|
-
this.broadcast({ traces: [], metrics: [], logs: [log], errors:
|
|
438
|
+
this.broadcast({ traces: [], metrics: [], logs: [log], errors: this.errorAggregator.getErrorGroups() });
|
|
436
439
|
}
|
|
437
440
|
addLogs(logs) {
|
|
438
441
|
this.logs = appendManyWithLimit(this.logs, logs, this.limits.maxLogCount);
|
|
439
|
-
this.broadcast({ traces: [], metrics: [], logs, errors:
|
|
442
|
+
this.broadcast({ traces: [], metrics: [], logs, errors: this.errorAggregator.getErrorGroups() });
|
|
440
443
|
}
|
|
441
444
|
addMetric(metric) {
|
|
442
445
|
this.metrics = appendWithLimit(
|
|
@@ -444,7 +447,7 @@ var DevtoolsServer = class {
|
|
|
444
447
|
metric,
|
|
445
448
|
this.limits.maxMetricCount
|
|
446
449
|
);
|
|
447
|
-
this.broadcast({ traces: [], metrics: [metric], logs: [], errors:
|
|
450
|
+
this.broadcast({ traces: [], metrics: [metric], logs: [], errors: this.errorAggregator.getErrorGroups() });
|
|
448
451
|
}
|
|
449
452
|
getCurrentData() {
|
|
450
453
|
return {
|
|
@@ -923,7 +926,38 @@ function decodeOtlpMetricsRequest(body) {
|
|
|
923
926
|
return decodeRequest("opentelemetry.proto.metrics.v1.ExportMetricsServiceRequest", body);
|
|
924
927
|
}
|
|
925
928
|
|
|
929
|
+
// src/server/identity.ts
|
|
930
|
+
var DEVTOOLS_IDENTITY = "autotel-devtools";
|
|
931
|
+
async function probePortHolder(host, port, timeoutMs = 500) {
|
|
932
|
+
const authority = host.includes(":") ? `[${host}]` : host;
|
|
933
|
+
const controller = new AbortController();
|
|
934
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
935
|
+
try {
|
|
936
|
+
const res = await fetch(`http://${authority}:${port}/healthz`, {
|
|
937
|
+
signal: controller.signal
|
|
938
|
+
});
|
|
939
|
+
if (res.headers.get("x-autotel-devtools")) return "autotel-devtools";
|
|
940
|
+
try {
|
|
941
|
+
const body = await res.json();
|
|
942
|
+
if (body && body.service === DEVTOOLS_IDENTITY) return "autotel-devtools";
|
|
943
|
+
} catch {
|
|
944
|
+
}
|
|
945
|
+
return "foreign";
|
|
946
|
+
} catch {
|
|
947
|
+
return "none";
|
|
948
|
+
} finally {
|
|
949
|
+
clearTimeout(timer);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
926
953
|
// src/server/http.ts
|
|
954
|
+
function sendOtlpError(res, req, e) {
|
|
955
|
+
sendJson(res, 400, {
|
|
956
|
+
error: "Invalid OTLP payload",
|
|
957
|
+
message: e instanceof Error ? e.message : String(e),
|
|
958
|
+
contentType: req.headers["content-type"] ?? null
|
|
959
|
+
});
|
|
960
|
+
}
|
|
927
961
|
var PROTOBUF_DECODERS = {
|
|
928
962
|
traces: decodeOtlpTraceRequest,
|
|
929
963
|
logs: decodeOtlpLogsRequest,
|
|
@@ -944,6 +978,18 @@ function findPackageRoot() {
|
|
|
944
978
|
return dir;
|
|
945
979
|
}
|
|
946
980
|
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>`;
|
|
981
|
+
var cachedVersion = null;
|
|
982
|
+
function getVersion() {
|
|
983
|
+
if (cachedVersion !== null) return cachedVersion;
|
|
984
|
+
let version = "unknown";
|
|
985
|
+
try {
|
|
986
|
+
const pkg = JSON.parse(fs.readFileSync(path.resolve(findPackageRoot(), "package.json"), "utf8"));
|
|
987
|
+
if (typeof pkg.version === "string") version = pkg.version;
|
|
988
|
+
} catch {
|
|
989
|
+
}
|
|
990
|
+
cachedVersion = version;
|
|
991
|
+
return version;
|
|
992
|
+
}
|
|
947
993
|
var cachedWidgetJs = null;
|
|
948
994
|
function getWidgetJs() {
|
|
949
995
|
if (!cachedWidgetJs) {
|
|
@@ -970,6 +1016,8 @@ function attachDevtoolsRoutes(httpServer, devtools) {
|
|
|
970
1016
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
971
1017
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
972
1018
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
1019
|
+
res.setHeader("x-autotel-devtools", getVersion());
|
|
1020
|
+
res.setHeader("Access-Control-Expose-Headers", "x-autotel-devtools");
|
|
973
1021
|
if (req.method === "OPTIONS") {
|
|
974
1022
|
res.writeHead(204);
|
|
975
1023
|
res.end();
|
|
@@ -988,7 +1036,12 @@ function attachDevtoolsRoutes(httpServer, devtools) {
|
|
|
988
1036
|
return;
|
|
989
1037
|
}
|
|
990
1038
|
if (req.method === "GET" && url === "/healthz") {
|
|
991
|
-
sendJson(res, 200, {
|
|
1039
|
+
sendJson(res, 200, {
|
|
1040
|
+
ok: true,
|
|
1041
|
+
service: DEVTOOLS_IDENTITY,
|
|
1042
|
+
version: getVersion(),
|
|
1043
|
+
clients: devtools.clientCount
|
|
1044
|
+
});
|
|
992
1045
|
return;
|
|
993
1046
|
}
|
|
994
1047
|
if (req.method === "GET" && url === "/v1/traces") {
|
|
@@ -1008,7 +1061,7 @@ function attachDevtoolsRoutes(httpServer, devtools) {
|
|
|
1008
1061
|
devtools.addTraces(traces);
|
|
1009
1062
|
sendJson(res, 200, { acceptedTraces: traces.length });
|
|
1010
1063
|
} catch (e) {
|
|
1011
|
-
|
|
1064
|
+
sendOtlpError(res, req, e);
|
|
1012
1065
|
}
|
|
1013
1066
|
return;
|
|
1014
1067
|
}
|
|
@@ -1019,7 +1072,7 @@ function attachDevtoolsRoutes(httpServer, devtools) {
|
|
|
1019
1072
|
devtools.addLogs(logs);
|
|
1020
1073
|
sendJson(res, 200, { acceptedLogs: logs.length });
|
|
1021
1074
|
} catch (e) {
|
|
1022
|
-
|
|
1075
|
+
sendOtlpError(res, req, e);
|
|
1023
1076
|
}
|
|
1024
1077
|
return;
|
|
1025
1078
|
}
|
|
@@ -1029,7 +1082,7 @@ function attachDevtoolsRoutes(httpServer, devtools) {
|
|
|
1029
1082
|
const count = countOtlpMetrics(payload);
|
|
1030
1083
|
sendJson(res, 200, { acceptedMetrics: count });
|
|
1031
1084
|
} catch (e) {
|
|
1032
|
-
|
|
1085
|
+
sendOtlpError(res, req, e);
|
|
1033
1086
|
}
|
|
1034
1087
|
return;
|
|
1035
1088
|
}
|
|
@@ -1246,6 +1299,18 @@ async function main() {
|
|
|
1246
1299
|
attachSecondary: (s) => attachDevtoolsRoutes(s, wsServer)
|
|
1247
1300
|
});
|
|
1248
1301
|
const { addresses, warnings, port: boundPort } = await listeners.ready;
|
|
1302
|
+
if (boundPort !== options.port) {
|
|
1303
|
+
const holder = await probePortHolder(options.host, options.port);
|
|
1304
|
+
if (holder === "autotel-devtools") {
|
|
1305
|
+
warnings.push(
|
|
1306
|
+
`another autotel-devtools is already running on port ${options.port}; this instance is on ${boundPort}. Use the existing one, or stop it and restart here.`
|
|
1307
|
+
);
|
|
1308
|
+
} else {
|
|
1309
|
+
warnings.push(
|
|
1310
|
+
`port ${options.port} is held by another process that is NOT autotel-devtools. Anything exporting OTLP to :${options.port} is reaching that process, not this devtools. Point your exporter at :${boundPort}, or free :${options.port} and restart.`
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1249
1314
|
const uiBase = `http://${options.host === "localhost" ? "127.0.0.1" : options.host}:${boundPort}`;
|
|
1250
1315
|
const title = options.title || "autotel-devtools";
|
|
1251
1316
|
process.stdout.write(`
|