@interfere/next 9.0.1 → 10.0.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 +33 -5
- package/dist/config.d.mts +24 -5
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +38 -28
- package/dist/config.mjs.map +1 -1
- package/dist/instrument-client.d.mts +14 -3
- package/dist/instrument-client.d.mts.map +1 -1
- package/dist/instrument-client.mjs +7 -9
- package/dist/instrument-client.mjs.map +1 -1
- package/dist/instrumentation-client.d.mts +1 -0
- package/dist/instrumentation-client.mjs +22 -0
- package/dist/instrumentation-client.mjs.map +1 -0
- package/dist/instrumentation.d.mts +134 -0
- package/dist/instrumentation.d.mts.map +1 -0
- package/dist/instrumentation.edge.d.mts +35 -0
- package/dist/instrumentation.edge.d.mts.map +1 -0
- package/dist/instrumentation.edge.mjs +34 -0
- package/dist/instrumentation.edge.mjs.map +1 -0
- package/dist/instrumentation.mjs +165 -0
- package/dist/instrumentation.mjs.map +1 -0
- package/dist/internal/build/configure-build.d.mts +1 -2
- package/dist/internal/build/configure-build.d.mts.map +1 -1
- package/dist/internal/build/configure-build.mjs +10 -2
- package/dist/internal/build/configure-build.mjs.map +1 -1
- package/dist/internal/build/detect-bundler.d.mts +6 -0
- package/dist/internal/build/detect-bundler.d.mts.map +1 -0
- package/dist/internal/build/detect-bundler.mjs +9 -0
- package/dist/internal/build/detect-bundler.mjs.map +1 -0
- package/dist/internal/build/pipeline.d.mts +15 -1
- package/dist/internal/build/pipeline.d.mts.map +1 -1
- package/dist/internal/build/pipeline.mjs +28 -13
- package/dist/internal/build/pipeline.mjs.map +1 -1
- package/dist/internal/build/release/destinations/index.d.mts +14 -0
- package/dist/internal/build/release/destinations/index.d.mts.map +1 -0
- package/dist/internal/build/release/destinations/index.mjs +13 -0
- package/dist/internal/build/release/destinations/index.mjs.map +1 -0
- package/dist/internal/build/release/destinations/vercel.mjs.map +1 -1
- package/dist/internal/build/release/git.d.mts +13 -0
- package/dist/internal/build/release/git.d.mts.map +1 -1
- package/dist/internal/build/release/git.mjs +13 -2
- package/dist/internal/build/release/git.mjs.map +1 -1
- package/dist/internal/build/release/index.d.mts +2 -1
- package/dist/internal/build/release/index.d.mts.map +1 -1
- package/dist/internal/build/release/index.mjs +4 -5
- package/dist/internal/build/release/index.mjs.map +1 -1
- package/dist/internal/build/release/sources/github.mjs.map +1 -1
- package/dist/internal/build/release/sources/index.d.mts +21 -0
- package/dist/internal/build/release/sources/index.d.mts.map +1 -0
- package/dist/internal/build/release/sources/index.mjs +20 -0
- package/dist/internal/build/release/sources/index.mjs.map +1 -0
- package/dist/internal/build/source-maps/discover-turbopack.d.mts +32 -0
- package/dist/internal/build/source-maps/discover-turbopack.d.mts.map +1 -0
- package/dist/internal/build/source-maps/discover-turbopack.mjs +68 -0
- package/dist/internal/build/source-maps/discover-turbopack.mjs.map +1 -0
- package/dist/internal/build/source-maps/discover-webpack.d.mts +53 -0
- package/dist/internal/build/source-maps/discover-webpack.d.mts.map +1 -0
- package/dist/internal/build/source-maps/discover-webpack.mjs +112 -0
- package/dist/internal/build/source-maps/discover-webpack.mjs.map +1 -0
- package/dist/internal/build/source-maps/discover.d.mts +28 -10
- package/dist/internal/build/source-maps/discover.d.mts.map +1 -1
- package/dist/internal/build/source-maps/discover.mjs +22 -83
- package/dist/internal/build/source-maps/discover.mjs.map +1 -1
- package/dist/internal/build/source-maps/index.d.mts +2 -24
- package/dist/internal/build/source-maps/index.d.mts.map +1 -1
- package/dist/internal/build/source-maps/index.mjs +13 -23
- package/dist/internal/build/source-maps/index.mjs.map +1 -1
- package/dist/internal/build/source-maps/paths.d.mts +28 -0
- package/dist/internal/build/source-maps/paths.d.mts.map +1 -0
- package/dist/internal/build/source-maps/paths.mjs +49 -0
- package/dist/internal/build/source-maps/paths.mjs.map +1 -0
- package/dist/internal/build/source-maps/upload.d.mts +46 -0
- package/dist/internal/build/source-maps/upload.d.mts.map +1 -0
- package/dist/internal/build/source-maps/upload.mjs +134 -0
- package/dist/internal/build/source-maps/upload.mjs.map +1 -0
- package/dist/internal/build/value-injection-loader.mjs.map +1 -1
- package/dist/internal/env.d.mts +11 -2
- package/dist/internal/env.d.mts.map +1 -1
- package/dist/internal/env.mjs +12 -3
- package/dist/internal/env.mjs.map +1 -1
- package/dist/internal/logger.d.mts +9 -1
- package/dist/internal/logger.d.mts.map +1 -1
- package/dist/internal/logger.mjs +10 -2
- package/dist/internal/logger.mjs.map +1 -1
- package/dist/internal/release-slug.d.mts +25 -0
- package/dist/internal/release-slug.d.mts.map +1 -0
- package/dist/internal/release-slug.mjs +32 -0
- package/dist/internal/release-slug.mjs.map +1 -0
- package/dist/internal/route/handle-get.d.mts +14 -1
- package/dist/internal/route/handle-get.d.mts.map +1 -1
- package/dist/internal/route/handle-get.mjs +35 -14
- package/dist/internal/route/handle-get.mjs.map +1 -1
- package/dist/internal/route/handle-post.d.mts +11 -0
- package/dist/internal/route/handle-post.d.mts.map +1 -1
- package/dist/internal/route/handle-post.mjs +11 -50
- package/dist/internal/route/handle-post.mjs.map +1 -1
- package/dist/internal/route/proxy.d.mts +21 -1
- package/dist/internal/route/proxy.d.mts.map +1 -1
- package/dist/internal/route/proxy.mjs +61 -16
- package/dist/internal/route/proxy.mjs.map +1 -1
- package/dist/internal/server/capture.d.mts +2 -2
- package/dist/internal/server/capture.d.mts.map +1 -1
- package/dist/internal/server/capture.mjs +71 -37
- package/dist/internal/server/capture.mjs.map +1 -1
- package/dist/internal/server/console-bridge.d.mts +19 -0
- package/dist/internal/server/console-bridge.d.mts.map +1 -0
- package/dist/internal/server/console-bridge.mjs +112 -0
- package/dist/internal/server/console-bridge.mjs.map +1 -0
- package/dist/internal/server/id-generator.d.mts +38 -0
- package/dist/internal/server/id-generator.d.mts.map +1 -0
- package/dist/internal/server/id-generator.mjs +68 -0
- package/dist/internal/server/id-generator.mjs.map +1 -0
- package/dist/internal/server/instrumentation-options.d.mts +86 -0
- package/dist/internal/server/instrumentation-options.d.mts.map +1 -0
- package/dist/internal/server/instrumentation-options.mjs +1 -0
- package/dist/internal/server/remote-config.mjs +2 -2
- package/dist/internal/server/remote-config.mjs.map +1 -1
- package/dist/internal/server/trace-meta.d.mts +34 -0
- package/dist/internal/server/trace-meta.d.mts.map +1 -0
- package/dist/internal/server/trace-meta.mjs +41 -0
- package/dist/internal/server/trace-meta.mjs.map +1 -0
- package/dist/internal/server/traceparent.d.mts +16 -0
- package/dist/internal/server/traceparent.d.mts.map +1 -0
- package/dist/internal/server/traceparent.mjs +26 -0
- package/dist/internal/server/traceparent.mjs.map +1 -0
- package/dist/internal/server/types.d.mts +1 -7
- package/dist/internal/server/types.d.mts.map +1 -1
- package/dist/internal/setup-warnings.d.mts +17 -0
- package/dist/internal/setup-warnings.d.mts.map +1 -0
- package/dist/internal/setup-warnings.mjs +45 -0
- package/dist/internal/setup-warnings.mjs.map +1 -0
- package/dist/package.mjs +1 -1
- package/dist/provider.d.mts +23 -2
- package/dist/provider.d.mts.map +1 -0
- package/dist/provider.mjs +23 -1
- package/dist/provider.mjs.map +1 -0
- package/dist/route-handler.d.mts +7 -2
- package/dist/route-handler.d.mts.map +1 -1
- package/dist/route-handler.mjs +11 -9
- package/dist/route-handler.mjs.map +1 -1
- package/dist/server.d.mts +2 -2
- package/dist/server.mjs +2 -2
- package/package.json +73 -20
- package/dist/internal/route/sw-script.d.mts +0 -4
- package/dist/internal/route/sw-script.d.mts.map +0 -1
- package/dist/internal/route/sw-script.mjs +0 -38
- package/dist/internal/route/sw-script.mjs.map +0 -1
- package/dist/internal/server/dedupe.d.mts +0 -5
- package/dist/internal/server/dedupe.d.mts.map +0 -1
- package/dist/internal/server/dedupe.mjs +0 -11
- package/dist/internal/server/dedupe.mjs.map +0 -1
- package/dist/internal/server/envelope.d.mts +0 -14
- package/dist/internal/server/envelope.d.mts.map +0 -1
- package/dist/internal/server/envelope.mjs +0 -59
- package/dist/internal/server/envelope.mjs.map +0 -1
- package/dist/internal/server/normalize-request.d.mts +0 -7
- package/dist/internal/server/normalize-request.d.mts.map +0 -1
- package/dist/internal/server/normalize-request.mjs +0 -50
- package/dist/internal/server/normalize-request.mjs.map +0 -1
- package/dist/internal/server/runtime.d.mts +0 -14
- package/dist/internal/server/runtime.d.mts.map +0 -1
- package/dist/internal/server/runtime.mjs +0 -18
- package/dist/internal/server/runtime.mjs.map +0 -1
- package/dist/internal/server/transport.d.mts +0 -12
- package/dist/internal/server/transport.d.mts.map +0 -1
- package/dist/internal/server/transport.mjs +0 -17
- package/dist/internal/server/transport.mjs.map +0 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { context, trace } from "@opentelemetry/api";
|
|
2
|
+
import { logs } from "@opentelemetry/api-logs";
|
|
3
|
+
//#region src/internal/server/console-bridge.ts
|
|
4
|
+
const ATTR_EXCEPTION_TYPE = "exception.type";
|
|
5
|
+
const ATTR_EXCEPTION_MESSAGE = "exception.message";
|
|
6
|
+
const ATTR_EXCEPTION_STACKTRACE = "exception.stacktrace";
|
|
7
|
+
const CONSOLE_METHODS = [
|
|
8
|
+
"debug",
|
|
9
|
+
"log",
|
|
10
|
+
"info",
|
|
11
|
+
"warn",
|
|
12
|
+
"error"
|
|
13
|
+
];
|
|
14
|
+
const SEVERITY = {
|
|
15
|
+
debug: 5,
|
|
16
|
+
log: 9,
|
|
17
|
+
info: 9,
|
|
18
|
+
warn: 13,
|
|
19
|
+
error: 17
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Bound at first failure log; subsequent failures only re-log every Nth
|
|
23
|
+
* occurrence so a misconfigured logger backend doesn't itself spam the
|
|
24
|
+
* console with bridge errors at line rate.
|
|
25
|
+
*/
|
|
26
|
+
const BRIDGE_FAILURE_LOG_INTERVAL = 100;
|
|
27
|
+
function stringify(value) {
|
|
28
|
+
if (typeof value === "string") return value;
|
|
29
|
+
if (value instanceof Error) return value.stack ?? `${value.name}: ${value.message}`;
|
|
30
|
+
try {
|
|
31
|
+
return JSON.stringify(value);
|
|
32
|
+
} catch {
|
|
33
|
+
return String(value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function extractException(args) {
|
|
37
|
+
for (const arg of args) if (arg instanceof Error) return arg;
|
|
38
|
+
}
|
|
39
|
+
function buildExceptionAttributes(args) {
|
|
40
|
+
const exception = extractException(args);
|
|
41
|
+
if (!exception) return;
|
|
42
|
+
const attrs = {
|
|
43
|
+
[ATTR_EXCEPTION_TYPE]: exception.name,
|
|
44
|
+
[ATTR_EXCEPTION_MESSAGE]: exception.message
|
|
45
|
+
};
|
|
46
|
+
if (exception.stack) attrs[ATTR_EXCEPTION_STACKTRACE] = exception.stack;
|
|
47
|
+
return attrs;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Patches `console.{debug,log,info,warn,error}` to additionally emit OTel
|
|
51
|
+
* `LogRecord`s alongside the original output. Every log record carries
|
|
52
|
+
* the active span's context (so trace ↔ log correlation works in
|
|
53
|
+
* dashboards) and any `Error` arg gets unpacked into semconv exception
|
|
54
|
+
* attrs.
|
|
55
|
+
*
|
|
56
|
+
* Returns a disposer that restores the original `console.*` methods.
|
|
57
|
+
*
|
|
58
|
+
* Cycle protection: `emitting` guards against the bridge re-entering
|
|
59
|
+
* itself if a logger backend itself logs to console (which would
|
|
60
|
+
* recursively emit forever). Failures are bounded-rate logged via the
|
|
61
|
+
* original `console.error` so a broken backend doesn't drown the
|
|
62
|
+
* customer's logs in bridge-failure messages.
|
|
63
|
+
*/
|
|
64
|
+
function bridgeConsoleToOtel() {
|
|
65
|
+
const logger = logs.getLogger("@interfere/next/console");
|
|
66
|
+
let emitting = false;
|
|
67
|
+
let bridgeFailures = 0;
|
|
68
|
+
const original = {
|
|
69
|
+
debug: console.debug,
|
|
70
|
+
log: console.log,
|
|
71
|
+
info: console.info,
|
|
72
|
+
warn: console.warn,
|
|
73
|
+
error: console.error
|
|
74
|
+
};
|
|
75
|
+
function emitLog(orig, severity, method, body, activeContext, activeSpan, attributes) {
|
|
76
|
+
if (emitting) return;
|
|
77
|
+
emitting = true;
|
|
78
|
+
try {
|
|
79
|
+
logger.emit({
|
|
80
|
+
severityNumber: severity,
|
|
81
|
+
severityText: method.toUpperCase(),
|
|
82
|
+
body,
|
|
83
|
+
...activeSpan ? { context: activeContext } : {},
|
|
84
|
+
...attributes ? { attributes } : {}
|
|
85
|
+
});
|
|
86
|
+
} catch (error) {
|
|
87
|
+
bridgeFailures++;
|
|
88
|
+
if (bridgeFailures % BRIDGE_FAILURE_LOG_INTERVAL === 1) orig.call(console, `[interfere] console→OTel bridge emission failed (${bridgeFailures} total):`, error);
|
|
89
|
+
} finally {
|
|
90
|
+
emitting = false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for (const method of CONSOLE_METHODS) {
|
|
94
|
+
const orig = original[method];
|
|
95
|
+
const severity = SEVERITY[method];
|
|
96
|
+
console[method] = (...args) => {
|
|
97
|
+
orig.apply(console, args);
|
|
98
|
+
const body = args.map(stringify).join(" ");
|
|
99
|
+
const activeContext = context.active();
|
|
100
|
+
const activeSpan = trace.getSpan(activeContext);
|
|
101
|
+
const attributes = buildExceptionAttributes(args);
|
|
102
|
+
queueMicrotask(() => {
|
|
103
|
+
emitLog(orig, severity, method, body, activeContext, activeSpan, attributes);
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return () => {
|
|
108
|
+
for (const method of CONSOLE_METHODS) console[method] = original[method];
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
export { bridgeConsoleToOtel };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console-bridge.mjs","names":[],"sources":["../../../src/internal/server/console-bridge.ts"],"sourcesContent":["import { context, trace } from \"@opentelemetry/api\";\nimport { logs, type SeverityNumber } from \"@opentelemetry/api-logs\";\n\nconst ATTR_EXCEPTION_TYPE = \"exception.type\" as const;\nconst ATTR_EXCEPTION_MESSAGE = \"exception.message\" as const;\nconst ATTR_EXCEPTION_STACKTRACE = \"exception.stacktrace\" as const;\n\nconst CONSOLE_METHODS = [\"debug\", \"log\", \"info\", \"warn\", \"error\"] as const;\ntype ConsoleMethod = (typeof CONSOLE_METHODS)[number];\n\nconst SEVERITY: Record<ConsoleMethod, SeverityNumber> = {\n debug: 5,\n log: 9,\n info: 9,\n warn: 13,\n error: 17,\n};\n\n/**\n * Bound at first failure log; subsequent failures only re-log every Nth\n * occurrence so a misconfigured logger backend doesn't itself spam the\n * console with bridge errors at line rate.\n */\nconst BRIDGE_FAILURE_LOG_INTERVAL = 100;\n\nfunction stringify(value: unknown): string {\n if (typeof value === \"string\") {\n return value;\n }\n if (value instanceof Error) {\n return value.stack ?? `${value.name}: ${value.message}`;\n }\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\nfunction extractException(args: unknown[]): Error | undefined {\n for (const arg of args) {\n if (arg instanceof Error) {\n return arg;\n }\n }\n return;\n}\n\nfunction buildExceptionAttributes(\n args: unknown[]\n): Record<string, string> | undefined {\n const exception = extractException(args);\n if (!exception) {\n return;\n }\n const attrs: Record<string, string> = {\n [ATTR_EXCEPTION_TYPE]: exception.name,\n [ATTR_EXCEPTION_MESSAGE]: exception.message,\n };\n if (exception.stack) {\n attrs[ATTR_EXCEPTION_STACKTRACE] = exception.stack;\n }\n return attrs;\n}\n\n/**\n * Patches `console.{debug,log,info,warn,error}` to additionally emit OTel\n * `LogRecord`s alongside the original output. Every log record carries\n * the active span's context (so trace ↔ log correlation works in\n * dashboards) and any `Error` arg gets unpacked into semconv exception\n * attrs.\n *\n * Returns a disposer that restores the original `console.*` methods.\n *\n * Cycle protection: `emitting` guards against the bridge re-entering\n * itself if a logger backend itself logs to console (which would\n * recursively emit forever). Failures are bounded-rate logged via the\n * original `console.error` so a broken backend doesn't drown the\n * customer's logs in bridge-failure messages.\n */\nexport function bridgeConsoleToOtel(): () => void {\n const logger = logs.getLogger(\"@interfere/next/console\");\n let emitting = false;\n let bridgeFailures = 0;\n\n const original: Record<ConsoleMethod, (...args: unknown[]) => void> = {\n debug: console.debug,\n log: console.log,\n info: console.info,\n warn: console.warn,\n error: console.error,\n };\n\n function emitLog(\n orig: (...args: unknown[]) => void,\n severity: SeverityNumber,\n method: ConsoleMethod,\n body: string,\n activeContext: ReturnType<typeof context.active>,\n activeSpan: ReturnType<typeof trace.getSpan>,\n attributes: Record<string, string> | undefined\n ): void {\n if (emitting) {\n return;\n }\n emitting = true;\n try {\n logger.emit({\n severityNumber: severity,\n severityText: method.toUpperCase(),\n body,\n ...(activeSpan ? { context: activeContext } : {}),\n ...(attributes ? { attributes } : {}),\n });\n } catch (error) {\n bridgeFailures++;\n if (bridgeFailures % BRIDGE_FAILURE_LOG_INTERVAL === 1) {\n orig.call(\n console,\n `[interfere] console→OTel bridge emission failed (${bridgeFailures} total):`,\n error\n );\n }\n } finally {\n emitting = false;\n }\n }\n\n for (const method of CONSOLE_METHODS) {\n const orig = original[method];\n const severity = SEVERITY[method];\n console[method] = (...args: unknown[]) => {\n orig.apply(console, args);\n\n const body = args.map(stringify).join(\" \");\n const activeContext = context.active();\n const activeSpan = trace.getSpan(activeContext);\n const attributes = buildExceptionAttributes(args);\n\n // Defer emission via microtask so the synchronous return path\n // out of `console.X` doesn't include the LoggerProvider's batch\n // logic. Customer code's `console.error(...)` call should feel\n // synchronous; the OTel emission rides the next microtask.\n // `queueMicrotask` (not `setTimeout(0)`): macrotask-deferred\n // emissions risk being lost on hard process exit.\n queueMicrotask(() => {\n emitLog(\n orig,\n severity,\n method,\n body,\n activeContext,\n activeSpan,\n attributes\n );\n });\n };\n }\n\n return () => {\n for (const method of CONSOLE_METHODS) {\n console[method] = original[method];\n }\n };\n}\n"],"mappings":";;;AAGA,MAAM,sBAAsB;AAC5B,MAAM,yBAAyB;AAC/B,MAAM,4BAA4B;AAElC,MAAM,kBAAkB;CAAC;CAAS;CAAO;CAAQ;CAAQ;CAAQ;AAGjE,MAAM,WAAkD;CACtD,OAAO;CACP,KAAK;CACL,MAAM;CACN,MAAM;CACN,OAAO;CACR;;;;;;AAOD,MAAM,8BAA8B;AAEpC,SAAS,UAAU,OAAwB;CACzC,IAAI,OAAO,UAAU,UACnB,OAAO;CAET,IAAI,iBAAiB,OACnB,OAAO,MAAM,SAAS,GAAG,MAAM,KAAK,IAAI,MAAM;CAEhD,IAAI;EACF,OAAO,KAAK,UAAU,MAAM;SACtB;EACN,OAAO,OAAO,MAAM;;;AAIxB,SAAS,iBAAiB,MAAoC;CAC5D,KAAK,MAAM,OAAO,MAChB,IAAI,eAAe,OACjB,OAAO;;AAMb,SAAS,yBACP,MACoC;CACpC,MAAM,YAAY,iBAAiB,KAAK;CACxC,IAAI,CAAC,WACH;CAEF,MAAM,QAAgC;GACnC,sBAAsB,UAAU;GAChC,yBAAyB,UAAU;EACrC;CACD,IAAI,UAAU,OACZ,MAAM,6BAA6B,UAAU;CAE/C,OAAO;;;;;;;;;;;;;;;;;AAkBT,SAAgB,sBAAkC;CAChD,MAAM,SAAS,KAAK,UAAU,0BAA0B;CACxD,IAAI,WAAW;CACf,IAAI,iBAAiB;CAErB,MAAM,WAAgE;EACpE,OAAO,QAAQ;EACf,KAAK,QAAQ;EACb,MAAM,QAAQ;EACd,MAAM,QAAQ;EACd,OAAO,QAAQ;EAChB;CAED,SAAS,QACP,MACA,UACA,QACA,MACA,eACA,YACA,YACM;EACN,IAAI,UACF;EAEF,WAAW;EACX,IAAI;GACF,OAAO,KAAK;IACV,gBAAgB;IAChB,cAAc,OAAO,aAAa;IAClC;IACA,GAAI,aAAa,EAAE,SAAS,eAAe,GAAG,EAAE;IAChD,GAAI,aAAa,EAAE,YAAY,GAAG,EAAE;IACrC,CAAC;WACK,OAAO;GACd;GACA,IAAI,iBAAiB,gCAAgC,GACnD,KAAK,KACH,SACA,oDAAoD,eAAe,WACnE,MACD;YAEK;GACR,WAAW;;;CAIf,KAAK,MAAM,UAAU,iBAAiB;EACpC,MAAM,OAAO,SAAS;EACtB,MAAM,WAAW,SAAS;EAC1B,QAAQ,WAAW,GAAG,SAAoB;GACxC,KAAK,MAAM,SAAS,KAAK;GAEzB,MAAM,OAAO,KAAK,IAAI,UAAU,CAAC,KAAK,IAAI;GAC1C,MAAM,gBAAgB,QAAQ,QAAQ;GACtC,MAAM,aAAa,MAAM,QAAQ,cAAc;GAC/C,MAAM,aAAa,yBAAyB,KAAK;GAQjD,qBAAqB;IACnB,QACE,MACA,UACA,QACA,MACA,eACA,YACA,WACD;KACD;;;CAIN,aAAa;EACX,KAAK,MAAM,UAAU,iBACnB,QAAQ,UAAU,SAAS"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { IdGenerator } from "@opentelemetry/sdk-trace-base";
|
|
2
|
+
|
|
3
|
+
//#region src/internal/server/id-generator.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* IdGenerator hybrid that uses OTel's default `RandomIdGenerator` whenever
|
|
6
|
+
* available and falls back to a counter-based generator inside Next 16's
|
|
7
|
+
* prerender contexts.
|
|
8
|
+
*
|
|
9
|
+
* Why: Next 16's prerender extension throws synchronously on any
|
|
10
|
+
* `Math.random()` / `crypto.getRandomValues()` call inside a Server
|
|
11
|
+
* Component that hasn't first awaited Request data
|
|
12
|
+
* (`next-prerender-random` / `next-prerender-crypto`). OTel's default
|
|
13
|
+
* generator hits `Math.random()` on every span creation, so spans Next
|
|
14
|
+
* opens around prerendered route renders blow up the build.
|
|
15
|
+
*
|
|
16
|
+
* The hybrid path keeps full 128-bit trace IDs / 64-bit span IDs for the
|
|
17
|
+
* common case (SSR, runtime requests, fluid-compute invocations) and only
|
|
18
|
+
* degrades inside prerender — where the alternative is build failure.
|
|
19
|
+
*
|
|
20
|
+
* Inside prerender, IDs are minted from a per-process random prefix
|
|
21
|
+
* (seeded once at construction time, outside any prerender ALS frame) plus
|
|
22
|
+
* a monotonic counter. Counter widths are masked to keep IDs at their
|
|
23
|
+
* spec-mandated lengths even after wrap.
|
|
24
|
+
*/
|
|
25
|
+
declare class PrerenderSafeIdGenerator implements IdGenerator {
|
|
26
|
+
private readonly random;
|
|
27
|
+
private readonly tracePrefix;
|
|
28
|
+
private readonly spanPrefix;
|
|
29
|
+
private traceCounter;
|
|
30
|
+
private spanCounter;
|
|
31
|
+
constructor();
|
|
32
|
+
generateTraceId(): string;
|
|
33
|
+
generateSpanId(): string;
|
|
34
|
+
private fallbackTraceId;
|
|
35
|
+
private fallbackSpanId;
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { PrerenderSafeIdGenerator };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id-generator.d.mts","names":[],"sources":["../../../src/internal/server/id-generator.ts"],"mappings":";;;;;AAoCA;;;;;;;;;;;;;;;;;;;cAAa,wBAAA,YAAoC,WAAA;EAAA,iBAC9B,MAAA;EAAA,iBACA,WAAA;EAAA,iBACA,UAAA;EAAA,QACT,YAAA;EAAA,QACA,WAAA;;EAaR,eAAA,CAAA;EAQA,cAAA,CAAA;EAAA,QAQQ,eAAA;EAAA,QAQA,cAAA;AAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { RandomIdGenerator } from "@opentelemetry/sdk-trace-base";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
//#region src/internal/server/id-generator.ts
|
|
4
|
+
const TRACE_ID_HEX_LEN = 32;
|
|
5
|
+
const SPAN_ID_HEX_LEN = 16;
|
|
6
|
+
const TRACE_PREFIX_HEX_LEN = 24;
|
|
7
|
+
const SPAN_PREFIX_HEX_LEN = 12;
|
|
8
|
+
const TRACE_COUNTER_HEX_LEN = TRACE_ID_HEX_LEN - TRACE_PREFIX_HEX_LEN;
|
|
9
|
+
const SPAN_COUNTER_HEX_LEN = SPAN_ID_HEX_LEN - SPAN_PREFIX_HEX_LEN;
|
|
10
|
+
const TRACE_COUNTER_MODULUS = 2 ** (TRACE_COUNTER_HEX_LEN * 4);
|
|
11
|
+
const SPAN_COUNTER_MODULUS = 2 ** (SPAN_COUNTER_HEX_LEN * 4);
|
|
12
|
+
/**
|
|
13
|
+
* IdGenerator hybrid that uses OTel's default `RandomIdGenerator` whenever
|
|
14
|
+
* available and falls back to a counter-based generator inside Next 16's
|
|
15
|
+
* prerender contexts.
|
|
16
|
+
*
|
|
17
|
+
* Why: Next 16's prerender extension throws synchronously on any
|
|
18
|
+
* `Math.random()` / `crypto.getRandomValues()` call inside a Server
|
|
19
|
+
* Component that hasn't first awaited Request data
|
|
20
|
+
* (`next-prerender-random` / `next-prerender-crypto`). OTel's default
|
|
21
|
+
* generator hits `Math.random()` on every span creation, so spans Next
|
|
22
|
+
* opens around prerendered route renders blow up the build.
|
|
23
|
+
*
|
|
24
|
+
* The hybrid path keeps full 128-bit trace IDs / 64-bit span IDs for the
|
|
25
|
+
* common case (SSR, runtime requests, fluid-compute invocations) and only
|
|
26
|
+
* degrades inside prerender — where the alternative is build failure.
|
|
27
|
+
*
|
|
28
|
+
* Inside prerender, IDs are minted from a per-process random prefix
|
|
29
|
+
* (seeded once at construction time, outside any prerender ALS frame) plus
|
|
30
|
+
* a monotonic counter. Counter widths are masked to keep IDs at their
|
|
31
|
+
* spec-mandated lengths even after wrap.
|
|
32
|
+
*/
|
|
33
|
+
var PrerenderSafeIdGenerator = class {
|
|
34
|
+
random = new RandomIdGenerator();
|
|
35
|
+
tracePrefix;
|
|
36
|
+
spanPrefix;
|
|
37
|
+
traceCounter = 0;
|
|
38
|
+
spanCounter = 0;
|
|
39
|
+
constructor() {
|
|
40
|
+
const seed = randomBytes((TRACE_PREFIX_HEX_LEN + SPAN_PREFIX_HEX_LEN) / 2);
|
|
41
|
+
this.tracePrefix = seed.subarray(0, TRACE_PREFIX_HEX_LEN / 2).toString("hex");
|
|
42
|
+
this.spanPrefix = seed.subarray(TRACE_PREFIX_HEX_LEN / 2).toString("hex");
|
|
43
|
+
}
|
|
44
|
+
generateTraceId() {
|
|
45
|
+
try {
|
|
46
|
+
return this.random.generateTraceId();
|
|
47
|
+
} catch {
|
|
48
|
+
return this.fallbackTraceId();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
generateSpanId() {
|
|
52
|
+
try {
|
|
53
|
+
return this.random.generateSpanId();
|
|
54
|
+
} catch {
|
|
55
|
+
return this.fallbackSpanId();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
fallbackTraceId() {
|
|
59
|
+
this.traceCounter = (this.traceCounter + 1) % TRACE_COUNTER_MODULUS;
|
|
60
|
+
return this.tracePrefix + this.traceCounter.toString(16).padStart(TRACE_COUNTER_HEX_LEN, "0");
|
|
61
|
+
}
|
|
62
|
+
fallbackSpanId() {
|
|
63
|
+
this.spanCounter = (this.spanCounter + 1) % SPAN_COUNTER_MODULUS;
|
|
64
|
+
return this.spanPrefix + this.spanCounter.toString(16).padStart(SPAN_COUNTER_HEX_LEN, "0");
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
//#endregion
|
|
68
|
+
export { PrerenderSafeIdGenerator };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id-generator.mjs","names":[],"sources":["../../../src/internal/server/id-generator.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport {\n type IdGenerator,\n RandomIdGenerator,\n} from \"@opentelemetry/sdk-trace-base\";\n\nconst TRACE_ID_HEX_LEN = 32;\nconst SPAN_ID_HEX_LEN = 16;\nconst TRACE_PREFIX_HEX_LEN = 24;\nconst SPAN_PREFIX_HEX_LEN = 12;\nconst TRACE_COUNTER_HEX_LEN = TRACE_ID_HEX_LEN - TRACE_PREFIX_HEX_LEN;\nconst SPAN_COUNTER_HEX_LEN = SPAN_ID_HEX_LEN - SPAN_PREFIX_HEX_LEN;\nconst TRACE_COUNTER_MODULUS = 2 ** (TRACE_COUNTER_HEX_LEN * 4);\nconst SPAN_COUNTER_MODULUS = 2 ** (SPAN_COUNTER_HEX_LEN * 4);\n\n/**\n * IdGenerator hybrid that uses OTel's default `RandomIdGenerator` whenever\n * available and falls back to a counter-based generator inside Next 16's\n * prerender contexts.\n *\n * Why: Next 16's prerender extension throws synchronously on any\n * `Math.random()` / `crypto.getRandomValues()` call inside a Server\n * Component that hasn't first awaited Request data\n * (`next-prerender-random` / `next-prerender-crypto`). OTel's default\n * generator hits `Math.random()` on every span creation, so spans Next\n * opens around prerendered route renders blow up the build.\n *\n * The hybrid path keeps full 128-bit trace IDs / 64-bit span IDs for the\n * common case (SSR, runtime requests, fluid-compute invocations) and only\n * degrades inside prerender — where the alternative is build failure.\n *\n * Inside prerender, IDs are minted from a per-process random prefix\n * (seeded once at construction time, outside any prerender ALS frame) plus\n * a monotonic counter. Counter widths are masked to keep IDs at their\n * spec-mandated lengths even after wrap.\n */\nexport class PrerenderSafeIdGenerator implements IdGenerator {\n private readonly random = new RandomIdGenerator();\n private readonly tracePrefix: string;\n private readonly spanPrefix: string;\n private traceCounter = 0;\n private spanCounter = 0;\n\n constructor() {\n // Seeded once at `register()` time — module init runs outside any\n // prerender ALS frame, so `randomBytes` is permitted here even if\n // every later call from inside a render is not.\n const seed = randomBytes((TRACE_PREFIX_HEX_LEN + SPAN_PREFIX_HEX_LEN) / 2);\n this.tracePrefix = seed\n .subarray(0, TRACE_PREFIX_HEX_LEN / 2)\n .toString(\"hex\");\n this.spanPrefix = seed.subarray(TRACE_PREFIX_HEX_LEN / 2).toString(\"hex\");\n }\n\n generateTraceId(): string {\n try {\n return this.random.generateTraceId();\n } catch {\n return this.fallbackTraceId();\n }\n }\n\n generateSpanId(): string {\n try {\n return this.random.generateSpanId();\n } catch {\n return this.fallbackSpanId();\n }\n }\n\n private fallbackTraceId(): string {\n this.traceCounter = (this.traceCounter + 1) % TRACE_COUNTER_MODULUS;\n return (\n this.tracePrefix +\n this.traceCounter.toString(16).padStart(TRACE_COUNTER_HEX_LEN, \"0\")\n );\n }\n\n private fallbackSpanId(): string {\n this.spanCounter = (this.spanCounter + 1) % SPAN_COUNTER_MODULUS;\n return (\n this.spanPrefix +\n this.spanCounter.toString(16).padStart(SPAN_COUNTER_HEX_LEN, \"0\")\n );\n }\n}\n"],"mappings":";;;AAMA,MAAM,mBAAmB;AACzB,MAAM,kBAAkB;AACxB,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAC5B,MAAM,wBAAwB,mBAAmB;AACjD,MAAM,uBAAuB,kBAAkB;AAC/C,MAAM,wBAAwB,MAAM,wBAAwB;AAC5D,MAAM,uBAAuB,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;AAuB1D,IAAa,2BAAb,MAA6D;CAC3D,SAA0B,IAAI,mBAAmB;CACjD;CACA;CACA,eAAuB;CACvB,cAAsB;CAEtB,cAAc;EAIZ,MAAM,OAAO,aAAa,uBAAuB,uBAAuB,EAAE;EAC1E,KAAK,cAAc,KAChB,SAAS,GAAG,uBAAuB,EAAE,CACrC,SAAS,MAAM;EAClB,KAAK,aAAa,KAAK,SAAS,uBAAuB,EAAE,CAAC,SAAS,MAAM;;CAG3E,kBAA0B;EACxB,IAAI;GACF,OAAO,KAAK,OAAO,iBAAiB;UAC9B;GACN,OAAO,KAAK,iBAAiB;;;CAIjC,iBAAyB;EACvB,IAAI;GACF,OAAO,KAAK,OAAO,gBAAgB;UAC7B;GACN,OAAO,KAAK,gBAAgB;;;CAIhC,kBAAkC;EAChC,KAAK,gBAAgB,KAAK,eAAe,KAAK;EAC9C,OACE,KAAK,cACL,KAAK,aAAa,SAAS,GAAG,CAAC,SAAS,uBAAuB,IAAI;;CAIvE,iBAAiC;EAC/B,KAAK,eAAe,KAAK,cAAc,KAAK;EAC5C,OACE,KAAK,aACL,KAAK,YAAY,SAAS,GAAG,CAAC,SAAS,sBAAsB,IAAI"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { LogRecordProcessor } from "@opentelemetry/sdk-logs";
|
|
2
|
+
import { MetricReader } from "@opentelemetry/sdk-metrics";
|
|
3
|
+
import { SpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
4
|
+
|
|
5
|
+
//#region src/internal/server/instrumentation-options.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Per-instrumentation tuning for the server-side `register()`. Mirrors
|
|
8
|
+
* the browser-side `TracingOptions` — same names, same semantics —
|
|
9
|
+
* so a customer running both client + server with the same SDK has
|
|
10
|
+
* one mental model.
|
|
11
|
+
*
|
|
12
|
+
* The shape lives in its own module (rather than alongside
|
|
13
|
+
* `instrumentation.ts`) so the Node entry (`instrumentation.ts`) and the
|
|
14
|
+
* Edge no-op entry (`instrumentation.edge.ts`) can both pull the same
|
|
15
|
+
* type contract — the Edge entry never imports the Node-only OTel
|
|
16
|
+
* implementation, but its `register()` signature must stay byte-for-byte
|
|
17
|
+
* compatible so customer code compiles unchanged when bundled for either
|
|
18
|
+
* runtime.
|
|
19
|
+
*
|
|
20
|
+
* `LogRecordProcessor`, `MetricReader`, and `SpanProcessor` come from
|
|
21
|
+
* the OTel "base" packages (`@opentelemetry/sdk-logs`,
|
|
22
|
+
* `@opentelemetry/sdk-metrics`, `@opentelemetry/sdk-trace-base`),
|
|
23
|
+
* which are edge-safe at the import level. They're also `import type` here
|
|
24
|
+
* so they're erased before bundling — the Edge runtime bundle never sees
|
|
25
|
+
* them at all.
|
|
26
|
+
*/
|
|
27
|
+
interface ServerInstrumentationOptions {
|
|
28
|
+
/**
|
|
29
|
+
* @internal
|
|
30
|
+
*
|
|
31
|
+
* Extra log-record processors fanned into the LoggerProvider's
|
|
32
|
+
* processor list. Used by `@interfere/observability` for internal-only
|
|
33
|
+
* dual-write to BetterStack from `interfere/homepage` +
|
|
34
|
+
* `interfere/dashboard`. Customers don't get a fan-out hook on the
|
|
35
|
+
* SDK surface; this is a private bridge for our own dogfood apps.
|
|
36
|
+
*/
|
|
37
|
+
_internalAdditionalLogRecordProcessors?: LogRecordProcessor[];
|
|
38
|
+
/**
|
|
39
|
+
* @internal
|
|
40
|
+
*
|
|
41
|
+
* Extra metric readers fanned into the MeterProvider's reader list.
|
|
42
|
+
* See `_internalAdditionalLogRecordProcessors`. Used to keep
|
|
43
|
+
* `http.client.request.duration` (from `UndiciInstrumentation`) and
|
|
44
|
+
* any first-party `metrics.getMeter(...)` counters landing on the
|
|
45
|
+
* BetterStack-fronting OTel collector after the `@vercel/otel` →
|
|
46
|
+
* `@interfere/next` migration removed the default metric reader
|
|
47
|
+
* `registerOTel({ metricReader: "auto" })` used to install.
|
|
48
|
+
*/
|
|
49
|
+
_internalAdditionalMetricReaders?: MetricReader[];
|
|
50
|
+
/**
|
|
51
|
+
* @internal
|
|
52
|
+
*
|
|
53
|
+
* Extra span processors fanned into the NodeTracerProvider's processor
|
|
54
|
+
* list. See `_internalAdditionalLogRecordProcessors`.
|
|
55
|
+
*/
|
|
56
|
+
_internalAdditionalSpanProcessors?: SpanProcessor[];
|
|
57
|
+
/**
|
|
58
|
+
* `false` disables the `console.*` → OTel `LogRecord` bridge.
|
|
59
|
+
* Defaults to enabled. Customers running their own structured
|
|
60
|
+
* logger (Pino, Winston, etc.) that already emits OTel logs can
|
|
61
|
+
* opt out to avoid double-emit.
|
|
62
|
+
*/
|
|
63
|
+
consoleBridge?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* URL patterns the undici fetch instrumentation skips (no spans
|
|
66
|
+
* created for these). The collector OTLP endpoint is always
|
|
67
|
+
* skipped automatically — without this the SDK would trace its
|
|
68
|
+
* own export requests in an infinite loop.
|
|
69
|
+
*/
|
|
70
|
+
ignoreUrls?: (string | RegExp)[];
|
|
71
|
+
/**
|
|
72
|
+
* URL patterns the undici fetch instrumentation injects W3C
|
|
73
|
+
* `traceparent` + `baggage` headers on for cross-process trace
|
|
74
|
+
* correlation. Same-origin requests aren't a concept on the server
|
|
75
|
+
* the way they are in a browser, so the allowlist is the only
|
|
76
|
+
* propagation control.
|
|
77
|
+
*/
|
|
78
|
+
propagateContextUrls?: (string | RegExp)[];
|
|
79
|
+
/**
|
|
80
|
+
* Override the OTel `service.name` resource attribute. Defaults to
|
|
81
|
+
* `"interfere-sdk-next-server"`.
|
|
82
|
+
*/
|
|
83
|
+
serviceName?: string;
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { ServerInstrumentationOptions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrumentation-options.d.mts","names":[],"sources":["../../../src/internal/server/instrumentation-options.ts"],"mappings":";;;;;;;AAyBA;;;;;;;;;;;;;;;;;;;UAAiB,4BAAA;EAmDkB;;;;;;;;;EAzCjC,sCAAA,GAAyC,kBAAA;;;;;;;;;;;;EAYzC,gCAAA,GAAmC,YAAA;;;;;;;EAOnC,iCAAA,GAAoC,aAAA;;;;;;;EAOpC,aAAA;;;;;;;EAOA,UAAA,aAAuB,MAAA;;;;;;;;EAQvB,oBAAA,aAAiC,MAAA;;;;;EAKjC,WAAA;AAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isEnabledOnServer, readInterfereEnv } from "../env.mjs";
|
|
2
2
|
import { API_PATHS } from "@interfere/constants/api";
|
|
3
3
|
//#region src/internal/server/remote-config.ts
|
|
4
4
|
let cachedConfig = null;
|
|
5
5
|
async function fetchAndCacheRemoteConfig() {
|
|
6
|
-
if (!
|
|
6
|
+
if (!isEnabledOnServer()) return;
|
|
7
7
|
const env = readInterfereEnv();
|
|
8
8
|
if (env.apiKey === null) return;
|
|
9
9
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-config.mjs","names":[],"sources":["../../../src/internal/server/remote-config.ts"],"sourcesContent":["import { API_PATHS } from \"@interfere/constants/api\";\nimport type {\n RemoteConfig,\n RemotePluginConfig,\n} from \"@interfere/types/sdk/remote-config\";\n\nimport {
|
|
1
|
+
{"version":3,"file":"remote-config.mjs","names":[],"sources":["../../../src/internal/server/remote-config.ts"],"sourcesContent":["import { API_PATHS } from \"@interfere/constants/api\";\nimport type {\n RemoteConfig,\n RemotePluginConfig,\n} from \"@interfere/types/sdk/remote-config\";\n\nimport { isEnabledOnServer, readInterfereEnv } from \"../env.js\";\n\nlet cachedConfig: RemotePluginConfig | null = null;\n\nexport async function fetchAndCacheRemoteConfig(): Promise<void> {\n if (!isEnabledOnServer()) {\n return;\n }\n\n const env = readInterfereEnv();\n if (env.apiKey === null) {\n return;\n }\n\n try {\n const url = `${env.apiUrl}${API_PATHS.CONFIG}`;\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n \"content-type\": \"application/json\",\n \"x-api-key\": env.apiKey,\n },\n signal: AbortSignal.timeout(10_000),\n });\n\n if (!response.ok) {\n return;\n }\n\n const config = (await response.json()) as RemoteConfig;\n if (config?.plugins) {\n cachedConfig = config.plugins;\n }\n } catch {\n // Fail silently — all plugins remain enabled\n }\n}\n\nexport function isPluginEnabled(plugin: string): boolean {\n if (!cachedConfig) {\n return true;\n }\n return cachedConfig[plugin as keyof RemotePluginConfig] !== false;\n}\n"],"mappings":";;;AAQA,IAAI,eAA0C;AAE9C,eAAsB,4BAA2C;CAC/D,IAAI,CAAC,mBAAmB,EACtB;CAGF,MAAM,MAAM,kBAAkB;CAC9B,IAAI,IAAI,WAAW,MACjB;CAGF,IAAI;EACF,MAAM,MAAM,GAAG,IAAI,SAAS,UAAU;EACtC,MAAM,WAAW,MAAM,MAAM,KAAK;GAChC,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,aAAa,IAAI;IAClB;GACD,QAAQ,YAAY,QAAQ,IAAO;GACpC,CAAC;EAEF,IAAI,CAAC,SAAS,IACZ;EAGF,MAAM,SAAU,MAAM,SAAS,MAAM;EACrC,IAAI,QAAQ,SACV,eAAe,OAAO;SAElB;;AAKV,SAAgB,gBAAgB,QAAyB;CACvD,IAAI,CAAC,cACH,OAAO;CAET,OAAO,aAAa,YAAwC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
|
+
|
|
3
|
+
//#region src/internal/server/trace-meta.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Server Component that emits a `<meta name="traceparent">` tag so the
|
|
6
|
+
* client SDK can stitch every browser span onto the server-side trace.
|
|
7
|
+
*
|
|
8
|
+
* Customer usage — drop into the root layout's `<head>`:
|
|
9
|
+
*
|
|
10
|
+
* ```tsx
|
|
11
|
+
* import { TraceMeta } from "@interfere/next/server";
|
|
12
|
+
*
|
|
13
|
+
* export default function RootLayout({ children }) {
|
|
14
|
+
* return (
|
|
15
|
+
* <html>
|
|
16
|
+
* <head>
|
|
17
|
+
* <TraceMeta />
|
|
18
|
+
* </head>
|
|
19
|
+
* <body>{children}</body>
|
|
20
|
+
* </html>
|
|
21
|
+
* );
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* Renders nothing when no OTel span is active (dev without OTel
|
|
26
|
+
* registered, edge runtime where `@interfere/next/instrumentation`'s
|
|
27
|
+
* async `register()` hasn't completed before the layout renders, or
|
|
28
|
+
* unsampled traces). The client SDK's propagation reader handles a
|
|
29
|
+
* missing meta tag the same as no parent — every browser span starts a
|
|
30
|
+
* fresh root, which is at least internally consistent.
|
|
31
|
+
*/
|
|
32
|
+
declare function TraceMeta(): _$react_jsx_runtime0.JSX.Element | null;
|
|
33
|
+
//#endregion
|
|
34
|
+
export { TraceMeta };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace-meta.d.mts","names":[],"sources":["../../../src/internal/server/trace-meta.tsx"],"mappings":";;;;;;AA8BA;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,SAAA,CAAA,GAAS,oBAAA,CAAA,GAAA,CAAA,OAAA"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { activeTraceparent } from "./traceparent.mjs";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
//#region src/internal/server/trace-meta.tsx
|
|
4
|
+
/**
|
|
5
|
+
* Server Component that emits a `<meta name="traceparent">` tag so the
|
|
6
|
+
* client SDK can stitch every browser span onto the server-side trace.
|
|
7
|
+
*
|
|
8
|
+
* Customer usage — drop into the root layout's `<head>`:
|
|
9
|
+
*
|
|
10
|
+
* ```tsx
|
|
11
|
+
* import { TraceMeta } from "@interfere/next/server";
|
|
12
|
+
*
|
|
13
|
+
* export default function RootLayout({ children }) {
|
|
14
|
+
* return (
|
|
15
|
+
* <html>
|
|
16
|
+
* <head>
|
|
17
|
+
* <TraceMeta />
|
|
18
|
+
* </head>
|
|
19
|
+
* <body>{children}</body>
|
|
20
|
+
* </html>
|
|
21
|
+
* );
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* Renders nothing when no OTel span is active (dev without OTel
|
|
26
|
+
* registered, edge runtime where `@interfere/next/instrumentation`'s
|
|
27
|
+
* async `register()` hasn't completed before the layout renders, or
|
|
28
|
+
* unsampled traces). The client SDK's propagation reader handles a
|
|
29
|
+
* missing meta tag the same as no parent — every browser span starts a
|
|
30
|
+
* fresh root, which is at least internally consistent.
|
|
31
|
+
*/
|
|
32
|
+
function TraceMeta() {
|
|
33
|
+
const traceparent = activeTraceparent();
|
|
34
|
+
if (!traceparent) return null;
|
|
35
|
+
return /* @__PURE__ */ jsx("meta", {
|
|
36
|
+
content: traceparent,
|
|
37
|
+
name: "traceparent"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { TraceMeta };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace-meta.mjs","names":[],"sources":["../../../src/internal/server/trace-meta.tsx"],"sourcesContent":["import { activeTraceparent } from \"./traceparent.js\";\n\n/**\n * Server Component that emits a `<meta name=\"traceparent\">` tag so the\n * client SDK can stitch every browser span onto the server-side trace.\n *\n * Customer usage — drop into the root layout's `<head>`:\n *\n * ```tsx\n * import { TraceMeta } from \"@interfere/next/server\";\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html>\n * <head>\n * <TraceMeta />\n * </head>\n * <body>{children}</body>\n * </html>\n * );\n * }\n * ```\n *\n * Renders nothing when no OTel span is active (dev without OTel\n * registered, edge runtime where `@interfere/next/instrumentation`'s\n * async `register()` hasn't completed before the layout renders, or\n * unsampled traces). The client SDK's propagation reader handles a\n * missing meta tag the same as no parent — every browser span starts a\n * fresh root, which is at least internally consistent.\n */\nexport function TraceMeta() {\n const traceparent = activeTraceparent();\n if (!traceparent) {\n return null;\n }\n return <meta content={traceparent} name=\"traceparent\" />;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,YAAY;CAC1B,MAAM,cAAc,mBAAmB;CACvC,IAAI,CAAC,aACH,OAAO;CAET,OAAO,oBAAC,QAAD;EAAM,SAAS;EAAa,MAAK;EAAgB,CAAA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/internal/server/traceparent.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Build a W3C `traceparent` string from the currently-active OTel
|
|
4
|
+
* span, or `null` if no span is active or the trace is not sampled.
|
|
5
|
+
*
|
|
6
|
+
* Server-side counterpart to the client SDK's
|
|
7
|
+
* `internal/otel/propagation.ts`: both ends of the wire filter
|
|
8
|
+
* unsampled traces. The browser doesn't honor the `sampled` bit when
|
|
9
|
+
* creating child spans, so propagating an unsampled trace_id from the
|
|
10
|
+
* server would orphan every browser span under a trace with no
|
|
11
|
+
* recorded server segments. Returning null lets the browser fall back
|
|
12
|
+
* to its own root, which is at least internally consistent.
|
|
13
|
+
*/
|
|
14
|
+
declare function activeTraceparent(): string | null;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { activeTraceparent };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traceparent.d.mts","names":[],"sources":["../../../src/internal/server/traceparent.ts"],"mappings":";;AAiBA;;;;;;;;;;;iBAAgB,iBAAA,CAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { TraceFlags, trace } from "@opentelemetry/api";
|
|
2
|
+
//#region src/internal/server/traceparent.ts
|
|
3
|
+
const SAMPLED_BIT = TraceFlags.SAMPLED;
|
|
4
|
+
const W3C_TRACECONTEXT_VERSION = "00";
|
|
5
|
+
/**
|
|
6
|
+
* Build a W3C `traceparent` string from the currently-active OTel
|
|
7
|
+
* span, or `null` if no span is active or the trace is not sampled.
|
|
8
|
+
*
|
|
9
|
+
* Server-side counterpart to the client SDK's
|
|
10
|
+
* `internal/otel/propagation.ts`: both ends of the wire filter
|
|
11
|
+
* unsampled traces. The browser doesn't honor the `sampled` bit when
|
|
12
|
+
* creating child spans, so propagating an unsampled trace_id from the
|
|
13
|
+
* server would orphan every browser span under a trace with no
|
|
14
|
+
* recorded server segments. Returning null lets the browser fall back
|
|
15
|
+
* to its own root, which is at least internally consistent.
|
|
16
|
+
*/
|
|
17
|
+
function activeTraceparent() {
|
|
18
|
+
const span = trace.getActiveSpan();
|
|
19
|
+
if (!span) return null;
|
|
20
|
+
const ctx = span.spanContext();
|
|
21
|
+
if ((ctx.traceFlags & SAMPLED_BIT) !== SAMPLED_BIT) return null;
|
|
22
|
+
const flags = ctx.traceFlags.toString(16).padStart(2, "0");
|
|
23
|
+
return `${W3C_TRACECONTEXT_VERSION}-${ctx.traceId}-${ctx.spanId}-${flags}`;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { activeTraceparent };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traceparent.mjs","names":[],"sources":["../../../src/internal/server/traceparent.ts"],"sourcesContent":["import { TraceFlags, trace } from \"@opentelemetry/api\";\n\nconst SAMPLED_BIT = TraceFlags.SAMPLED;\nconst W3C_TRACECONTEXT_VERSION = \"00\";\n\n/**\n * Build a W3C `traceparent` string from the currently-active OTel\n * span, or `null` if no span is active or the trace is not sampled.\n *\n * Server-side counterpart to the client SDK's\n * `internal/otel/propagation.ts`: both ends of the wire filter\n * unsampled traces. The browser doesn't honor the `sampled` bit when\n * creating child spans, so propagating an unsampled trace_id from the\n * server would orphan every browser span under a trace with no\n * recorded server segments. Returning null lets the browser fall back\n * to its own root, which is at least internally consistent.\n */\nexport function activeTraceparent(): string | null {\n const span = trace.getActiveSpan();\n if (!span) {\n return null;\n }\n const ctx = span.spanContext();\n // biome-ignore lint/suspicious/noBitwiseOperators: W3C trace_flags is a bitmask; SAMPLED (0x01) must be tested independently of any future reserved flags.\n if ((ctx.traceFlags & SAMPLED_BIT) !== SAMPLED_BIT) {\n return null;\n }\n const flags = ctx.traceFlags.toString(16).padStart(2, \"0\");\n return `${W3C_TRACECONTEXT_VERSION}-${ctx.traceId}-${ctx.spanId}-${flags}`;\n}\n"],"mappings":";;AAEA,MAAM,cAAc,WAAW;AAC/B,MAAM,2BAA2B;;;;;;;;;;;;;AAcjC,SAAgB,oBAAmC;CACjD,MAAM,OAAO,MAAM,eAAe;CAClC,IAAI,CAAC,MACH,OAAO;CAET,MAAM,MAAM,KAAK,aAAa;CAE9B,KAAK,IAAI,aAAa,iBAAiB,aACrC,OAAO;CAET,MAAM,QAAQ,IAAI,WAAW,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;CAC1D,OAAO,GAAG,yBAAyB,GAAG,IAAI,QAAQ,GAAG,IAAI,OAAO,GAAG"}
|
|
@@ -6,12 +6,6 @@ type OnRequestErrorContext = Omit<NextjsContext, "errorDigest" | "requestMethod"
|
|
|
6
6
|
interface CaptureErrorContext {
|
|
7
7
|
readonly mechanism?: ErrorMechanism;
|
|
8
8
|
readonly nextjs?: Omit<NextjsContext, "runtime">;
|
|
9
|
-
readonly traceparent?: string;
|
|
10
|
-
}
|
|
11
|
-
interface NormalizedRequest {
|
|
12
|
-
readonly headers: Headers;
|
|
13
|
-
readonly method: string;
|
|
14
|
-
readonly path: string;
|
|
15
9
|
}
|
|
16
10
|
//#endregion
|
|
17
|
-
export { CaptureErrorContext,
|
|
11
|
+
export { CaptureErrorContext, OnRequestErrorContext };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/internal/server/types.ts"],"mappings":";;;;KAGY,qBAAA,GAAwB,IAAA,CAClC,aAAA;AAAA,UAIe,mBAAA;EAAA,SACN,SAAA,GAAY,cAAA;EAAA,SACZ,MAAA,GAAS,IAAA,CAAK,aAAA;
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/internal/server/types.ts"],"mappings":";;;;KAGY,qBAAA,GAAwB,IAAA,CAClC,aAAA;AAAA,UAIe,mBAAA;EAAA,SACN,SAAA,GAAY,cAAA;EAAA,SACZ,MAAA,GAAS,IAAA,CAAK,aAAA;AAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/internal/setup-warnings.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Best-effort warning when the customer set `INTERFERE_API_KEY` but never
|
|
4
|
+
* created `instrumentation.ts`. Server-side tracing then silently does
|
|
5
|
+
* nothing — that's surprising and there's no other place we get to tell
|
|
6
|
+
* them. Never blocks the build: some customers genuinely don't want
|
|
7
|
+
* server-side tracing.
|
|
8
|
+
*/
|
|
9
|
+
declare function warnIfServerInstrumentationMissing(projectDir: string): void;
|
|
10
|
+
/**
|
|
11
|
+
* Reads the customer's installed `next` package version. Returns `null` if
|
|
12
|
+
* Next isn't resolvable from `projectDir` (e.g. running outside a Next app),
|
|
13
|
+
* in which case callers should skip version-conditional behaviour.
|
|
14
|
+
*/
|
|
15
|
+
declare function readNextMajorVersion(projectDir: string): number | null;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { readNextMajorVersion, warnIfServerInstrumentationMissing };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-warnings.d.mts","names":[],"sources":["../../src/internal/setup-warnings.ts"],"mappings":";;AA8BA;;;;;AAeA;iBAfgB,kCAAA,CAAmC,UAAA;;;;;;iBAenC,oBAAA,CAAqB,UAAA"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { log } from "./logger.mjs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
//#region src/internal/setup-warnings.ts
|
|
6
|
+
const SERVER_INSTRUMENTATION_FILES = [
|
|
7
|
+
"instrumentation.ts",
|
|
8
|
+
"instrumentation.tsx",
|
|
9
|
+
"instrumentation.js",
|
|
10
|
+
"instrumentation.jsx",
|
|
11
|
+
"src/instrumentation.ts",
|
|
12
|
+
"src/instrumentation.tsx",
|
|
13
|
+
"src/instrumentation.js",
|
|
14
|
+
"src/instrumentation.jsx"
|
|
15
|
+
];
|
|
16
|
+
function hasServerInstrumentation(projectDir) {
|
|
17
|
+
return SERVER_INSTRUMENTATION_FILES.some((c) => existsSync(resolve(projectDir, c)));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Best-effort warning when the customer set `INTERFERE_API_KEY` but never
|
|
21
|
+
* created `instrumentation.ts`. Server-side tracing then silently does
|
|
22
|
+
* nothing — that's surprising and there's no other place we get to tell
|
|
23
|
+
* them. Never blocks the build: some customers genuinely don't want
|
|
24
|
+
* server-side tracing.
|
|
25
|
+
*/
|
|
26
|
+
function warnIfServerInstrumentationMissing(projectDir) {
|
|
27
|
+
if (hasServerInstrumentation(projectDir)) return;
|
|
28
|
+
log.warn("No instrumentation.ts found", ["Server-side traces will be skipped. To enable, create instrumentation.ts at the project root with:", " export { register } from '@interfere/next/instrumentation';"]);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Reads the customer's installed `next` package version. Returns `null` if
|
|
32
|
+
* Next isn't resolvable from `projectDir` (e.g. running outside a Next app),
|
|
33
|
+
* in which case callers should skip version-conditional behaviour.
|
|
34
|
+
*/
|
|
35
|
+
function readNextMajorVersion(projectDir) {
|
|
36
|
+
try {
|
|
37
|
+
const pkg = createRequire(`${projectDir}/_`)("next/package.json");
|
|
38
|
+
const major = Number.parseInt(pkg.version?.split(".")[0] ?? "", 10);
|
|
39
|
+
return Number.isFinite(major) ? major : null;
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { readNextMajorVersion, warnIfServerInstrumentationMissing };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-warnings.mjs","names":[],"sources":["../../src/internal/setup-warnings.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { resolve } from \"node:path\";\n\nimport { log } from \"./logger.js\";\n\nconst SERVER_INSTRUMENTATION_FILES = [\n \"instrumentation.ts\",\n \"instrumentation.tsx\",\n \"instrumentation.js\",\n \"instrumentation.jsx\",\n \"src/instrumentation.ts\",\n \"src/instrumentation.tsx\",\n \"src/instrumentation.js\",\n \"src/instrumentation.jsx\",\n] as const;\n\nfunction hasServerInstrumentation(projectDir: string): boolean {\n return SERVER_INSTRUMENTATION_FILES.some((c) =>\n existsSync(resolve(projectDir, c))\n );\n}\n\n/**\n * Best-effort warning when the customer set `INTERFERE_API_KEY` but never\n * created `instrumentation.ts`. Server-side tracing then silently does\n * nothing — that's surprising and there's no other place we get to tell\n * them. Never blocks the build: some customers genuinely don't want\n * server-side tracing.\n */\nexport function warnIfServerInstrumentationMissing(projectDir: string): void {\n if (hasServerInstrumentation(projectDir)) {\n return;\n }\n log.warn(\"No instrumentation.ts found\", [\n \"Server-side traces will be skipped. To enable, create instrumentation.ts at the project root with:\",\n \" export { register } from '@interfere/next/instrumentation';\",\n ]);\n}\n\n/**\n * Reads the customer's installed `next` package version. Returns `null` if\n * Next isn't resolvable from `projectDir` (e.g. running outside a Next app),\n * in which case callers should skip version-conditional behaviour.\n */\nexport function readNextMajorVersion(projectDir: string): number | null {\n try {\n const require = createRequire(`${projectDir}/_`);\n const pkg = require(\"next/package.json\") as { version?: string };\n const major = Number.parseInt(pkg.version?.split(\".\")[0] ?? \"\", 10);\n return Number.isFinite(major) ? major : null;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;AAMA,MAAM,+BAA+B;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,SAAS,yBAAyB,YAA6B;CAC7D,OAAO,6BAA6B,MAAM,MACxC,WAAW,QAAQ,YAAY,EAAE,CAAC,CACnC;;;;;;;;;AAUH,SAAgB,mCAAmC,YAA0B;CAC3E,IAAI,yBAAyB,WAAW,EACtC;CAEF,IAAI,KAAK,+BAA+B,CACtC,sGACA,gEACD,CAAC;;;;;;;AAQJ,SAAgB,qBAAqB,YAAmC;CACtE,IAAI;EAEF,MAAM,MADU,cAAc,GAAG,WAAW,IACzB,CAAC,oBAAoB;EACxC,MAAM,QAAQ,OAAO,SAAS,IAAI,SAAS,MAAM,IAAI,CAAC,MAAM,IAAI,GAAG;EACnE,OAAO,OAAO,SAAS,MAAM,GAAG,QAAQ;SAClC;EACN,OAAO"}
|
package/dist/package.mjs
CHANGED
package/dist/provider.d.mts
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { useInterfere, useSession } from "@interfere/react/provider";
|
|
2
|
+
import { PropsWithChildren, ReactNode } from "react";
|
|
3
|
+
import { ConsentCategory, ConsentState, ConsentState as ConsentState$1, GateableCategory } from "@interfere/types/sdk/plugins/manifest";
|
|
4
|
+
|
|
5
|
+
//#region src/provider.d.ts
|
|
6
|
+
interface InterfereProviderProps extends PropsWithChildren {
|
|
7
|
+
consent?: ConsentState$1 | undefined;
|
|
8
|
+
errorBoundary?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Next.js wrapper around `@interfere/react`'s provider that resolves the
|
|
12
|
+
* kernel from module scope (set by the matching `init()` call). Customer
|
|
13
|
+
* code never passes the kernel explicitly. Uses `getKernelOrNull()` so the
|
|
14
|
+
* provider is safe to render during SSR/SSG when `init()` hasn't run
|
|
15
|
+
* (`init()` is client-only); the core provider mounts with null-safe
|
|
16
|
+
* accessors and switches to the real kernel on client hydration.
|
|
17
|
+
*/
|
|
18
|
+
declare function InterfereProvider({
|
|
19
|
+
children,
|
|
20
|
+
consent,
|
|
21
|
+
errorBoundary
|
|
22
|
+
}: InterfereProviderProps): ReactNode;
|
|
23
|
+
//#endregion
|
|
3
24
|
export { type ConsentCategory, type ConsentState, type GateableCategory, InterfereProvider, useInterfere, useSession };
|