@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
@@ -0,0 +1,42 @@
1
+ // For frameworks that answer from their own server (Next.js, Nuxt): every response of a traced request gets what the
2
+ // widget and the badge read, Tracepanel-Id, Tracepanel-Url and Server-Timing, as its headers go out.
3
+ import { ServerResponse } from "node:http";
4
+ import { exposedHeaders } from "./widget.js";
5
+ const PATCHED = Symbol.for("tracepanel.headers");
6
+ export function headersOnResponses(tracepanel, framework) {
7
+ const prototype = ServerResponse.prototype;
8
+ if (prototype[PATCHED])
9
+ return;
10
+ prototype[PATCHED] = true;
11
+ const writeHead = prototype.writeHead;
12
+ prototype.writeHead = function (...args) {
13
+ if (!this.headersSent) {
14
+ const marks = tracepanel.badge({
15
+ method: this.req?.method ?? "GET",
16
+ url: this.req?.url ?? "/",
17
+ status: typeof args[0] === "number" ? args[0] : this.statusCode,
18
+ startedMs: performance.now(),
19
+ page: false,
20
+ framework: tracepanel.processor.framework() ?? framework,
21
+ });
22
+ for (const [name, value] of Object.entries(marks?.headers ?? {})) {
23
+ if (!this.hasHeader(name))
24
+ this.setHeader(name, value);
25
+ }
26
+ // Headers passed to writeHead itself (an object) count too: CORS middleware often sets them there.
27
+ const given = [args[1], args[2]].find((arg) => arg !== null && typeof arg === "object" && !Array.isArray(arg));
28
+ const passed = (given ?? {});
29
+ const lower = Object.fromEntries(Object.entries(passed ?? {}).map(([name, value]) => [name.toLowerCase(), value]));
30
+ const expose = marks
31
+ ? exposedHeaders(this.getHeader("access-control-allow-origin") ?? lower["access-control-allow-origin"], this.getHeader("access-control-expose-headers") ?? lower["access-control-expose-headers"])
32
+ : undefined;
33
+ if (expose) {
34
+ this.setHeader("Access-Control-Expose-Headers", expose);
35
+ delete passed["access-control-expose-headers"];
36
+ delete passed["Access-Control-Expose-Headers"];
37
+ }
38
+ }
39
+ return writeHead.apply(this, args);
40
+ };
41
+ }
42
+ //# sourceMappingURL=headers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.js","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export type { TraceOptions } from "./api.js";
2
+ export { span, trace } from "./api.js";
3
+ export type { Config, Thresholds } from "./config.js";
4
+ export { loadConfig } from "./config.js";
5
+ export type { Trace } from "./model.js";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { span, trace } from "./api.js";
2
+ export { loadConfig } from "./config.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEvC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
package/dist/logs.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { InstrumentationBase, InstrumentationNodeModuleDefinition } from "@opentelemetry/instrumentation";
2
+ import type { TracepanelProcessor } from "./processor.js";
3
+ /** Nest's ConsoleLogger, which Logger and the app's own loggers use unless replaced. */
4
+ export declare class NestLogs extends InstrumentationBase {
5
+ private readonly processor;
6
+ constructor(processor: TracepanelProcessor);
7
+ protected init(): InstrumentationNodeModuleDefinition;
8
+ }
package/dist/logs.js ADDED
@@ -0,0 +1,54 @@
1
+ // Nest's Logger calls go to the request's trace, as console's do (console.ts): Nest writes to the terminal itself.
2
+ import { format } from "node:util";
3
+ import { InstrumentationBase, InstrumentationNodeModuleDefinition, isWrapped } from "@opentelemetry/instrumentation";
4
+ const NEST_LEVELS = {
5
+ verbose: "debug",
6
+ debug: "debug",
7
+ log: "info",
8
+ warn: "warning",
9
+ error: "error",
10
+ fatal: "critical",
11
+ };
12
+ /** `logger.log("created", "BooksService")`: the context is the last string argument, a stack the one before it. */
13
+ function nestRecord(level, message, params) {
14
+ const context = params.length > 0 && typeof params.at(-1) === "string" ? String(params.at(-1)) : "nest";
15
+ const stack = level === "error" && params.length > 1 && typeof params[0] === "string" ? params[0] : null;
16
+ return {
17
+ time: Date.now() * 1000,
18
+ level,
19
+ logger: context,
20
+ message: message instanceof Error ? message.message : format(message),
21
+ exception: message instanceof Error ? (message.stack ?? null) : stack,
22
+ attrs: {},
23
+ };
24
+ }
25
+ /** Nest's ConsoleLogger, which Logger and the app's own loggers use unless replaced. */
26
+ export class NestLogs extends InstrumentationBase {
27
+ processor;
28
+ constructor(processor) {
29
+ super("tracepanel-nest-logger", "1.0.0", {});
30
+ this.processor = processor;
31
+ }
32
+ init() {
33
+ return new InstrumentationNodeModuleDefinition("@nestjs/common", [">=8"], (exports) => {
34
+ const prototype = exports.ConsoleLogger
35
+ ?.prototype;
36
+ if (!prototype)
37
+ return exports;
38
+ for (const [method, level] of Object.entries(NEST_LEVELS)) {
39
+ const original = prototype[method];
40
+ if (typeof original !== "function" || isWrapped(original))
41
+ continue;
42
+ this._wrap(prototype, method, () => {
43
+ const processor = this.processor;
44
+ return function (message, ...params) {
45
+ processor.addLog(nestRecord(level, message, params));
46
+ return original.call(this, message, ...params);
47
+ };
48
+ });
49
+ }
50
+ return exports;
51
+ });
52
+ }
53
+ }
54
+ //# sourceMappingURL=logs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.js","sourceRoot":"","sources":["../src/logs.ts"],"names":[],"mappings":"AAAA,mHAAmH;AAEnH,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,mBAAmB,EAAE,mCAAmC,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAIrH,MAAM,WAAW,GAAiC;IAChD,OAAO,EAAE,OAAO;IAChB,KAAK,EAAE,OAAO;IACd,GAAG,EAAE,MAAM;IACX,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,UAAU;CAClB,CAAC;AAIF,mHAAmH;AACnH,SAAS,UAAU,CAAC,KAAmB,EAAE,OAAgB,EAAE,MAAiB;IAC1E,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACxG,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACzG,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI;QACvB,KAAK;QACL,MAAM,EAAE,OAAO;QACf,OAAO,EAAE,OAAO,YAAY,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;QACrE,SAAS,EAAE,OAAO,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK;QACrE,KAAK,EAAE,EAAE;KACV,CAAC;AACJ,CAAC;AAED,wFAAwF;AACxF,MAAM,OAAO,QAAS,SAAQ,mBAAmB;IAClB,SAAS;IAAtC,YAA6B,SAA8B;QACzD,KAAK,CAAC,wBAAwB,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;yBADlB,SAAS;IAEtC,CAAC;IAES,IAAI;QACZ,OAAO,IAAI,mCAAmC,CAAC,gBAAgB,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE;YACpF,MAAM,SAAS,GAAI,OAAyE,CAAC,aAAa;gBACxG,EAAE,SAAS,CAAC;YACd,IAAI,CAAC,SAAS;gBAAE,OAAO,OAAO,CAAC;YAC/B,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC1D,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;gBACnC,IAAI,OAAO,QAAQ,KAAK,UAAU,IAAI,SAAS,CAAC,QAAQ,CAAC;oBAAE,SAAS;gBACpE,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE;oBACjC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;oBACjC,OAAO,UAAyB,OAAgB,EAAE,GAAG,MAAiB;wBACpE,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;wBACrD,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,CAAC;oBACjD,CAAC,CAAC;gBACJ,CAAC,CAAC,CAAC;YACL,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,57 @@
1
+ export type Attrs = Record<string, unknown>;
2
+ export interface Span {
3
+ id: string;
4
+ parent_id: string | null;
5
+ kind: string;
6
+ name: string;
7
+ start: number;
8
+ duration: number;
9
+ status: "ok" | "error";
10
+ attrs: Attrs;
11
+ }
12
+ export interface Log {
13
+ span_id: string | null;
14
+ time: number;
15
+ level: "debug" | "info" | "warning" | "error" | "critical";
16
+ logger: string;
17
+ message: string;
18
+ exception: string | null;
19
+ attrs: Attrs;
20
+ }
21
+ export interface Frame {
22
+ filename: string;
23
+ module: string;
24
+ function: string;
25
+ lineno: number;
26
+ in_app: boolean;
27
+ context_start: number | null;
28
+ context: string[];
29
+ locals: Record<string, string>;
30
+ }
31
+ export interface TraceError {
32
+ span_id: string | null;
33
+ time: number;
34
+ type: string;
35
+ message: string;
36
+ handled: boolean;
37
+ frames: Frame[];
38
+ chain: unknown[];
39
+ }
40
+ export interface Trace {
41
+ id: string;
42
+ kind: "http" | "task" | "command";
43
+ name: string;
44
+ start: number;
45
+ duration: number;
46
+ status: "ok" | "error";
47
+ attrs: Attrs;
48
+ spans: Span[];
49
+ logs: Log[];
50
+ errors: TraceError[];
51
+ links: unknown[];
52
+ issues: unknown[];
53
+ }
54
+ /** A ULID: 48 bits of milliseconds, 80 random bits, 26 characters of Crockford base32. */
55
+ export declare function newId(now?: number): string;
56
+ /** Microseconds since the epoch, as the agent stores times. */
57
+ export declare function micros(ms: number): number;
package/dist/model.js ADDED
@@ -0,0 +1,18 @@
1
+ // The trace as every Tracepanel SDK sends it to the agent: schema/trace.schema.json in the tracepanel repository.
2
+ import { randomBytes } from "node:crypto";
3
+ const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
4
+ /** A ULID: 48 bits of milliseconds, 80 random bits, 26 characters of Crockford base32. */
5
+ export function newId(now = Date.now()) {
6
+ let value = (BigInt(now) << 80n) | BigInt(`0x${randomBytes(10).toString("hex")}`);
7
+ let out = "";
8
+ for (let i = 0; i < 26; i++) {
9
+ out = CROCKFORD[Number(value & 31n)] + out;
10
+ value >>= 5n;
11
+ }
12
+ return out;
13
+ }
14
+ /** Microseconds since the epoch, as the agent stores times. */
15
+ export function micros(ms) {
16
+ return Math.round(ms * 1000);
17
+ }
18
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,kHAAkH;AAElH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA6D1C,MAAM,SAAS,GAAG,kCAAkC,CAAC;AAErD,0FAA0F;AAC1F,MAAM,UAAU,KAAK,CAAC,GAAG,GAAW,IAAI,CAAC,GAAG,EAAE;IAC5C,IAAI,KAAK,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClF,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;QAC3C,KAAK,KAAK,EAAE,CAAC;IACf,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,MAAM,CAAC,EAAU;IAC/B,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type ReactElement } from "react";
2
+ /** The widget's script tag for this request's page, or nothing outside development. */
3
+ export declare function Tracepanel(): ReactElement | null;
@@ -0,0 +1,17 @@
1
+ // The badge on a Next.js app's pages: `<Tracepanel />` in the root layout. A server component that reads the active
2
+ // request from the process's Tracepanel; no OpenTelemetry in it, so the layout's bundle stays as it was.
3
+ import { createElement } from "react";
4
+ const KEY = Symbol.for("tracepanel.runtime");
5
+ /** The widget's script tag for this request's page, or nothing outside development. */
6
+ export function Tracepanel() {
7
+ const tracepanel = globalThis[KEY];
8
+ const marks = tracepanel?.badge({
9
+ method: "GET",
10
+ status: 200,
11
+ startedMs: performance.now(),
12
+ page: true,
13
+ framework: tracepanel.processor.framework() ?? "Next.js",
14
+ });
15
+ return marks?.scriptAttrs ? createElement("script", { ...marks.scriptAttrs, async: true }) : null;
16
+ }
17
+ //# sourceMappingURL=next-widget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next-widget.js","sourceRoot":"","sources":["../src/next-widget.ts"],"names":[],"mappings":"AAAA,oHAAoH;AACpH,yGAAyG;AAEzG,OAAO,EAAE,aAAa,EAAqB,MAAM,OAAO,CAAC;AAGzD,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;AAE7C,uFAAuF;AACvF,MAAM,UAAU,UAAU;IACxB,MAAM,UAAU,GAAI,UAAkC,CAAC,GAAG,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,UAAU,EAAE,KAAK,CAAC;QAC9B,MAAM,EAAE,KAAK;QACb,MAAM,EAAE,GAAG;QACX,SAAS,EAAE,WAAW,CAAC,GAAG,EAAE;QAC5B,IAAI,EAAE,IAAI;QACV,SAAS,EAAE,UAAU,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,SAAS;KACzD,CAAC,CAAC;IACH,OAAO,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,EAAE,EAAE,GAAG,KAAK,CAAC,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACpG,CAAC"}
package/dist/next.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ /** OpenTelemetry for the app, without Node's HTTP server spans: Next's root span is the request. */
2
+ export declare function register(): void;
package/dist/next.js ADDED
@@ -0,0 +1,16 @@
1
+ // Next.js starts its own server, before any `--import` could reach it, and makes the request's root span itself.
2
+ // Tracepanel goes in from the app's instrumentation hook:
3
+ //
4
+ // // instrumentation.ts
5
+ // export async function register() {
6
+ // if (process.env.NEXT_RUNTIME === "nodejs") (await import("@tracepanel/js/next")).register();
7
+ // }
8
+ import { headersOnResponses } from "./headers.js";
9
+ import { start } from "./setup.js";
10
+ /** OpenTelemetry for the app, without Node's HTTP server spans: Next's root span is the request. */
11
+ export function register() {
12
+ const tracepanel = start({ httpServer: false });
13
+ if (tracepanel)
14
+ headersOnResponses(tracepanel, "Next.js");
15
+ }
16
+ //# sourceMappingURL=next.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next.js","sourceRoot":"","sources":["../src/next.ts"],"names":[],"mappings":"AAAA,iHAAiH;AACjH,0DAA0D;AAC1D,EAAE;AACF,0BAA0B;AAC1B,uCAAuC;AACvC,mGAAmG;AACnG,MAAM;AAEN,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,oGAAoG;AACpG,MAAM,UAAU,QAAQ;IACtB,MAAM,UAAU,GAAG,KAAK,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;IAChD,IAAI,UAAU;QAAE,kBAAkB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,24 @@
1
+ interface Event {
2
+ method?: string;
3
+ path?: string;
4
+ context: {
5
+ matchedRoute?: {
6
+ path?: string;
7
+ };
8
+ };
9
+ }
10
+ interface NitroApp {
11
+ hooks: {
12
+ hook(name: "request" | "beforeResponse", handler: (event: Event) => void): void;
13
+ hook(name: "render:html", handler: (html: {
14
+ head: string[];
15
+ }, context: {
16
+ event: Event;
17
+ }) => void): void;
18
+ hook(name: "error", handler: (error: unknown, context: {
19
+ event?: Event;
20
+ }) => void): void;
21
+ };
22
+ }
23
+ export default function tracepanelPlugin(nitroApp: NitroApp): void;
24
+ export {};
@@ -0,0 +1,39 @@
1
+ // The Nitro plugin @tracepanel/js/nuxt adds in development. Nitro bundles it into the server: it reads the process's
2
+ // Tracepanel from globalThis and brings no OpenTelemetry of its own.
3
+ import { captureConsole } from "./console.js";
4
+ import { headersOnResponses } from "./headers.js";
5
+ const KEY = Symbol.for("tracepanel.runtime");
6
+ export default function tracepanelPlugin(nitroApp) {
7
+ const tracepanel = globalThis[KEY];
8
+ if (!tracepanel)
9
+ return;
10
+ headersOnResponses(tracepanel, "Nuxt");
11
+ // Nuxt's dev server replaced console with its own logger after --import wrapped it.
12
+ captureConsole(tracepanel.processor);
13
+ // h3 answers a thrown error with a 500 and records it nowhere: the request's trace gets it here.
14
+ // createError({ statusCode: 404 }) and the like are answers, not errors.
15
+ nitroApp.hooks.hook("error", (error) => {
16
+ const status = error.statusCode ?? 500;
17
+ if (status >= 500)
18
+ tracepanel.processor.addError(error);
19
+ });
20
+ nitroApp.hooks.hook("request", () => tracepanel.processor.setFramework("Nuxt"));
21
+ // The route is matched by then. The renderer's catch-all (/**) serves every page: its path names a page better.
22
+ nitroApp.hooks.hook("beforeResponse", (event) => {
23
+ const route = event.context.matchedRoute?.path;
24
+ if (route && !route.includes("**"))
25
+ tracepanel.processor.setRoute(route);
26
+ });
27
+ nitroApp.hooks.hook("render:html", (html, { event }) => {
28
+ const marks = tracepanel.badge({
29
+ method: event.method ?? "GET",
30
+ status: 200,
31
+ startedMs: performance.now(),
32
+ page: true,
33
+ framework: tracepanel.processor.framework() ?? "Nuxt",
34
+ });
35
+ if (marks?.script)
36
+ html.head.unshift(marks.script);
37
+ });
38
+ }
39
+ //# sourceMappingURL=nuxt-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nuxt-plugin.js","sourceRoot":"","sources":["../src/nuxt-plugin.ts"],"names":[],"mappings":"AAAA,qHAAqH;AACrH,qEAAqE;AAErE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAGlD,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;AAgB7C,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,QAAkB;IACzD,MAAM,UAAU,GAAI,UAAkC,CAAC,GAAG,CAAC,CAAC;IAC5D,IAAI,CAAC,UAAU;QAAE,OAAO;IACxB,kBAAkB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IACvC,oFAAoF;IACpF,cAAc,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IACrC,iGAAiG;IACjG,yEAAyE;IACzE,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;QACrC,MAAM,MAAM,GAAI,KAAiC,CAAC,UAAU,IAAI,GAAG,CAAC;QACpE,IAAI,MAAM,IAAI,GAAG;YAAE,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IACH,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;IAChF,gHAAgH;IAChH,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,EAAE;QAC9C,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC;QAC/C,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IACH,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;QACrD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC;YAC7B,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,KAAK;YAC7B,MAAM,EAAE,GAAG;YACX,SAAS,EAAE,WAAW,CAAC,GAAG,EAAE;YAC5B,IAAI,EAAE,IAAI;YACV,SAAS,EAAE,UAAU,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,MAAM;SACtD,CAAC,CAAC;QACH,IAAI,KAAK,EAAE,MAAM;YAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/nuxt.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ interface Nuxt {
2
+ options: {
3
+ dev: boolean;
4
+ nitro: {
5
+ plugins?: string[];
6
+ };
7
+ };
8
+ }
9
+ export default function tracepanelModule(_options: unknown, nuxt: Nuxt): void;
10
+ export {};
package/dist/nuxt.js ADDED
@@ -0,0 +1,16 @@
1
+ // `modules: ["@tracepanel/js/nuxt"]` in nuxt.config: a Nitro plugin that names each request by its route, puts the badge
2
+ // on server-rendered pages and the trace's id on every response. OpenTelemetry itself goes in before Nitro, with
3
+ // `NODE_OPTIONS=--import=@tracepanel/js/register nuxt dev`, so it can hook the database driver as Nitro loads it.
4
+ import { fileURLToPath } from "node:url";
5
+ export default function tracepanelModule(_options, nuxt) {
6
+ if (!nuxt.options.dev)
7
+ return;
8
+ nuxt.options.nitro.plugins = [
9
+ ...(nuxt.options.nitro.plugins ?? []),
10
+ fileURLToPath(new URL("./nuxt-plugin.js", import.meta.url)),
11
+ ];
12
+ }
13
+ Object.assign(tracepanelModule, {
14
+ getMeta: () => Promise.resolve({ name: "tracepanel", configKey: "tracepanel" }),
15
+ });
16
+ //# sourceMappingURL=nuxt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nuxt.js","sourceRoot":"","sources":["../src/nuxt.ts"],"names":[],"mappings":"AAAA,yHAAyH;AACzH,iHAAiH;AACjH,kHAAkH;AAElH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAMzC,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,QAAiB,EAAE,IAAU;IACpE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG;QAAE,OAAO;IAC9B,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG;QAC3B,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC;QACrC,aAAa,CAAC,IAAI,GAAG,CAAC,kBAAkB,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;KAC5D,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,CAAC,gBAAgB,EAAE;IAC9B,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;CAChF,CAAC,CAAC"}
@@ -0,0 +1,105 @@
1
+ import { type Context } from "@opentelemetry/api";
2
+ import type { ReadableSpan, Span as SdkSpan, SpanProcessor } from "@opentelemetry/sdk-trace-base";
3
+ import type { Config } from "./config.js";
4
+ import { type Log, micros, type Trace } from "./model.js";
5
+ /** Where a trace goes once its request ends. */
6
+ export interface Sink {
7
+ submit(trace: Trace): void;
8
+ }
9
+ /** One request's trace while its spans end. */
10
+ interface Building {
11
+ trace: Trace;
12
+ /** OTel span id: the Tracepanel span id it is, or its nearest kept ancestor's when it is left out. */
13
+ ids: Map<string, string | null>;
14
+ origins: Map<string, string | null>;
15
+ queries: number;
16
+ dbTime: number;
17
+ framework: string;
18
+ /** performance.now() as the request's root span started. */
19
+ startedMs: number;
20
+ /** OTel span id of the request's root span here: its parent, if any, is in another process. */
21
+ rootSpanId: string;
22
+ /** The route pattern a framework named (Nitro's /api/books/:id), for a root span that has none. */
23
+ route: string | null;
24
+ /** The request as its root span started: method and target, to find it from a response sent outside its context. */
25
+ request: string;
26
+ /** Whether a response's headers have taken this trace's id already. */
27
+ answered: boolean;
28
+ /** Tracepanel ids of Nest controller spans: the exceptions they record may be HTTP answers, not errors. */
29
+ nestViews: Set<string>;
30
+ /** OTel span id: the Tracepanel id of the span's parent when it started. */
31
+ parents: Map<string, string | null>;
32
+ /** Express's anonymous route handlers that a Nest controller's span stands in for: left out, children and all. */
33
+ replaced: Set<string>;
34
+ /** OTel span id: the Express route handlers' ones, so a Nest span under one can take its place. */
35
+ expressHandlers: Set<string>;
36
+ /**
37
+ * The app's line behind the latest span that had one. A driver often starts its query span later, from a callback
38
+ * (pg's pool runs the query once it has a connection), where the stack holds none of the app's code; the
39
+ * connection checkout before it started right in the app's call.
40
+ */
41
+ lastOrigin: string | null;
42
+ }
43
+ export declare class TracepanelProcessor implements SpanProcessor {
44
+ private readonly config;
45
+ private readonly sink;
46
+ /** The traces being built, by their root span's OTel id. */
47
+ private readonly open;
48
+ /**
49
+ * OTel span id → the trace it belongs to. Not the OTel trace id: a request the app makes to itself (fetch to its own
50
+ * API) carries the caller's trace id, and is a trace of its own all the same.
51
+ */
52
+ private readonly owner;
53
+ constructor(config: Config, sink: Sink);
54
+ onStart(span: SdkSpan, _parentContext: Context): void;
55
+ onEnd(span: ReadableSpan): void;
56
+ forceFlush(): Promise<void>;
57
+ shutdown(): Promise<void>;
58
+ /** The trace being built for the active request, with the current span's Tracepanel id. */
59
+ current(): {
60
+ building: Building;
61
+ spanId: string | null;
62
+ } | undefined;
63
+ /** What the widget shows before the request's span ends: its id, its queries so far and their time. */
64
+ timing(request?: {
65
+ method: string;
66
+ url: string;
67
+ }): Timing | undefined;
68
+ private dropAbandoned;
69
+ /** The trace is done here: its spans no longer lead to it. */
70
+ private close;
71
+ /**
72
+ * The open trace of a request whose response goes out outside its context (Next.js streams a page from another
73
+ * async context): the latest one for this method and URL that no response has answered yet.
74
+ */
75
+ private waiting;
76
+ /** An error a framework caught and answered with a 500 itself, without recording it on a span (Nitro). */
77
+ addError(error: unknown): void;
78
+ addLog(log: Omit<Log, "span_id">): void;
79
+ /** The framework the active request runs on, unless a more specific one (Nest on Express) already said so. */
80
+ setFramework(name: string): void;
81
+ /** The route pattern the framework matched, when the root span doesn't carry one (Nitro). */
82
+ setRoute(route: string): void;
83
+ /** The framework the active request ran on, as the panel shows it. */
84
+ framework(): string | undefined;
85
+ private start;
86
+ /** A span as Tracepanel draws it: the request, a route handler (view), a statement (sql), an outgoing call. */
87
+ private convert;
88
+ private sqlAttrs;
89
+ /** Exceptions the instrumentation recorded on a span: the request's errors, with their frames. */
90
+ private collectErrors;
91
+ /** The trace from its root span: name, timing, the request's attributes. */
92
+ private finish;
93
+ /** A command (trace()) or a task (a queue's job): named as it was started, its task.* attributes kept. */
94
+ private finishWork;
95
+ }
96
+ export interface Timing {
97
+ id: string;
98
+ queries: number;
99
+ dbMs: number;
100
+ /** performance.now() as the request started. */
101
+ startedMs: number;
102
+ }
103
+ /** `Node 26 · Express`: what the panel shows next to its logo. */
104
+ export declare function runtimeLabel(framework: string): string;
105
+ export { micros };