autotel-devtools 6.0.1 → 6.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.
- package/README.md +19 -0
- package/dist/cli.cjs +66 -4
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +66 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +33 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +33 -4
- package/dist/index.js.map +1 -1
- package/dist/server/index.cjs +56 -4
- 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 +55 -5
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
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
|
@@ -923,7 +923,38 @@ function decodeOtlpMetricsRequest(body) {
|
|
|
923
923
|
return decodeRequest("opentelemetry.proto.metrics.v1.ExportMetricsServiceRequest", body);
|
|
924
924
|
}
|
|
925
925
|
|
|
926
|
+
// src/server/identity.ts
|
|
927
|
+
var DEVTOOLS_IDENTITY = "autotel-devtools";
|
|
928
|
+
async function probePortHolder(host, port, timeoutMs = 500) {
|
|
929
|
+
const authority = host.includes(":") ? `[${host}]` : host;
|
|
930
|
+
const controller = new AbortController();
|
|
931
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
932
|
+
try {
|
|
933
|
+
const res = await fetch(`http://${authority}:${port}/healthz`, {
|
|
934
|
+
signal: controller.signal
|
|
935
|
+
});
|
|
936
|
+
if (res.headers.get("x-autotel-devtools")) return "autotel-devtools";
|
|
937
|
+
try {
|
|
938
|
+
const body = await res.json();
|
|
939
|
+
if (body && body.service === DEVTOOLS_IDENTITY) return "autotel-devtools";
|
|
940
|
+
} catch {
|
|
941
|
+
}
|
|
942
|
+
return "foreign";
|
|
943
|
+
} catch {
|
|
944
|
+
return "none";
|
|
945
|
+
} finally {
|
|
946
|
+
clearTimeout(timer);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
926
950
|
// src/server/http.ts
|
|
951
|
+
function sendOtlpError(res, req, e) {
|
|
952
|
+
sendJson(res, 400, {
|
|
953
|
+
error: "Invalid OTLP payload",
|
|
954
|
+
message: e instanceof Error ? e.message : String(e),
|
|
955
|
+
contentType: req.headers["content-type"] ?? null
|
|
956
|
+
});
|
|
957
|
+
}
|
|
927
958
|
var PROTOBUF_DECODERS = {
|
|
928
959
|
traces: decodeOtlpTraceRequest,
|
|
929
960
|
logs: decodeOtlpLogsRequest,
|
|
@@ -944,6 +975,18 @@ function findPackageRoot() {
|
|
|
944
975
|
return dir;
|
|
945
976
|
}
|
|
946
977
|
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>`;
|
|
978
|
+
var cachedVersion = null;
|
|
979
|
+
function getVersion() {
|
|
980
|
+
if (cachedVersion !== null) return cachedVersion;
|
|
981
|
+
let version = "unknown";
|
|
982
|
+
try {
|
|
983
|
+
const pkg = JSON.parse(fs.readFileSync(path.resolve(findPackageRoot(), "package.json"), "utf8"));
|
|
984
|
+
if (typeof pkg.version === "string") version = pkg.version;
|
|
985
|
+
} catch {
|
|
986
|
+
}
|
|
987
|
+
cachedVersion = version;
|
|
988
|
+
return version;
|
|
989
|
+
}
|
|
947
990
|
var cachedWidgetJs = null;
|
|
948
991
|
function getWidgetJs() {
|
|
949
992
|
if (!cachedWidgetJs) {
|
|
@@ -970,6 +1013,8 @@ function attachDevtoolsRoutes(httpServer, devtools) {
|
|
|
970
1013
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
971
1014
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
972
1015
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
1016
|
+
res.setHeader("x-autotel-devtools", getVersion());
|
|
1017
|
+
res.setHeader("Access-Control-Expose-Headers", "x-autotel-devtools");
|
|
973
1018
|
if (req.method === "OPTIONS") {
|
|
974
1019
|
res.writeHead(204);
|
|
975
1020
|
res.end();
|
|
@@ -988,7 +1033,12 @@ function attachDevtoolsRoutes(httpServer, devtools) {
|
|
|
988
1033
|
return;
|
|
989
1034
|
}
|
|
990
1035
|
if (req.method === "GET" && url === "/healthz") {
|
|
991
|
-
sendJson(res, 200, {
|
|
1036
|
+
sendJson(res, 200, {
|
|
1037
|
+
ok: true,
|
|
1038
|
+
service: DEVTOOLS_IDENTITY,
|
|
1039
|
+
version: getVersion(),
|
|
1040
|
+
clients: devtools.clientCount
|
|
1041
|
+
});
|
|
992
1042
|
return;
|
|
993
1043
|
}
|
|
994
1044
|
if (req.method === "GET" && url === "/v1/traces") {
|
|
@@ -1008,7 +1058,7 @@ function attachDevtoolsRoutes(httpServer, devtools) {
|
|
|
1008
1058
|
devtools.addTraces(traces);
|
|
1009
1059
|
sendJson(res, 200, { acceptedTraces: traces.length });
|
|
1010
1060
|
} catch (e) {
|
|
1011
|
-
|
|
1061
|
+
sendOtlpError(res, req, e);
|
|
1012
1062
|
}
|
|
1013
1063
|
return;
|
|
1014
1064
|
}
|
|
@@ -1019,7 +1069,7 @@ function attachDevtoolsRoutes(httpServer, devtools) {
|
|
|
1019
1069
|
devtools.addLogs(logs);
|
|
1020
1070
|
sendJson(res, 200, { acceptedLogs: logs.length });
|
|
1021
1071
|
} catch (e) {
|
|
1022
|
-
|
|
1072
|
+
sendOtlpError(res, req, e);
|
|
1023
1073
|
}
|
|
1024
1074
|
return;
|
|
1025
1075
|
}
|
|
@@ -1029,7 +1079,7 @@ function attachDevtoolsRoutes(httpServer, devtools) {
|
|
|
1029
1079
|
const count = countOtlpMetrics(payload);
|
|
1030
1080
|
sendJson(res, 200, { acceptedMetrics: count });
|
|
1031
1081
|
} catch (e) {
|
|
1032
|
-
|
|
1082
|
+
sendOtlpError(res, req, e);
|
|
1033
1083
|
}
|
|
1034
1084
|
return;
|
|
1035
1085
|
}
|
|
@@ -1246,6 +1296,18 @@ async function main() {
|
|
|
1246
1296
|
attachSecondary: (s) => attachDevtoolsRoutes(s, wsServer)
|
|
1247
1297
|
});
|
|
1248
1298
|
const { addresses, warnings, port: boundPort } = await listeners.ready;
|
|
1299
|
+
if (boundPort !== options.port) {
|
|
1300
|
+
const holder = await probePortHolder(options.host, options.port);
|
|
1301
|
+
if (holder === "autotel-devtools") {
|
|
1302
|
+
warnings.push(
|
|
1303
|
+
`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.`
|
|
1304
|
+
);
|
|
1305
|
+
} else {
|
|
1306
|
+
warnings.push(
|
|
1307
|
+
`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.`
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1249
1311
|
const uiBase = `http://${options.host === "localhost" ? "127.0.0.1" : options.host}:${boundPort}`;
|
|
1250
1312
|
const title = options.title || "autotel-devtools";
|
|
1251
1313
|
process.stdout.write(`
|