@tracepanel/js 0.0.0-stage → 0.2.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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +271 -2
  3. package/dist/agent.d.ts +42 -0
  4. package/dist/agent.js +249 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/api.d.ts +11 -0
  7. package/dist/api.js +34 -0
  8. package/dist/api.js.map +1 -0
  9. package/dist/browser.d.ts +25 -0
  10. package/dist/browser.js +438 -0
  11. package/dist/browser.js.map +1 -0
  12. package/dist/bullmq.d.ts +7 -0
  13. package/dist/bullmq.js +86 -0
  14. package/dist/bullmq.js.map +1 -0
  15. package/dist/bun.d.ts +15 -0
  16. package/dist/bun.js +114 -0
  17. package/dist/bun.js.map +1 -0
  18. package/dist/calls.d.ts +20 -0
  19. package/dist/calls.js +145 -0
  20. package/dist/calls.js.map +1 -0
  21. package/dist/cli.d.ts +2 -0
  22. package/dist/cli.js +39 -0
  23. package/dist/cli.js.map +1 -0
  24. package/dist/config.d.ts +29 -0
  25. package/dist/config.js +48 -0
  26. package/dist/config.js.map +1 -0
  27. package/dist/console.d.ts +6 -0
  28. package/dist/console.js +44 -0
  29. package/dist/console.js.map +1 -0
  30. package/dist/express.d.ts +2 -0
  31. package/dist/express.js +77 -0
  32. package/dist/express.js.map +1 -0
  33. package/dist/frameworks.d.ts +2 -0
  34. package/dist/frameworks.js +39 -0
  35. package/dist/frameworks.js.map +1 -0
  36. package/dist/headers.d.ts +2 -0
  37. package/dist/headers.js +42 -0
  38. package/dist/headers.js.map +1 -0
  39. package/dist/index.d.ts +5 -0
  40. package/dist/index.js +3 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/logs.d.ts +8 -0
  43. package/dist/logs.js +54 -0
  44. package/dist/logs.js.map +1 -0
  45. package/dist/model.d.ts +57 -0
  46. package/dist/model.js +18 -0
  47. package/dist/model.js.map +1 -0
  48. package/dist/next-widget.d.ts +3 -0
  49. package/dist/next-widget.js +17 -0
  50. package/dist/next-widget.js.map +1 -0
  51. package/dist/next.d.ts +2 -0
  52. package/dist/next.js +16 -0
  53. package/dist/next.js.map +1 -0
  54. package/dist/nuxt-plugin.d.ts +24 -0
  55. package/dist/nuxt-plugin.js +39 -0
  56. package/dist/nuxt-plugin.js.map +1 -0
  57. package/dist/nuxt.d.ts +10 -0
  58. package/dist/nuxt.js +16 -0
  59. package/dist/nuxt.js.map +1 -0
  60. package/dist/processor.d.ts +105 -0
  61. package/dist/processor.js +482 -0
  62. package/dist/processor.js.map +1 -0
  63. package/dist/register.d.ts +1 -0
  64. package/dist/register.js +5 -0
  65. package/dist/register.js.map +1 -0
  66. package/dist/runtime.d.ts +17 -0
  67. package/dist/runtime.js +27 -0
  68. package/dist/runtime.js.map +1 -0
  69. package/dist/setup.d.ts +10 -0
  70. package/dist/setup.js +99 -0
  71. package/dist/setup.js.map +1 -0
  72. package/dist/sql.d.ts +3 -0
  73. package/dist/sql.js +18 -0
  74. package/dist/sql.js.map +1 -0
  75. package/dist/stack.d.ts +13 -0
  76. package/dist/stack.js +66 -0
  77. package/dist/stack.js.map +1 -0
  78. package/dist/vite.d.ts +36 -0
  79. package/dist/vite.js +139 -0
  80. package/dist/vite.js.map +1 -0
  81. package/dist/widget.d.ts +26 -0
  82. package/dist/widget.js +70 -0
  83. package/dist/widget.js.map +1 -0
  84. package/package.json +125 -4
package/dist/bun.js ADDED
@@ -0,0 +1,114 @@
1
+ // Bun's own server, Bun.serve, doesn't go through node:http: a request's root span comes from here. `serve()` takes
2
+ // Bun.serve's options; `withTracepanel()` wraps any fetch handler (Hono's app.fetch, Elysia's handle).
3
+ //
4
+ // import { serve } from "@tracepanel/js/bun";
5
+ // serve({ routes: { "/api/books/:id": (req) => ... }, fetch: (req) => ... });
6
+ //
7
+ // Start the app with `bun --preload @tracepanel/js/register server.ts` so the database driver is hooked as it loads.
8
+ import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
9
+ import { start } from "./setup.js";
10
+ import { exposedHeaders, inject } from "./widget.js";
11
+ const tracer = trace.getTracer("tracepanel-bun");
12
+ /** What Bun answers an error with when the app has no `error` handler of its own. */
13
+ function internalError(error) {
14
+ // biome-ignore lint/suspicious/noConsole: printing the error is what Bun does in this case too
15
+ console.error(error);
16
+ return new Response("Internal Server Error", { status: 500 });
17
+ }
18
+ /**
19
+ * The handler, in a server span for each request: its route, status, errors; the badge's headers and script. An
20
+ * error it throws is answered here, by `onError` (Bun.serve's `error` option) or a 500, so the answer has its trace.
21
+ */
22
+ export function withTracepanel(handler, route, onError) {
23
+ const tracepanel = start({ httpServer: false });
24
+ if (!tracepanel)
25
+ return handler;
26
+ const traced = (request, server) => {
27
+ const url = new URL(request.url);
28
+ const attributes = {
29
+ "http.request.method": request.method,
30
+ "url.path": url.pathname,
31
+ "http.target": url.pathname + url.search,
32
+ ...(route ? { "http.route": route } : {}),
33
+ };
34
+ const startedMs = performance.now();
35
+ return tracer.startActiveSpan(`${request.method}`, { kind: SpanKind.SERVER, attributes }, async (span) => {
36
+ tracepanel.processor.setFramework("Bun.serve");
37
+ try {
38
+ let response;
39
+ try {
40
+ response = await handler(request, server);
41
+ }
42
+ catch (thrown) {
43
+ const error = thrown instanceof Error ? thrown : new Error(String(thrown));
44
+ span.recordException(error);
45
+ response = await (onError ?? internalError)(error);
46
+ }
47
+ span.setAttribute("http.response.status_code", response.status);
48
+ if (response.status >= 500)
49
+ span.setStatus({ code: SpanStatusCode.ERROR });
50
+ return await marked(tracepanel, request, response, startedMs);
51
+ }
52
+ finally {
53
+ span.end();
54
+ }
55
+ });
56
+ };
57
+ return traced;
58
+ }
59
+ /** The trace's headers on the response, and the widget in an HTML page. */
60
+ async function marked(tracepanel, request, response, startedMs) {
61
+ const page = (response.headers.get("content-type") ?? "").includes("text/html");
62
+ const marks = tracepanel.badge({
63
+ method: request.method,
64
+ status: response.status,
65
+ startedMs,
66
+ page,
67
+ framework: tracepanel.processor.framework() ?? "Bun.serve",
68
+ });
69
+ if (!marks)
70
+ return response;
71
+ const headers = new Headers(response.headers);
72
+ for (const [name, value] of Object.entries(marks.headers))
73
+ headers.set(name, value);
74
+ const expose = exposedHeaders(headers.get("access-control-allow-origin") ?? undefined, headers.get("access-control-expose-headers"));
75
+ if (expose)
76
+ headers.set("Access-Control-Expose-Headers", expose);
77
+ if (!marks.script)
78
+ return new Response(response.body, { status: response.status, headers });
79
+ headers.delete("content-length");
80
+ const body = inject(await response.text(), marks.script);
81
+ return new Response(body, { status: response.status, statusText: response.statusText, headers });
82
+ }
83
+ /** Bun.serve's routes, each handler traced under its pattern: `/api/books/:id`. */
84
+ function tracedRoutes(routes, onError) {
85
+ const traced = {};
86
+ for (const [pattern, value] of Object.entries(routes)) {
87
+ if (typeof value === "function") {
88
+ traced[pattern] = withTracepanel(value, pattern, onError);
89
+ }
90
+ else if (value instanceof Response) {
91
+ traced[pattern] = value;
92
+ }
93
+ else {
94
+ const methods = {};
95
+ for (const [method, handler] of Object.entries(value)) {
96
+ methods[method] = withTracepanel(handler, pattern, onError);
97
+ }
98
+ traced[pattern] = methods;
99
+ }
100
+ }
101
+ return traced;
102
+ }
103
+ /** Bun.serve, with every route and the fetch fallback traced. */
104
+ export function serve(options) {
105
+ const bun = globalThis.Bun;
106
+ if (!bun)
107
+ throw new Error("@tracepanel/js/bun's serve() runs on Bun");
108
+ return bun.serve({
109
+ ...options,
110
+ ...(options.routes ? { routes: tracedRoutes(options.routes, options.error) } : {}),
111
+ ...(options.fetch ? { fetch: withTracepanel(options.fetch, undefined, options.error) } : {}),
112
+ });
113
+ }
114
+ //# sourceMappingURL=bun.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bun.js","sourceRoot":"","sources":["../src/bun.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { Attrs } from "./model.js";
2
+ /** The URL without its credentials, and with secret query values masked. */
3
+ export declare function safeUrl(raw: string): string;
4
+ /** An outgoing HTTP call: `GET api.example.com/users/42`. */
5
+ export declare function httpCall(attrs: Attrs): {
6
+ name: string;
7
+ attrs: Attrs;
8
+ };
9
+ export declare function isRedis(attrs: Attrs): boolean;
10
+ /** A Redis command: `GET post:42:summary`, its key, and where the server is. */
11
+ export declare function redisCall(attrs: Attrs, spanName: string): {
12
+ name: string;
13
+ attrs: Attrs;
14
+ };
15
+ export declare function isMongo(attrs: Attrs): boolean;
16
+ /** A MongoDB command: `find users`, its shape with the values as "?", and a hash of the values (duplicates). */
17
+ export declare function mongoCall(attrs: Attrs, spanName: string): {
18
+ name: string;
19
+ attrs: Attrs;
20
+ };
package/dist/calls.js ADDED
@@ -0,0 +1,145 @@
1
+ // Calls to other systems, as the agent and the other SDKs name them: an outgoing HTTP call (http.client), a Redis
2
+ // command (redis), a MongoDB command (mongodb). OpenTelemetry's instrumentations describe them; these are the
3
+ // Tracepanel spans' names and attributes.
4
+ import { createHash } from "node:crypto";
5
+ function first(attrs, ...keys) {
6
+ for (const key of keys) {
7
+ if (attrs[key] !== undefined)
8
+ return attrs[key];
9
+ }
10
+ return undefined;
11
+ }
12
+ // Query parameters whose values are secrets: their values become "***".
13
+ const SECRET = /(token|key|secret|password|passwd|pwd|auth|signature|sig|session|credential)/i;
14
+ /** The URL without its credentials, and with secret query values masked. */
15
+ export function safeUrl(raw) {
16
+ try {
17
+ const url = new URL(raw);
18
+ url.username = "";
19
+ url.password = "";
20
+ for (const name of [...url.searchParams.keys()]) {
21
+ if (SECRET.test(name))
22
+ url.searchParams.set(name, "***");
23
+ }
24
+ return url.toString();
25
+ }
26
+ catch {
27
+ return raw;
28
+ }
29
+ }
30
+ /** An outgoing HTTP call: `GET api.example.com/users/42`. */
31
+ export function httpCall(attrs) {
32
+ const method = String(first(attrs, "http.request.method", "http.method") ?? "GET").toUpperCase();
33
+ const raw = String(first(attrs, "url.full", "http.url") ?? "");
34
+ let host = String(first(attrs, "server.address", "net.peer.name") ?? "");
35
+ let path = "/";
36
+ let port = first(attrs, "server.port", "net.peer.port");
37
+ let scheme = "http";
38
+ try {
39
+ const url = new URL(raw);
40
+ host = url.hostname;
41
+ path = url.pathname || "/";
42
+ scheme = url.protocol.replace(":", "");
43
+ port = url.port ? Number(url.port) : scheme === "https" ? 443 : 80;
44
+ }
45
+ catch {
46
+ // No full URL: the host and port the instrumentation gave.
47
+ }
48
+ const status = first(attrs, "http.response.status_code", "http.status_code");
49
+ return {
50
+ name: `${method} ${host}${path}`,
51
+ attrs: {
52
+ "http.request.method": method,
53
+ "url.full": safeUrl(raw),
54
+ "url.scheme": scheme,
55
+ "server.address": host,
56
+ "server.port": port ?? null,
57
+ ...(status === undefined ? {} : { "http.response.status_code": status }),
58
+ },
59
+ };
60
+ }
61
+ export function isRedis(attrs) {
62
+ return first(attrs, "db.system.name", "db.system") === "redis";
63
+ }
64
+ /** A Redis command: `GET post:42:summary`, its key, and where the server is. */
65
+ export function redisCall(attrs, spanName) {
66
+ const statement = String(first(attrs, "db.query.text", "db.statement") ?? "");
67
+ const words = statement.split(/\s+/).filter(Boolean);
68
+ const command = String(first(attrs, "db.operation.name", "db.operation") ?? words[0] ?? spanName).toUpperCase();
69
+ const key = words[1] && words[1] !== "?" ? words[1] : null;
70
+ const namespace = first(attrs, "db.namespace", "db.redis.database_index");
71
+ return {
72
+ name: key && command !== "PIPELINE" && command !== "MULTI" ? `${command} ${key}` : command,
73
+ attrs: {
74
+ "db.system": "redis",
75
+ "db.operation": command,
76
+ "redis.key": key,
77
+ "redis.alias": namespace === undefined || namespace === null || namespace === 0 ? "redis" : `redis/${namespace}`,
78
+ "server.address": first(attrs, "server.address", "net.peer.name") ?? null,
79
+ "server.port": first(attrs, "server.port", "net.peer.port") ?? null,
80
+ },
81
+ };
82
+ }
83
+ export function isMongo(attrs) {
84
+ return first(attrs, "db.system.name", "db.system") === "mongodb";
85
+ }
86
+ // Where a command carries the app's values: they become "?" in its shape, as in the Python SDK.
87
+ const VALUES = new Set(["filter", "query", "documents", "updates", "deletes", "pipeline", "q", "u", "update"]);
88
+ // What the driver adds for itself.
89
+ const NOISE = new Set([
90
+ "lsid",
91
+ "$clusterTime",
92
+ "$db",
93
+ "txnNumber",
94
+ "$readPreference",
95
+ "autocommit",
96
+ "startTransaction",
97
+ ]);
98
+ function shape(value, values) {
99
+ if (Array.isArray(value)) {
100
+ if (value.every((item) => item === null || typeof item !== "object")) {
101
+ values.push(...value);
102
+ return ["?"];
103
+ }
104
+ return value.map((item) => shape(item, values));
105
+ }
106
+ if (value !== null && typeof value === "object") {
107
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, shape(item, values)]));
108
+ }
109
+ values.push(value);
110
+ return "?";
111
+ }
112
+ /** A MongoDB command: `find users`, its shape with the values as "?", and a hash of the values (duplicates). */
113
+ export function mongoCall(attrs, spanName) {
114
+ const operation = String(first(attrs, "db.operation.name", "db.operation") ?? spanName.split(".").at(-1) ?? "");
115
+ const collection = first(attrs, "db.collection.name", "db.mongodb.collection");
116
+ const raw = String(first(attrs, "db.query.text", "db.statement") ?? "");
117
+ let statement = raw;
118
+ let hash = null;
119
+ try {
120
+ const command = JSON.parse(raw);
121
+ const values = [];
122
+ const shaped = Object.fromEntries(Object.entries(command)
123
+ .filter(([key]) => !NOISE.has(key))
124
+ .map(([key, value]) => [key, VALUES.has(key) ? shape(value, values) : value]));
125
+ statement = JSON.stringify(shaped);
126
+ hash = createHash("sha1").update(JSON.stringify(values)).digest("hex").slice(0, 12);
127
+ }
128
+ catch {
129
+ // Not JSON: kept as the instrumentation wrote it.
130
+ }
131
+ return {
132
+ name: collection ? `${operation} ${collection}` : operation,
133
+ attrs: {
134
+ "db.system": "mongodb",
135
+ "db.operation": operation,
136
+ "db.namespace": first(attrs, "db.namespace", "db.name") ?? null,
137
+ "db.collection.name": collection ?? null,
138
+ "db.statement": statement.length > 2000 ? `${statement.slice(0, 1999)}…` : statement,
139
+ "db.params_hash": hash,
140
+ "server.address": first(attrs, "server.address", "net.peer.name") ?? null,
141
+ "server.port": first(attrs, "server.port", "net.peer.port") ?? null,
142
+ },
143
+ };
144
+ }
145
+ //# sourceMappingURL=calls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calls.js","sourceRoot":"","sources":["../src/calls.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ // `tracepanel run <command>`, `tracepanel dev`, `tracepanel mcp`: as the Python package's, for Node apps.
3
+ import { spawn } from "node:child_process";
4
+ import { createRequire } from "node:module";
5
+ import { loadConfig } from "./config.js";
6
+ const HELP = `usage: tracepanel <command>
7
+ run <command...> run a Node command with Tracepanel on (node --import @tracepanel/js/register)
8
+ dev [--port 8765] run the agent in the foreground, with the console on http://127.0.0.1:8765/
9
+ mcp [--values] an MCP server for AI coding agents, on stdin and stdout`;
10
+ function agentBinary() {
11
+ return createRequire(import.meta.url)("@tracepanel/agent").binaryPath();
12
+ }
13
+ function exec(command, args, env = process.env) {
14
+ const child = spawn(command, args, { stdio: "inherit", env });
15
+ child.on("exit", (code, signal) => {
16
+ if (signal)
17
+ process.kill(process.pid, signal);
18
+ process.exit(code ?? 1);
19
+ });
20
+ }
21
+ const [command, ...args] = process.argv.slice(2);
22
+ const config = loadConfig();
23
+ if (command === "run" && args.length > 0) {
24
+ const register = new URL("./register.js", import.meta.url).href;
25
+ const options = [process.env.NODE_OPTIONS, `--import ${register}`].filter(Boolean).join(" ");
26
+ exec(args[0], args.slice(1), { ...process.env, NODE_OPTIONS: options, TRACEPANEL_ENABLED: "1" });
27
+ }
28
+ else if (command === "dev") {
29
+ const port = args[args.indexOf("--port") + 1] && args.includes("--port") ? args[args.indexOf("--port") + 1] : "8765";
30
+ exec(agentBinary(), ["--dir", config.storeDir, "--serve", `127.0.0.1:${port}`]);
31
+ }
32
+ else if (command === "mcp") {
33
+ exec(agentBinary(), ["mcp", "--dir", config.storeDir, ...(args.includes("--values") ? ["--values"] : [])]);
34
+ }
35
+ else {
36
+ process.stdout.write(`${HELP}\n`);
37
+ process.exit(command ? 2 : 0);
38
+ }
39
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,0GAA0G;AAE1G,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,IAAI,GAAG;;;6EAGgE,CAAC;AAE9E,SAAS,WAAW;IAClB,OAAQ,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,mBAAmB,CAA8B,CAAC,UAAU,EAAE,CAAC;AACxG,CAAC;AAED,SAAS,IAAI,CAAC,OAAe,EAAE,IAAc,EAAE,GAAG,GAAsB,OAAO,CAAC,GAAG;IACjF,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9D,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;QAChC,IAAI,MAAM;YAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC9C,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACjD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;AAE5B,IAAI,OAAO,KAAK,KAAK,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;IACzC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,eAAe,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;IAChE,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,YAAY,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7F,IAAI,CAAC,IAAI,CAAC,CAAC,CAAW,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,kBAAkB,EAAE,GAAG,EAAE,CAAC,CAAC;AAC7G,CAAC;KAAM,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;IAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACrH,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,QAAQ,EAAE,SAAS,EAAE,aAAa,IAAI,EAAE,CAAC,CAAC,CAAC;AAClF,CAAC;KAAM,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;IAC7B,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAC7G,CAAC;KAAM,CAAC;IACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;IAClC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChC,CAAC"}
@@ -0,0 +1,29 @@
1
+ /** Where Tracepanel keeps its state and what it calls the app: from TRACEPANEL_* variables and the project. */
2
+ export interface Config {
3
+ enabled: boolean;
4
+ /** The project's directory: paths in stacks are relative to it, and the store lives in it. */
5
+ root: string;
6
+ /** The store directory the agent uses (.tracepanel), where it leaves its addresses. */
7
+ storeDir: string;
8
+ /** The app's name on the service map: TRACEPANEL_SERVICE, package.json's name, or the directory's. */
9
+ service: string;
10
+ maxTraces: number;
11
+ /** The agent's limits for the issues it finds, as the Python SDK names them: TRACEPANEL_SLOW_SQL_MS and so on. */
12
+ thresholds: Thresholds;
13
+ }
14
+ /** What the agent reports, and from when. Its defaults are these too. */
15
+ export interface Thresholds {
16
+ /** Repeats of one statement or call in a request that make an N+1. */
17
+ n_plus_one: number;
18
+ /** For calls to memory stores (Redis, a cache): an N+1 only once the repeats cost this much together. */
19
+ n_plus_one_min_ms: number;
20
+ slow_sql_ms: number;
21
+ /** Outgoing HTTP calls, and RPC calls. */
22
+ slow_http_ms: number;
23
+ long_transaction_ms: number;
24
+ /** A task that waited this long in its queue. */
25
+ queue_wait_ms: number;
26
+ /** A SELECT without a LIMIT that returned this many rows. */
27
+ large_result_rows: number;
28
+ }
29
+ export declare function loadConfig(env?: NodeJS.ProcessEnv): Config;
package/dist/config.js ADDED
@@ -0,0 +1,48 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { basename, join, resolve } from "node:path";
3
+ const THRESHOLDS = {
4
+ n_plus_one: ["TRACEPANEL_N_PLUS_ONE_THRESHOLD", 3],
5
+ n_plus_one_min_ms: ["TRACEPANEL_N_PLUS_ONE_MIN_MS", 5],
6
+ slow_sql_ms: ["TRACEPANEL_SLOW_SQL_MS", 100],
7
+ slow_http_ms: ["TRACEPANEL_SLOW_HTTP_MS", 500],
8
+ long_transaction_ms: ["TRACEPANEL_LONG_TRANSACTION_MS", 500],
9
+ queue_wait_ms: ["TRACEPANEL_QUEUE_WAIT_MS", 1000],
10
+ large_result_rows: ["TRACEPANEL_LARGE_RESULT_ROWS", 1000],
11
+ };
12
+ function thresholds(env) {
13
+ const found = {};
14
+ for (const [name, [variable, fallback]] of Object.entries(THRESHOLDS)) {
15
+ const value = Number(env[variable]);
16
+ found[name] = Number.isFinite(value) && env[variable] !== undefined && env[variable] !== "" ? value : fallback;
17
+ }
18
+ return found;
19
+ }
20
+ const OFF = new Set(["0", "false", "no", "off"]);
21
+ /** On in development: unless NODE_ENV is production, or TRACEPANEL_ENABLED turns it off (or on). */
22
+ function enabled(env) {
23
+ const explicit = env.TRACEPANEL_ENABLED?.toLowerCase();
24
+ if (explicit !== undefined && explicit !== "")
25
+ return !OFF.has(explicit);
26
+ return env.NODE_ENV !== "production";
27
+ }
28
+ function packageName(root) {
29
+ try {
30
+ const name = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).name;
31
+ return typeof name === "string" && name !== "" ? name : undefined;
32
+ }
33
+ catch {
34
+ return undefined;
35
+ }
36
+ }
37
+ export function loadConfig(env = process.env) {
38
+ const root = resolve(env.TRACEPANEL_ROOT ?? process.cwd());
39
+ return {
40
+ enabled: enabled(env),
41
+ root,
42
+ storeDir: resolve(root, env.TRACEPANEL_DIR ?? ".tracepanel"),
43
+ service: env.TRACEPANEL_SERVICE || packageName(root) || basename(root),
44
+ maxTraces: Number(env.TRACEPANEL_MAX_TRACES) || 2000,
45
+ thresholds: thresholds(env),
46
+ };
47
+ }
48
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgCpD,MAAM,UAAU,GAA+C;IAC7D,UAAU,EAAE,CAAC,iCAAiC,EAAE,CAAC,CAAC;IAClD,iBAAiB,EAAE,CAAC,8BAA8B,EAAE,CAAC,CAAC;IACtD,WAAW,EAAE,CAAC,wBAAwB,EAAE,GAAG,CAAC;IAC5C,YAAY,EAAE,CAAC,yBAAyB,EAAE,GAAG,CAAC;IAC9C,mBAAmB,EAAE,CAAC,gCAAgC,EAAE,GAAG,CAAC;IAC5D,aAAa,EAAE,CAAC,0BAA0B,EAAE,IAAI,CAAC;IACjD,iBAAiB,EAAE,CAAC,8BAA8B,EAAE,IAAI,CAAC;CAC1D,CAAC;AAEF,SAAS,UAAU,CAAC,GAAsB;IACxC,MAAM,KAAK,GAAG,EAAgB,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAA2C,EAAE,CAAC;QAChH,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;QACpC,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,SAAS,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;IACjH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AAEjD,oGAAoG;AACpG,SAAS,OAAO,CAAC,GAAsB;IACrC,MAAM,QAAQ,GAAG,GAAG,CAAC,kBAAkB,EAAE,WAAW,EAAE,CAAC;IACvD,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,EAAE;QAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACzE,OAAO,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC;AACvC,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,IAAI,CAAC;QACH,MAAM,IAAI,GAAY,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;QACxF,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACpE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAG,GAAsB,OAAO,CAAC,GAAG;IAC7D,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3D,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC;QACrB,IAAI;QACJ,QAAQ,EAAE,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,cAAc,IAAI,aAAa,CAAC;QAC5D,OAAO,EAAE,GAAG,CAAC,kBAAkB,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC;QACtE,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,qBAAqB,CAAC,IAAI,IAAI;QACpD,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC;KAC5B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { TracepanelProcessor } from "./processor.js";
2
+ /**
3
+ * Each of console's methods becomes a property whose value records the call, then runs the method as it is now:
4
+ * something that replaces it later (Nuxt's dev server puts its own logger there) replaces what runs, not the record.
5
+ */
6
+ export declare function captureConsole(processor: TracepanelProcessor): void;
@@ -0,0 +1,44 @@
1
+ // console.* calls made during a request go to its trace, then on to the terminal as before.
2
+ import { format } from "node:util";
3
+ const LEVELS = {
4
+ debug: "debug",
5
+ log: "info",
6
+ info: "info",
7
+ warn: "warning",
8
+ error: "error",
9
+ };
10
+ const CAPTURED = Symbol.for("tracepanel.console");
11
+ /**
12
+ * Each of console's methods becomes a property whose value records the call, then runs the method as it is now:
13
+ * something that replaces it later (Nuxt's dev server puts its own logger there) replaces what runs, not the record.
14
+ */
15
+ export function captureConsole(processor) {
16
+ const target = console;
17
+ if (target[CAPTURED])
18
+ return;
19
+ target[CAPTURED] = true;
20
+ for (const [method, level] of Object.entries(LEVELS)) {
21
+ let current = target[method];
22
+ const recorded = (...args) => {
23
+ const error = args.find((arg) => arg instanceof Error);
24
+ processor.addLog({
25
+ time: Date.now() * 1000,
26
+ level,
27
+ logger: "console",
28
+ message: format(...args),
29
+ exception: error?.stack ?? null,
30
+ attrs: {},
31
+ });
32
+ current.apply(console, args);
33
+ };
34
+ Object.defineProperty(console, method, {
35
+ configurable: true,
36
+ enumerable: true,
37
+ get: () => recorded,
38
+ set: (replacement) => {
39
+ current = replacement;
40
+ },
41
+ });
42
+ }
43
+ }
44
+ //# sourceMappingURL=console.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console.js","sourceRoot":"","sources":["../src/console.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAE5F,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAInC,MAAM,MAAM,GAAiC;IAC3C,KAAK,EAAE,OAAO;IACd,GAAG,EAAE,MAAM;IACX,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,OAAO;CACf,CAAC;AAEF,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;AAElD;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,SAA8B;IAC3D,MAAM,MAAM,GAAG,OAAwE,CAAC;IACxF,IAAI,MAAM,CAAC,QAAQ,CAAC;QAAE,OAAO;IAC7B,MAAM,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;IACxB,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACrD,IAAI,OAAO,GAAG,MAAM,CAAC,MAAM,CAAiC,CAAC;QAC7D,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAe,EAAE,EAAE;YACtC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAgB,EAAE,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC;YACrE,SAAS,CAAC,MAAM,CAAC;gBACf,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI;gBACvB,KAAK;gBACL,MAAM,EAAE,SAAS;gBACjB,OAAO,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;gBACxB,SAAS,EAAE,KAAK,EAAE,KAAK,IAAI,IAAI;gBAC/B,KAAK,EAAE,EAAE;aACV,CAAC,CAAC;YACH,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC/B,CAAC,CAAC;QACF,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE;YACrC,YAAY,EAAE,IAAI;YAClB,UAAU,EAAE,IAAI;YAChB,GAAG,EAAE,GAAG,EAAE,CAAC,QAAQ;YACnB,GAAG,EAAE,CAAC,WAAyC,EAAE,EAAE;gBACjD,OAAO,GAAG,WAAW,CAAC;YACxB,CAAC;SACF,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { NextFunction, Request, Response } from "express";
2
+ export declare function tracepanel(): (req: Request, res: Response, next: NextFunction) => void;
@@ -0,0 +1,77 @@
1
+ // `app.use(tracepanel())`: every response gets what the badge reads (Tracepanel-Id, Server-Timing), and HTML pages
2
+ // get widget.min.js. The response is held until the handler ends it: a page or a JSON answer, not a stream.
3
+ import { runtime } from "./runtime.js";
4
+ import { badge, exposedHeaders, inject } from "./widget.js";
5
+ function toBuffer(chunk, encoding) {
6
+ if (Buffer.isBuffer(chunk))
7
+ return chunk;
8
+ if (chunk instanceof Uint8Array)
9
+ return Buffer.from(chunk);
10
+ return Buffer.from(String(chunk), encoding);
11
+ }
12
+ export function tracepanel() {
13
+ const tp = runtime();
14
+ return function tracepanelMiddleware(req, res, next) {
15
+ if (!tp) {
16
+ next();
17
+ return;
18
+ }
19
+ tp.processor.setFramework("Express");
20
+ const startedMs = performance.now();
21
+ const chunks = [];
22
+ const write = res.write.bind(res);
23
+ const end = res.end.bind(res);
24
+ let streaming = false;
25
+ const flushHeld = () => {
26
+ if (chunks.length > 0)
27
+ write(Buffer.concat(chunks.splice(0)));
28
+ };
29
+ res.write = ((chunk, encoding, callback) => {
30
+ if (streaming || String(res.getHeader("content-type")).includes("text/event-stream")) {
31
+ streaming = true;
32
+ flushHeld();
33
+ return write(chunk, encoding, callback);
34
+ }
35
+ chunks.push(toBuffer(chunk, typeof encoding === "string" ? encoding : undefined));
36
+ if (typeof encoding === "function")
37
+ encoding();
38
+ else if (typeof callback === "function")
39
+ callback();
40
+ return true;
41
+ });
42
+ res.end = ((chunk, encoding, callback) => {
43
+ res.write = write;
44
+ res.end = end;
45
+ if (typeof chunk === "function")
46
+ return end(chunk);
47
+ if (chunk !== undefined && chunk !== null && !streaming) {
48
+ chunks.push(toBuffer(chunk, typeof encoding === "string" ? encoding : undefined));
49
+ }
50
+ else if (streaming && chunk !== undefined && chunk !== null) {
51
+ return end(chunk, encoding, callback);
52
+ }
53
+ if (streaming || res.headersSent) {
54
+ flushHeld();
55
+ return end(callback);
56
+ }
57
+ let body = Buffer.concat(chunks);
58
+ const page = String(res.getHeader("content-type") ?? "").includes("text/html");
59
+ const framework = tp.processor.framework() ?? "Express";
60
+ const marks = badge(tp, { method: req.method, status: res.statusCode, startedMs, page, framework });
61
+ if (marks) {
62
+ for (const [name, value] of Object.entries(marks.headers))
63
+ res.setHeader(name, value);
64
+ const expose = exposedHeaders(res.getHeader("access-control-allow-origin"), res.getHeader("access-control-expose-headers"));
65
+ if (expose)
66
+ res.setHeader("Access-Control-Expose-Headers", expose);
67
+ if (marks.script)
68
+ body = Buffer.from(inject(body.toString("utf8"), marks.script));
69
+ }
70
+ if (!res.getHeader("transfer-encoding"))
71
+ res.setHeader("Content-Length", body.length);
72
+ return end(body, callback);
73
+ });
74
+ next();
75
+ };
76
+ }
77
+ //# sourceMappingURL=express.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.js","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ /** "Express" as "Express 5.3.0", or with `parts` of the version only ("Express 5.3"); as it is when unknown. */
2
+ export declare function withVersion(framework: string, parts?: number): string;
@@ -0,0 +1,39 @@
1
+ // A framework's version, from its package.json in the project's node_modules: "Next.js 16.4", as the Python SDK says
2
+ // "Django 6.1". Read once per framework.
3
+ import { readFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ const PACKAGES = {
6
+ Express: "express",
7
+ NestJS: "@nestjs/core",
8
+ "Next.js": "next",
9
+ Nuxt: "nuxt",
10
+ };
11
+ const versions = new Map();
12
+ function installed(name) {
13
+ let folder = process.cwd();
14
+ for (;;) {
15
+ try {
16
+ const version = JSON.parse(readFileSync(join(folder, "node_modules", name, "package.json"), "utf8")).version;
17
+ return typeof version === "string" ? version : null;
18
+ }
19
+ catch {
20
+ const parent = dirname(folder);
21
+ if (parent === folder)
22
+ return null;
23
+ folder = parent;
24
+ }
25
+ }
26
+ }
27
+ /** "Express" as "Express 5.3.0", or with `parts` of the version only ("Express 5.3"); as it is when unknown. */
28
+ export function withVersion(framework, parts) {
29
+ const name = PACKAGES[framework];
30
+ if (!name)
31
+ return framework;
32
+ if (!versions.has(name))
33
+ versions.set(name, installed(name));
34
+ const version = versions.get(name);
35
+ if (!version)
36
+ return framework;
37
+ return `${framework} ${parts ? version.split(".").slice(0, parts).join(".") : version}`;
38
+ }
39
+ //# sourceMappingURL=frameworks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frameworks.js","sourceRoot":"","sources":["../src/frameworks.ts"],"names":[],"mappings":"AAAA,qHAAqH;AACrH,yCAAyC;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,QAAQ,GAA2B;IACvC,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,cAAc;IACtB,SAAS,EAAE,MAAM;IACjB,IAAI,EAAE,MAAM;CACb,CAAC;AAEF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAC;AAElD,SAAS,SAAS,CAAC,IAAY;IAC7B,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC3B,SAAS,CAAC;QACR,IAAI,CAAC;YACH,MAAM,OAAO,GAAY,IAAI,CAAC,KAAK,CACjC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CACzE,CAAC,OAAO,CAAC;YACV,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;QACtD,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;YAC/B,IAAI,MAAM,KAAK,MAAM;gBAAE,OAAO,IAAI,CAAC;YACnC,MAAM,GAAG,MAAM,CAAC;QAClB,CAAC;IACH,CAAC;AACH,CAAC;AAED,gHAAgH;AAChH,MAAM,UAAU,WAAW,CAAC,SAAiB,EAAE,KAAc;IAC3D,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;IACjC,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7D,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAC/B,OAAO,GAAG,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;AAC1F,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Runtime } from "./runtime.js";
2
+ export declare function headersOnResponses(tracepanel: Runtime, framework: string): void;