@interfere/next 10.0.0 → 10.0.1-canary.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/dist/config.d.mts +1 -0
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +1 -110
- package/dist/config.mjs.map +1 -1
- package/dist/instrument-client.d.mts +7 -10
- package/dist/instrument-client.d.mts.map +1 -1
- package/dist/instrument-client.mjs +1 -9
- package/dist/instrument-client.mjs.map +1 -1
- package/dist/instrumentation-client.mjs +1 -22
- package/dist/instrumentation-client.mjs.map +1 -1
- package/dist/instrumentation.d.mts +3 -3
- package/dist/instrumentation.d.mts.map +1 -1
- package/dist/instrumentation.edge.d.mts +1 -1
- package/dist/instrumentation.edge.d.mts.map +1 -1
- package/dist/instrumentation.edge.mjs +1 -34
- package/dist/instrumentation.edge.mjs.map +1 -1
- package/dist/instrumentation.mjs +1 -165
- package/dist/instrumentation.mjs.map +1 -1
- package/dist/internal/build/configure-build.d.mts +2 -0
- package/dist/internal/build/configure-build.d.mts.map +1 -1
- package/dist/internal/build/configure-build.mjs +1 -95
- package/dist/internal/build/configure-build.mjs.map +1 -1
- package/dist/internal/build/detect-bundler.d.mts +3 -2
- package/dist/internal/build/detect-bundler.d.mts.map +1 -1
- package/dist/internal/build/detect-bundler.mjs +1 -9
- package/dist/internal/build/detect-bundler.mjs.map +1 -1
- package/dist/internal/build/pipeline.d.mts +5 -7
- package/dist/internal/build/pipeline.d.mts.map +1 -1
- package/dist/internal/build/pipeline.mjs +1 -82
- package/dist/internal/build/pipeline.mjs.map +1 -1
- package/dist/internal/build/release/destinations/index.mjs +1 -13
- package/dist/internal/build/release/destinations/index.mjs.map +1 -1
- package/dist/internal/build/release/destinations/vercel.d.mts.map +1 -1
- package/dist/internal/build/release/destinations/vercel.mjs +1 -23
- package/dist/internal/build/release/destinations/vercel.mjs.map +1 -1
- package/dist/internal/build/release/git.d.mts.map +1 -1
- package/dist/internal/build/release/git.mjs +1 -32
- package/dist/internal/build/release/git.mjs.map +1 -1
- package/dist/internal/build/release/index.d.mts.map +1 -1
- package/dist/internal/build/release/index.mjs +1 -18
- package/dist/internal/build/release/index.mjs.map +1 -1
- package/dist/internal/build/release/sources/github.d.mts.map +1 -1
- package/dist/internal/build/release/sources/github.mjs +1 -13
- package/dist/internal/build/release/sources/github.mjs.map +1 -1
- package/dist/internal/build/release/sources/index.d.mts.map +1 -1
- package/dist/internal/build/release/sources/index.mjs +1 -20
- package/dist/internal/build/release/sources/index.mjs.map +1 -1
- package/dist/internal/build/source-maps/discover-turbopack.d.mts.map +1 -1
- package/dist/internal/build/source-maps/discover-turbopack.mjs +1 -68
- package/dist/internal/build/source-maps/discover-turbopack.mjs.map +1 -1
- package/dist/internal/build/source-maps/discover-webpack.d.mts.map +1 -1
- package/dist/internal/build/source-maps/discover-webpack.mjs +1 -112
- package/dist/internal/build/source-maps/discover-webpack.mjs.map +1 -1
- package/dist/internal/build/source-maps/discover.d.mts +4 -4
- package/dist/internal/build/source-maps/discover.d.mts.map +1 -1
- package/dist/internal/build/source-maps/discover.mjs +1 -26
- package/dist/internal/build/source-maps/discover.mjs.map +1 -1
- package/dist/internal/build/source-maps/index.d.mts.map +1 -1
- package/dist/internal/build/source-maps/index.mjs +1 -18
- package/dist/internal/build/source-maps/index.mjs.map +1 -1
- package/dist/internal/build/source-maps/paths.d.mts.map +1 -1
- package/dist/internal/build/source-maps/paths.mjs +1 -49
- package/dist/internal/build/source-maps/paths.mjs.map +1 -1
- package/dist/internal/build/source-maps/upload.d.mts.map +1 -1
- package/dist/internal/build/source-maps/upload.mjs +1 -134
- package/dist/internal/build/source-maps/upload.mjs.map +1 -1
- package/dist/internal/build/value-injection-loader.d.mts.map +1 -1
- package/dist/internal/build/value-injection-loader.mjs +2 -24
- package/dist/internal/build/value-injection-loader.mjs.map +1 -1
- package/dist/internal/env.d.mts +2 -1
- package/dist/internal/env.d.mts.map +1 -1
- package/dist/internal/env.mjs +1 -32
- package/dist/internal/env.mjs.map +1 -1
- package/dist/internal/logger.d.mts.map +1 -1
- package/dist/internal/logger.mjs +1 -68
- package/dist/internal/logger.mjs.map +1 -1
- package/dist/internal/release-slug.d.mts.map +1 -1
- package/dist/internal/release-slug.mjs +1 -32
- package/dist/internal/release-slug.mjs.map +1 -1
- package/dist/internal/route/handle-get.d.mts.map +1 -1
- package/dist/internal/route/handle-get.mjs +1 -43
- package/dist/internal/route/handle-get.mjs.map +1 -1
- package/dist/internal/route/handle-post.d.mts.map +1 -1
- package/dist/internal/route/handle-post.mjs +1 -31
- package/dist/internal/route/handle-post.mjs.map +1 -1
- package/dist/internal/route/proxy.d.mts +8 -6
- package/dist/internal/route/proxy.d.mts.map +1 -1
- package/dist/internal/route/proxy.mjs +1 -134
- package/dist/internal/route/proxy.mjs.map +1 -1
- package/dist/internal/server/capture.d.mts.map +1 -1
- package/dist/internal/server/capture.mjs +1 -89
- package/dist/internal/server/capture.mjs.map +1 -1
- package/dist/internal/server/console-bridge.d.mts.map +1 -1
- package/dist/internal/server/console-bridge.mjs +1 -112
- package/dist/internal/server/console-bridge.mjs.map +1 -1
- package/dist/internal/server/id-generator.d.mts.map +1 -1
- package/dist/internal/server/id-generator.mjs +1 -68
- package/dist/internal/server/id-generator.mjs.map +1 -1
- package/dist/internal/server/instrumentation-options.d.mts.map +1 -1
- package/dist/internal/server/instrumentation-options.mjs +1 -1
- package/dist/internal/server/remote-config.d.mts.map +1 -1
- package/dist/internal/server/remote-config.mjs +1 -29
- package/dist/internal/server/remote-config.mjs.map +1 -1
- package/dist/internal/server/trace-meta.d.mts +1 -3
- package/dist/internal/server/trace-meta.d.mts.map +1 -1
- package/dist/internal/server/trace-meta.mjs +1 -41
- package/dist/internal/server/trace-meta.mjs.map +1 -1
- package/dist/internal/server/traceparent.d.mts.map +1 -1
- package/dist/internal/server/traceparent.mjs +1 -26
- package/dist/internal/server/traceparent.mjs.map +1 -1
- package/dist/internal/server/types.d.mts.map +1 -1
- package/dist/internal/server/types.mjs +1 -1
- package/dist/internal/setup-warnings.d.mts +1 -1
- package/dist/internal/setup-warnings.d.mts.map +1 -1
- package/dist/internal/setup-warnings.mjs +1 -45
- package/dist/internal/setup-warnings.mjs.map +1 -1
- package/dist/internal/url.d.mts +4 -0
- package/dist/internal/url.d.mts.map +1 -0
- package/dist/internal/url.mjs +1 -0
- package/dist/internal/url.mjs.map +1 -0
- package/dist/internal/version.mjs +1 -5
- package/dist/internal/version.mjs.map +1 -1
- package/dist/package.mjs +1 -5
- package/dist/provider.d.mts.map +1 -1
- package/dist/provider.mjs +1 -25
- package/dist/provider.mjs.map +1 -1
- package/dist/route-handler.d.mts.map +1 -1
- package/dist/route-handler.mjs +1 -33
- package/dist/route-handler.mjs.map +1 -1
- package/dist/server.mjs +1 -3
- package/package.json +29 -30
|
@@ -10,29 +10,31 @@ import { InterfereEnv } from "../env.mjs";
|
|
|
10
10
|
*/
|
|
11
11
|
declare function forwardedHeaders(request: Request): Headers;
|
|
12
12
|
interface AuthenticatedEnv {
|
|
13
|
-
apiKey: string;
|
|
14
13
|
apiUrl: string;
|
|
14
|
+
publicKey: string | null;
|
|
15
15
|
release: InterfereEnv["release"];
|
|
16
16
|
}
|
|
17
|
-
declare function resolveAuthenticatedEnv(): AuthenticatedEnv
|
|
17
|
+
declare function resolveAuthenticatedEnv(): AuthenticatedEnv;
|
|
18
|
+
/** True when the proxy can attach a publishable key from env or the request. */
|
|
19
|
+
declare function hasPublicKeyCredential(request: Request, envPublicKey: string | null): boolean;
|
|
20
|
+
declare function notConfiguredResponse(): Response;
|
|
18
21
|
declare function extractSubPath(request: Request): string;
|
|
19
22
|
/**
|
|
20
23
|
* Preserve any query string verbatim when forwarding upstream.
|
|
21
24
|
*
|
|
22
|
-
* The browser SDK uses `?_pv=…` (and `?_pt=…` in direct
|
|
25
|
+
* The browser SDK uses `?_pv=…` and `?pk=…` (and `?_pt=…` in direct
|
|
23
26
|
* mode) as a `navigator.sendBeacon` fallback when `visibilitychange→
|
|
24
27
|
* hidden` aborts the keepalive fetch path. The collector's
|
|
25
|
-
* `otlpProducerGuard` and `
|
|
28
|
+
* `otlpProducerGuard` and `surfacePkAuth` accept those values verbatim
|
|
26
29
|
* — but only if the proxy actually forwards them. Passing
|
|
27
30
|
* `request.url`'s `search` through (`""` when absent, leading `?`
|
|
28
31
|
* when present) keeps the rewrite transparent.
|
|
29
32
|
*/
|
|
30
33
|
declare function extractSearch(request: Request): string;
|
|
31
|
-
declare function notConfiguredResponse(): Response;
|
|
32
34
|
declare function formatProxyError(error: unknown): {
|
|
33
35
|
message: string;
|
|
34
36
|
lines: string[];
|
|
35
37
|
};
|
|
36
38
|
declare function forwardToCollector(request: Request, env: AuthenticatedEnv, subPath: string): Promise<Response>;
|
|
37
39
|
//#endregion
|
|
38
|
-
export { AuthenticatedEnv, extractSearch, extractSubPath, formatProxyError, forwardToCollector, forwardedHeaders, notConfiguredResponse, resolveAuthenticatedEnv };
|
|
40
|
+
export { AuthenticatedEnv, extractSearch, extractSubPath, formatProxyError, forwardToCollector, forwardedHeaders, hasPublicKeyCredential, notConfiguredResponse, resolveAuthenticatedEnv };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.d.mts","names":[],"sources":["../../../src/internal/route/proxy.ts"],"mappings":";;;;;AAuDA;;;;;iBAAgB,gBAAA,CAAiB,OAAA,EAAS,OAAA,GAAU,
|
|
1
|
+
{"version":3,"file":"proxy.d.mts","names":[],"sources":["../../../src/internal/route/proxy.ts"],"mappings":";;;;;AAuDA;;;;;iBAAgB,gBAAA,CAAiB,OAAA,EAAS,OAAA,GAAU,OAAO;AAAA,UAW1C,gBAAA;EACf,MAAA;EACA,SAAA;EACA,OAAA,EAAS,YAAY;AAAA;AAAA,iBAGP,uBAAA,CAAA,GAA2B,gBAAgB;;iBAU3C,sBAAA,CACd,OAAA,EAAS,OAAO,EAChB,YAAA;AAAA,iBAQc,qBAAA,CAAA,GAAyB,QAAQ;AAAA,iBAajC,cAAA,CAAe,OAAgB,EAAP,OAAO;;;AApCxB;AAGvB;;;;AAA2D;AAU3D;;;iBAgDgB,aAAA,CAAc,OAAgB,EAAP,OAAO;AAAA,iBAI9B,gBAAA,CAAiB,KAAA;EAC/B,OAAA;EACA,KAAA;AAAA;AAAA,iBAqDoB,kBAAA,CACpB,OAAA,EAAS,OAAA,EACT,GAAA,EAAK,gBAAA,EACL,OAAA,WACC,OAAA,CAAQ,QAAA"}
|
|
@@ -1,134 +1 @@
|
|
|
1
|
-
import { readInterfereEnv }
|
|
2
|
-
import { log } from "../logger.mjs";
|
|
3
|
-
import { omitUndefined } from "@interfere/helpers/omit-undefined";
|
|
4
|
-
import { resolveRoutePrefix } from "@interfere/constants/route-prefix";
|
|
5
|
-
import { extractCountryHeader } from "@interfere/types/sdk/geo";
|
|
6
|
-
//#region src/internal/route/proxy.ts
|
|
7
|
-
const STRIPPED_AUTH_HEADERS = ["x-interfere-pub-token", "x-api-key"];
|
|
8
|
-
const STRIPPED_TRANSPORT_HEADERS = [
|
|
9
|
-
"host",
|
|
10
|
-
"content-length",
|
|
11
|
-
"connection",
|
|
12
|
-
"keep-alive",
|
|
13
|
-
"transfer-encoding",
|
|
14
|
-
"te",
|
|
15
|
-
"trailer",
|
|
16
|
-
"upgrade",
|
|
17
|
-
"proxy-authenticate",
|
|
18
|
-
"proxy-authorization"
|
|
19
|
-
];
|
|
20
|
-
/**
|
|
21
|
-
* Clone the inbound request's headers, removing entries that should never
|
|
22
|
-
* cross the proxy boundary. Returns a mutable `Headers` so callers can
|
|
23
|
-
* `.set()`/`.delete()` upstream-specific values without having to merge
|
|
24
|
-
* record literals (which lose case-insensitivity and create the precedence
|
|
25
|
-
* footguns that masked the 2026-05-01 incident in code review).
|
|
26
|
-
*/
|
|
27
|
-
function forwardedHeaders(request) {
|
|
28
|
-
const headers = new Headers(request.headers);
|
|
29
|
-
for (const name of STRIPPED_AUTH_HEADERS) headers.delete(name);
|
|
30
|
-
for (const name of STRIPPED_TRANSPORT_HEADERS) headers.delete(name);
|
|
31
|
-
return headers;
|
|
32
|
-
}
|
|
33
|
-
function resolveAuthenticatedEnv() {
|
|
34
|
-
const env = readInterfereEnv();
|
|
35
|
-
if (env.apiKey === null) return null;
|
|
36
|
-
return {
|
|
37
|
-
apiKey: env.apiKey,
|
|
38
|
-
apiUrl: env.apiUrl,
|
|
39
|
-
release: env.release
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
function extractSubPath(request) {
|
|
43
|
-
const prefix = resolveRoutePrefix();
|
|
44
|
-
const url = new URL(request.url);
|
|
45
|
-
const idx = url.pathname.indexOf(prefix);
|
|
46
|
-
if (idx === -1) return "/";
|
|
47
|
-
const remainder = url.pathname.slice(idx + prefix.length);
|
|
48
|
-
if (remainder === "") return "/";
|
|
49
|
-
return remainder.startsWith("/") ? remainder : `/${remainder}`;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Preserve any query string verbatim when forwarding upstream.
|
|
53
|
-
*
|
|
54
|
-
* The browser SDK uses `?_pv=…` (and `?_pt=…` in direct-ingestion
|
|
55
|
-
* mode) as a `navigator.sendBeacon` fallback when `visibilitychange→
|
|
56
|
-
* hidden` aborts the keepalive fetch path. The collector's
|
|
57
|
-
* `otlpProducerGuard` and `surfaceAuth` accept those values verbatim
|
|
58
|
-
* — but only if the proxy actually forwards them. Passing
|
|
59
|
-
* `request.url`'s `search` through (`""` when absent, leading `?`
|
|
60
|
-
* when present) keeps the rewrite transparent.
|
|
61
|
-
*/
|
|
62
|
-
function extractSearch(request) {
|
|
63
|
-
return new URL(request.url).search;
|
|
64
|
-
}
|
|
65
|
-
let notConfiguredWarned = false;
|
|
66
|
-
function notConfiguredResponse() {
|
|
67
|
-
if (!notConfiguredWarned) {
|
|
68
|
-
notConfiguredWarned = true;
|
|
69
|
-
log.warn("Not configured", ["INTERFERE_API_KEY is not set. The proxy route will return 503."]);
|
|
70
|
-
}
|
|
71
|
-
return Response.json({
|
|
72
|
-
code: "INTERFERE_NOT_CONFIGURED",
|
|
73
|
-
message: "INTERFERE_API_KEY is required."
|
|
74
|
-
}, { status: 503 });
|
|
75
|
-
}
|
|
76
|
-
function formatProxyError(error) {
|
|
77
|
-
if (!(error instanceof Error)) return {
|
|
78
|
-
message: String(error),
|
|
79
|
-
lines: [String(error)]
|
|
80
|
-
};
|
|
81
|
-
const lines = [`${error.name}: ${error.message}`];
|
|
82
|
-
if ("cause" in error && error.cause) {
|
|
83
|
-
const cause = error.cause instanceof Error ? error.cause.message : String(error.cause);
|
|
84
|
-
lines.push(`cause: ${cause}`);
|
|
85
|
-
}
|
|
86
|
-
if ("code" in error && typeof error.code === "string") lines.push(`code: ${error.code}`);
|
|
87
|
-
return {
|
|
88
|
-
message: `${error.name}: ${error.message}`,
|
|
89
|
-
lines
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
async function forwardToCollector(request, env, subPath) {
|
|
93
|
-
const url = `${env.apiUrl}${subPath}${extractSearch(request)}`;
|
|
94
|
-
const body = request.method !== "GET" && request.method !== "HEAD" ? await request.text() : void 0;
|
|
95
|
-
const headers = forwardedHeaders(request);
|
|
96
|
-
if (!headers.has("content-type")) headers.set("content-type", "application/json");
|
|
97
|
-
headers.set("x-api-key", env.apiKey);
|
|
98
|
-
const country = extractCountryHeader(request);
|
|
99
|
-
if (country !== null) headers.set("x-country-code", country);
|
|
100
|
-
const upstream = await fetch(url, {
|
|
101
|
-
...omitUndefined({ body }),
|
|
102
|
-
method: request.method,
|
|
103
|
-
headers,
|
|
104
|
-
signal: AbortSignal.timeout(1e4)
|
|
105
|
-
});
|
|
106
|
-
if (!upstream.ok) {
|
|
107
|
-
const text = await upstream.text().catch(() => "");
|
|
108
|
-
if (upstream.status >= 500) {
|
|
109
|
-
log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [text]);
|
|
110
|
-
return Response.json({
|
|
111
|
-
code: "INTERFERE_UPSTREAM_ERROR",
|
|
112
|
-
message: text
|
|
113
|
-
}, { status: upstream.status });
|
|
114
|
-
}
|
|
115
|
-
if (upstream.status >= 400) {
|
|
116
|
-
log.warn(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [text]);
|
|
117
|
-
return Response.json({
|
|
118
|
-
code: "INTERFERE_UPSTREAM_WARNING",
|
|
119
|
-
message: text
|
|
120
|
-
}, { status: upstream.status });
|
|
121
|
-
}
|
|
122
|
-
log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [text]);
|
|
123
|
-
return Response.json({
|
|
124
|
-
code: "INTERFERE_UPSTREAM_ERROR",
|
|
125
|
-
message: text
|
|
126
|
-
}, { status: upstream.status });
|
|
127
|
-
}
|
|
128
|
-
return new Response(upstream.body, {
|
|
129
|
-
status: upstream.status,
|
|
130
|
-
headers: { "content-type": upstream.headers.get("content-type") ?? "application/json" }
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
//#endregion
|
|
134
|
-
export { extractSearch, extractSubPath, formatProxyError, forwardToCollector, forwardedHeaders, notConfiguredResponse, resolveAuthenticatedEnv };
|
|
1
|
+
import{readInterfereEnv}from"../env.mjs";import{log}from"../logger.mjs";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{resolveRoutePrefix}from"@interfere/constants/route-prefix";import{extractCountryHeader}from"@interfere/types/sdk/geo";const STRIPPED_AUTH_HEADERS=[`authorization`,`x-api-key`,`x-interfere-api-key`],STRIPPED_TRANSPORT_HEADERS=[`host`,`content-length`,`connection`,`keep-alive`,`transfer-encoding`,`te`,`trailer`,`upgrade`,`proxy-authenticate`,`proxy-authorization`];function forwardedHeaders(request){let headers=new Headers(request.headers);for(let name of STRIPPED_AUTH_HEADERS)headers.delete(name);for(let name of STRIPPED_TRANSPORT_HEADERS)headers.delete(name);return headers}function resolveAuthenticatedEnv(){let env=readInterfereEnv();return{apiUrl:env.apiUrl,publicKey:env.publicKey,release:env.release}}function hasPublicKeyCredential(request,envPublicKey){return envPublicKey?!0:new URL(request.url).searchParams.has(`pk`)}function notConfiguredResponse(){return log.warn(`Not configured`,[`INTERFERE_PUBLIC_KEY is not set and the request has no ?pk= query param. The proxy route will return 503.`]),Response.json({code:`INTERFERE_NOT_CONFIGURED`,message:`INTERFERE_PUBLIC_KEY is required.`},{status:503})}function extractSubPath(request){let prefix=resolveRoutePrefix(),url=new URL(request.url),idx=url.pathname.indexOf(prefix);if(idx===-1)return`/`;let remainder=url.pathname.slice(idx+prefix.length);return remainder===``?`/`:remainder.startsWith(`/`)?remainder:`/${remainder}`}function extractSearch(request){return new URL(request.url).search}function formatProxyError(error){if(!(error instanceof Error))return{message:String(error),lines:[String(error)]};let lines=[`${error.name}: ${error.message}`];if(`cause`in error&&error.cause){let cause=error.cause instanceof Error?error.cause.message:String(error.cause);lines.push(`cause: ${cause}`)}return`code`in error&&typeof error.code==`string`&&lines.push(`code: ${error.code}`),{message:`${error.name}: ${error.message}`,lines}}function buildUpstreamUrl(apiUrl,subPath,request,publicKey){let incoming=new URL(request.url),upstream=new URL(`${apiUrl}${subPath}`);for(let[key,value]of incoming.searchParams)upstream.searchParams.append(key,value);return publicKey&&!upstream.searchParams.has(`pk`)&&upstream.searchParams.set(`pk`,publicKey),upstream.toString()}async function forwardToCollector(request,env,subPath){let url=buildUpstreamUrl(env.apiUrl,subPath,request,env.publicKey),body=request.method!==`GET`&&request.method!==`HEAD`?await request.text():void 0,headers=forwardedHeaders(request);headers.has(`content-type`)||headers.set(`content-type`,`application/json`);let country=extractCountryHeader(request);country!==null&&headers.set(`x-country-code`,country);let upstream=await fetch(url,{...omitUndefined({body}),method:request.method,headers,signal:AbortSignal.timeout(1e4)});if(!upstream.ok){let text=await upstream.text().catch(()=>``);return upstream.status>=500?(log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`,[text]),Response.json({code:`INTERFERE_UPSTREAM_ERROR`,message:text},{status:upstream.status})):upstream.status>=400?(log.warn(`Upstream ${upstream.status} for ${request.method} ${subPath}`,[text]),Response.json({code:`INTERFERE_UPSTREAM_WARNING`,message:text},{status:upstream.status})):(log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`,[text]),Response.json({code:`INTERFERE_UPSTREAM_ERROR`,message:text},{status:upstream.status}))}return new Response(upstream.body,{status:upstream.status,headers:{"content-type":upstream.headers.get(`content-type`)??`application/json`}})}export{extractSearch,extractSubPath,formatProxyError,forwardToCollector,forwardedHeaders,hasPublicKeyCredential,notConfiguredResponse,resolveAuthenticatedEnv};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.mjs","names":[],"sources":["../../../src/internal/route/proxy.ts"],"sourcesContent":["import { resolveRoutePrefix } from \"@interfere/constants/route-prefix\";\nimport { omitUndefined } from \"@interfere/helpers/omit-undefined\";\nimport { extractCountryHeader } from \"@interfere/types/sdk/geo\";\n\nimport { type InterfereEnv, readInterfereEnv } from \"../env.js\";\nimport { log } from \"../logger.js\";\n\n// Auth headers we strip on principle:\n// - `x-interfere-pub-token` is browser-side SDK auth that the proxy replaces\n// with `x-api-key`.\n// - `x-api-key` is always supplied from server env; we don't trust whatever\n// the caller might have sent.\nconst STRIPPED_AUTH_HEADERS = [\"x-interfere-pub-token\", \"x-api-key\"] as const;\n\n// Headers tied to the *inbound* HTTP transaction. These must not leak onto\n// the *outbound* `fetch` because the runtime computes them fresh for the new\n// body / connection, and forwarding them stale either silently corrupts the\n// request or hangs it until a timeout fires.\n//\n// Specifically `content-length`: `handleIngest` parses the incoming JSON,\n// injects build/release metadata, then re-`JSON.stringify`s the result. The\n// new body almost always has a different byte length to the original, so\n// forwarding the inbound `content-length` makes undici stall writing the\n// body until our 10s `AbortSignal.timeout` fires with\n// `Request body length does not match content-length header`. This was the\n// 2026-05-01 prod symptom (Vercel proxy 502s on `POST /v1/ingest` from\n// dashboard + homepage).\n//\n// `host` would route the upstream fetch back at the proxy hostname. fetch\n// sets it correctly from the URL automatically.\n//\n// The remainder are RFC 7230 §6.1 hop-by-hop headers — meaningful only on\n// the connection that received them. There's no canonical stdlib list for\n// any of this; every proxy in the Node ecosystem hand-rolls the same set\n// (`http-proxy`, Cloudflare Workers docs, Hono, undici's ProxyAgent, …).\nconst STRIPPED_TRANSPORT_HEADERS = [\n \"host\",\n \"content-length\",\n \"connection\",\n \"keep-alive\",\n \"transfer-encoding\",\n \"te\",\n \"trailer\",\n \"upgrade\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n] as const;\n\n/**\n * Clone the inbound request's headers, removing entries that should never\n * cross the proxy boundary. Returns a mutable `Headers` so callers can\n * `.set()`/`.delete()` upstream-specific values without having to merge\n * record literals (which lose case-insensitivity and create the precedence\n * footguns that masked the 2026-05-01 incident in code review).\n */\nexport function forwardedHeaders(request: Request): Headers {\n const headers = new Headers(request.headers);\n for (const name of STRIPPED_AUTH_HEADERS) {\n headers.delete(name);\n }\n for (const name of STRIPPED_TRANSPORT_HEADERS) {\n headers.delete(name);\n }\n return headers;\n}\n\nexport interface AuthenticatedEnv {\n apiKey: string;\n apiUrl: string;\n release: InterfereEnv[\"release\"];\n}\n\nexport function resolveAuthenticatedEnv(): AuthenticatedEnv | null {\n const env = readInterfereEnv();\n if (env.apiKey === null) {\n return null;\n }\n return { apiKey: env.apiKey, apiUrl: env.apiUrl, release: env.release };\n}\n\nexport function extractSubPath(request: Request): string {\n const prefix = resolveRoutePrefix();\n const url = new URL(request.url);\n const idx = url.pathname.indexOf(prefix);\n if (idx === -1) {\n return \"/\";\n }\n const remainder = url.pathname.slice(idx + prefix.length);\n if (remainder === \"\") {\n return \"/\";\n }\n return remainder.startsWith(\"/\") ? remainder : `/${remainder}`;\n}\n\n/**\n * Preserve any query string verbatim when forwarding upstream.\n *\n * The browser SDK uses `?_pv=…` (and `?_pt=…` in direct-ingestion\n * mode) as a `navigator.sendBeacon` fallback when `visibilitychange→\n * hidden` aborts the keepalive fetch path. The collector's\n * `otlpProducerGuard` and `surfaceAuth` accept those values verbatim\n * — but only if the proxy actually forwards them. Passing\n * `request.url`'s `search` through (`\"\"` when absent, leading `?`\n * when present) keeps the rewrite transparent.\n */\nexport function extractSearch(request: Request): string {\n return new URL(request.url).search;\n}\n\nlet notConfiguredWarned = false;\n\nexport function notConfiguredResponse(): Response {\n if (!notConfiguredWarned) {\n notConfiguredWarned = true;\n log.warn(\"Not configured\", [\n \"INTERFERE_API_KEY is not set. The proxy route will return 503.\",\n ]);\n }\n return Response.json(\n {\n code: \"INTERFERE_NOT_CONFIGURED\",\n message: \"INTERFERE_API_KEY is required.\",\n },\n { status: 503 }\n );\n}\n\nexport function formatProxyError(error: unknown): {\n message: string;\n lines: string[];\n} {\n if (!(error instanceof Error)) {\n return { message: String(error), lines: [String(error)] };\n }\n\n const lines = [`${error.name}: ${error.message}`];\n\n if (\"cause\" in error && error.cause) {\n const cause =\n error.cause instanceof Error ? error.cause.message : String(error.cause);\n lines.push(`cause: ${cause}`);\n }\n\n if (\n \"code\" in error &&\n typeof (error as NodeJS.ErrnoException).code === \"string\"\n ) {\n lines.push(`code: ${(error as NodeJS.ErrnoException).code}`);\n }\n\n return { message: `${error.name}: ${error.message}`, lines };\n}\n\nexport async function forwardToCollector(\n request: Request,\n env: AuthenticatedEnv,\n subPath: string\n): Promise<Response> {\n const url = `${env.apiUrl}${subPath}${extractSearch(request)}`;\n const hasBody = request.method !== \"GET\" && request.method !== \"HEAD\";\n const body = hasBody ? await request.text() : undefined;\n\n const headers = forwardedHeaders(request);\n // Default to JSON only when the inbound request didn't pin a content-type\n // — non-`/v1/ingest` paths relay the body bytes verbatim, so honour\n // whatever the caller said it was sending.\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n headers.set(\"x-api-key\", env.apiKey);\n const country = extractCountryHeader(request);\n if (country !== null) {\n headers.set(\"x-country-code\", country);\n }\n\n const upstream = await fetch(url, {\n ...omitUndefined({ body }),\n method: request.method,\n headers,\n signal: AbortSignal.timeout(10_000),\n });\n\n if (!upstream.ok) {\n const text = await upstream.text().catch(() => \"\");\n\n if (upstream.status >= 500) {\n log.error(\n `Upstream ${upstream.status} for ${request.method} ${subPath}`,\n [text]\n );\n return Response.json(\n { code: \"INTERFERE_UPSTREAM_ERROR\", message: text },\n { status: upstream.status }\n );\n }\n if (upstream.status >= 400) {\n log.warn(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [\n text,\n ]);\n return Response.json(\n { code: \"INTERFERE_UPSTREAM_WARNING\", message: text },\n { status: upstream.status }\n );\n }\n\n log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [\n text,\n ]);\n return Response.json(\n { code: \"INTERFERE_UPSTREAM_ERROR\", message: text },\n { status: upstream.status }\n );\n }\n\n return new Response(upstream.body, {\n status: upstream.status,\n headers: {\n \"content-type\":\n upstream.headers.get(\"content-type\") ?? \"application/json\",\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"proxy.mjs","names":[],"sources":["../../../src/internal/route/proxy.ts"],"sourcesContent":["import { resolveRoutePrefix } from \"@interfere/constants/route-prefix\";\nimport { omitUndefined } from \"@interfere/helpers/omit-undefined\";\nimport { extractCountryHeader } from \"@interfere/types/sdk/geo\";\n\nimport { type InterfereEnv, readInterfereEnv } from \"../env.js\";\nimport { log } from \"../logger.js\";\n\n// Never forward customer API keys from browser-originated ingestion requests.\nconst STRIPPED_AUTH_HEADERS = [\n \"authorization\",\n \"x-api-key\",\n \"x-interfere-api-key\",\n] as const;\n\n// Headers tied to the *inbound* HTTP transaction. These must not leak onto\n// the *outbound* `fetch` because the runtime computes them fresh for the new\n// body / connection, and forwarding them stale either silently corrupts the\n// request or hangs it until a timeout fires.\n//\n// Specifically `content-length`: `handleIngest` parses the incoming JSON,\n// injects build/release metadata, then re-`JSON.stringify`s the result. The\n// new body almost always has a different byte length to the original, so\n// forwarding the inbound `content-length` makes undici stall writing the\n// body until our 10s `AbortSignal.timeout` fires with\n// `Request body length does not match content-length header`. This was the\n// 2026-05-01 prod symptom (Vercel proxy 502s on `POST /v1/ingest` from\n// dashboard + homepage).\n//\n// `host` would route the upstream fetch back at the proxy hostname. fetch\n// sets it correctly from the URL automatically.\n//\n// The remainder are RFC 7230 §6.1 hop-by-hop headers — meaningful only on\n// the connection that received them. There's no canonical stdlib list for\n// any of this; every proxy in the Node ecosystem hand-rolls the same set\n// (`http-proxy`, Cloudflare Workers docs, Hono, undici's ProxyAgent, …).\nconst STRIPPED_TRANSPORT_HEADERS = [\n \"host\",\n \"content-length\",\n \"connection\",\n \"keep-alive\",\n \"transfer-encoding\",\n \"te\",\n \"trailer\",\n \"upgrade\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n] as const;\n\n/**\n * Clone the inbound request's headers, removing entries that should never\n * cross the proxy boundary. Returns a mutable `Headers` so callers can\n * `.set()`/`.delete()` upstream-specific values without having to merge\n * record literals (which lose case-insensitivity and create the precedence\n * footguns that masked the 2026-05-01 incident in code review).\n */\nexport function forwardedHeaders(request: Request): Headers {\n const headers = new Headers(request.headers);\n for (const name of STRIPPED_AUTH_HEADERS) {\n headers.delete(name);\n }\n for (const name of STRIPPED_TRANSPORT_HEADERS) {\n headers.delete(name);\n }\n return headers;\n}\n\nexport interface AuthenticatedEnv {\n apiUrl: string;\n publicKey: string | null;\n release: InterfereEnv[\"release\"];\n}\n\nexport function resolveAuthenticatedEnv(): AuthenticatedEnv {\n const env = readInterfereEnv();\n return {\n apiUrl: env.apiUrl,\n publicKey: env.publicKey,\n release: env.release,\n };\n}\n\n/** True when the proxy can attach a publishable key from env or the request. */\nexport function hasPublicKeyCredential(\n request: Request,\n envPublicKey: string | null\n): boolean {\n if (envPublicKey) {\n return true;\n }\n return new URL(request.url).searchParams.has(\"pk\");\n}\n\nexport function notConfiguredResponse(): Response {\n log.warn(\"Not configured\", [\n \"INTERFERE_PUBLIC_KEY is not set and the request has no ?pk= query param. The proxy route will return 503.\",\n ]);\n return Response.json(\n {\n code: \"INTERFERE_NOT_CONFIGURED\",\n message: \"INTERFERE_PUBLIC_KEY is required.\",\n },\n { status: 503 }\n );\n}\n\nexport function extractSubPath(request: Request): string {\n const prefix = resolveRoutePrefix();\n const url = new URL(request.url);\n const idx = url.pathname.indexOf(prefix);\n if (idx === -1) {\n return \"/\";\n }\n const remainder = url.pathname.slice(idx + prefix.length);\n if (remainder === \"\") {\n return \"/\";\n }\n return remainder.startsWith(\"/\") ? remainder : `/${remainder}`;\n}\n\n/**\n * Preserve any query string verbatim when forwarding upstream.\n *\n * The browser SDK uses `?_pv=…` and `?pk=…` (and `?_pt=…` in direct\n * mode) as a `navigator.sendBeacon` fallback when `visibilitychange→\n * hidden` aborts the keepalive fetch path. The collector's\n * `otlpProducerGuard` and `surfacePkAuth` accept those values verbatim\n * — but only if the proxy actually forwards them. Passing\n * `request.url`'s `search` through (`\"\"` when absent, leading `?`\n * when present) keeps the rewrite transparent.\n */\nexport function extractSearch(request: Request): string {\n return new URL(request.url).search;\n}\n\nexport function formatProxyError(error: unknown): {\n message: string;\n lines: string[];\n} {\n if (!(error instanceof Error)) {\n return { message: String(error), lines: [String(error)] };\n }\n\n const lines = [`${error.name}: ${error.message}`];\n\n if (\"cause\" in error && error.cause) {\n const cause =\n error.cause instanceof Error ? error.cause.message : String(error.cause);\n lines.push(`cause: ${cause}`);\n }\n\n if (\n \"code\" in error &&\n typeof (error as NodeJS.ErrnoException).code === \"string\"\n ) {\n lines.push(`code: ${(error as NodeJS.ErrnoException).code}`);\n }\n\n return { message: `${error.name}: ${error.message}`, lines };\n}\n\n/**\n * Append `?pk=` to the outbound URL from the server env. Proxy mode\n * means the browser never sees `INTERFERE_PUBLIC_KEY` (it's not\n * `NEXT_PUBLIC_`-exposed), so the proxy is the only place the\n * credential can attach to the request. Preserves any existing query\n * params (e.g. `_pv=` debug flag). If the inbound request already\n * carries `?pk=`, that value wins — a misconfigured server-env pk\n * shouldn't clobber an explicit one from the SDK.\n */\nfunction buildUpstreamUrl(\n apiUrl: string,\n subPath: string,\n request: Request,\n publicKey: string | null\n): string {\n const incoming = new URL(request.url);\n const upstream = new URL(`${apiUrl}${subPath}`);\n\n for (const [key, value] of incoming.searchParams) {\n upstream.searchParams.append(key, value);\n }\n\n if (publicKey && !upstream.searchParams.has(\"pk\")) {\n upstream.searchParams.set(\"pk\", publicKey);\n }\n\n return upstream.toString();\n}\n\nexport async function forwardToCollector(\n request: Request,\n env: AuthenticatedEnv,\n subPath: string\n): Promise<Response> {\n const url = buildUpstreamUrl(env.apiUrl, subPath, request, env.publicKey);\n const hasBody = request.method !== \"GET\" && request.method !== \"HEAD\";\n const body = hasBody ? await request.text() : undefined;\n\n const headers = forwardedHeaders(request);\n // Default to JSON only when the inbound request didn't pin a content-type\n // — non-`/v1/ingest` paths relay the body bytes verbatim, so honour\n // whatever the caller said it was sending.\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n const country = extractCountryHeader(request);\n if (country !== null) {\n headers.set(\"x-country-code\", country);\n }\n\n const upstream = await fetch(url, {\n ...omitUndefined({ body }),\n method: request.method,\n headers,\n signal: AbortSignal.timeout(10_000),\n });\n\n if (!upstream.ok) {\n const text = await upstream.text().catch(() => \"\");\n\n if (upstream.status >= 500) {\n log.error(\n `Upstream ${upstream.status} for ${request.method} ${subPath}`,\n [text]\n );\n return Response.json(\n { code: \"INTERFERE_UPSTREAM_ERROR\", message: text },\n { status: upstream.status }\n );\n }\n if (upstream.status >= 400) {\n log.warn(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [\n text,\n ]);\n return Response.json(\n { code: \"INTERFERE_UPSTREAM_WARNING\", message: text },\n { status: upstream.status }\n );\n }\n\n log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [\n text,\n ]);\n return Response.json(\n { code: \"INTERFERE_UPSTREAM_ERROR\", message: text },\n { status: upstream.status }\n );\n }\n\n return new Response(upstream.body, {\n status: upstream.status,\n headers: {\n \"content-type\":\n upstream.headers.get(\"content-type\") ?? \"application/json\",\n },\n });\n}\n"],"mappings":"kQAQA,MAAM,sBAAwB,CAC5B,gBACA,YACA,qBACF,EAuBM,2BAA6B,CACjC,OACA,iBACA,aACA,aACA,oBACA,KACA,UACA,UACA,qBACA,qBACF,EASA,SAAgB,iBAAiB,QAA2B,CAC1D,IAAM,QAAU,IAAI,QAAQ,QAAQ,OAAO,EAC3C,IAAK,IAAM,QAAQ,sBACjB,QAAQ,OAAO,IAAI,EAErB,IAAK,IAAM,QAAQ,2BACjB,QAAQ,OAAO,IAAI,EAErB,OAAO,OACT,CAQA,SAAgB,yBAA4C,CAC1D,IAAM,IAAM,iBAAiB,EAC7B,MAAO,CACL,OAAQ,IAAI,OACZ,UAAW,IAAI,UACf,QAAS,IAAI,OACf,CACF,CAGA,SAAgB,uBACd,QACA,aACS,CAIT,OAHI,aACK,GAEF,IAAI,IAAI,QAAQ,GAAG,EAAE,aAAa,IAAI,IAAI,CACnD,CAEA,SAAgB,uBAAkC,CAIhD,OAHA,IAAI,KAAK,iBAAkB,CACzB,2GACF,CAAC,EACM,SAAS,KACd,CACE,KAAM,2BACN,QAAS,mCACX,EACA,CAAE,OAAQ,GAAI,CAChB,CACF,CAEA,SAAgB,eAAe,QAA0B,CACvD,IAAM,OAAS,mBAAmB,EAC5B,IAAM,IAAI,IAAI,QAAQ,GAAG,EACzB,IAAM,IAAI,SAAS,QAAQ,MAAM,EACvC,GAAI,MAAQ,GACV,MAAO,IAET,IAAM,UAAY,IAAI,SAAS,MAAM,IAAM,OAAO,MAAM,EAIxD,OAHI,YAAc,GACT,IAEF,UAAU,WAAW,GAAG,EAAI,UAAY,IAAI,WACrD,CAaA,SAAgB,cAAc,QAA0B,CACtD,OAAO,IAAI,IAAI,QAAQ,GAAG,EAAE,MAC9B,CAEA,SAAgB,iBAAiB,MAG/B,CACA,GAAI,EAAE,iBAAiB,OACrB,MAAO,CAAE,QAAS,OAAO,KAAK,EAAG,MAAO,CAAC,OAAO,KAAK,CAAC,CAAE,EAG1D,IAAM,MAAQ,CAAC,GAAG,MAAM,KAAK,IAAI,MAAM,SAAS,EAEhD,GAAI,UAAW,OAAS,MAAM,MAAO,CACnC,IAAM,MACJ,MAAM,iBAAiB,MAAQ,MAAM,MAAM,QAAU,OAAO,MAAM,KAAK,EACzE,MAAM,KAAK,UAAU,OAAO,CAC9B,CASA,MANE,SAAU,OACV,OAAQ,MAAgC,MAAS,UAEjD,MAAM,KAAK,SAAU,MAAgC,MAAM,EAGtD,CAAE,QAAS,GAAG,MAAM,KAAK,IAAI,MAAM,UAAW,KAAM,CAC7D,CAWA,SAAS,iBACP,OACA,QACA,QACA,UACQ,CACR,IAAM,SAAW,IAAI,IAAI,QAAQ,GAAG,EAC9B,SAAW,IAAI,IAAI,GAAG,SAAS,SAAS,EAE9C,IAAK,GAAM,CAAC,IAAK,SAAU,SAAS,aAClC,SAAS,aAAa,OAAO,IAAK,KAAK,EAOzC,OAJI,WAAa,CAAC,SAAS,aAAa,IAAI,IAAI,GAC9C,SAAS,aAAa,IAAI,KAAM,SAAS,EAGpC,SAAS,SAAS,CAC3B,CAEA,eAAsB,mBACpB,QACA,IACA,QACmB,CACnB,IAAM,IAAM,iBAAiB,IAAI,OAAQ,QAAS,QAAS,IAAI,SAAS,EAElE,KADU,QAAQ,SAAW,OAAS,QAAQ,SAAW,OACxC,MAAM,QAAQ,KAAK,EAAI,IAAA,GAExC,QAAU,iBAAiB,OAAO,EAInC,QAAQ,IAAI,cAAc,GAC7B,QAAQ,IAAI,eAAgB,kBAAkB,EAEhD,IAAM,QAAU,qBAAqB,OAAO,EACxC,UAAY,MACd,QAAQ,IAAI,iBAAkB,OAAO,EAGvC,IAAM,SAAW,MAAM,MAAM,IAAK,CAChC,GAAG,cAAc,CAAE,IAAK,CAAC,EACzB,OAAQ,QAAQ,OAChB,QACA,OAAQ,YAAY,QAAQ,GAAM,CACpC,CAAC,EAED,GAAI,CAAC,SAAS,GAAI,CAChB,IAAM,KAAO,MAAM,SAAS,KAAK,EAAE,UAAY,EAAE,EAyBjD,OAvBI,SAAS,QAAU,KACrB,IAAI,MACF,YAAY,SAAS,OAAO,OAAO,QAAQ,OAAO,GAAG,UACrD,CAAC,IAAI,CACP,EACO,SAAS,KACd,CAAE,KAAM,2BAA4B,QAAS,IAAK,EAClD,CAAE,OAAQ,SAAS,MAAO,CAC5B,GAEE,SAAS,QAAU,KACrB,IAAI,KAAK,YAAY,SAAS,OAAO,OAAO,QAAQ,OAAO,GAAG,UAAW,CACvE,IACF,CAAC,EACM,SAAS,KACd,CAAE,KAAM,6BAA8B,QAAS,IAAK,EACpD,CAAE,OAAQ,SAAS,MAAO,CAC5B,IAGF,IAAI,MAAM,YAAY,SAAS,OAAO,OAAO,QAAQ,OAAO,GAAG,UAAW,CACxE,IACF,CAAC,EACM,SAAS,KACd,CAAE,KAAM,2BAA4B,QAAS,IAAK,EAClD,CAAE,OAAQ,SAAS,MAAO,CAC5B,EACF,CAEA,OAAO,IAAI,SAAS,SAAS,KAAM,CACjC,OAAQ,SAAS,OACjB,QAAS,CACP,eACE,SAAS,QAAQ,IAAI,cAAc,GAAK,kBAC5C,CACF,CAAC,CACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.d.mts","names":[],"sources":["../../../src/internal/server/capture.ts"],"mappings":";;;iBA+GgB,YAAA,CACd,KAAA,WACA,QAAA,YACA,OAAA,GAAU,
|
|
1
|
+
{"version":3,"file":"capture.d.mts","names":[],"sources":["../../../src/internal/server/capture.ts"],"mappings":";;;iBA+GgB,YAAA,CACd,KAAA,WACA,QAAA,YACA,OAAA,GAAU,mBAAmB;AAAA,iBAwBf,cAAA,CACd,KAAA,EAAO,KAAA;EAAU,MAAA;AAAA,GACjB,QAAA,WACA,OAAA,EAAS,qBAAqB"}
|
|
@@ -1,89 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { isPluginEnabled } from "./remote-config.mjs";
|
|
3
|
-
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
4
|
-
import { MECHANISM_TYPE, isNonErrorException, toException } from "@interfere/types/sdk/errors";
|
|
5
|
-
//#region src/internal/server/capture.ts
|
|
6
|
-
const TRACER_NAME = "@interfere/next/server";
|
|
7
|
-
const ON_REQUEST_ERROR_MECHANISM = {
|
|
8
|
-
type: MECHANISM_TYPE.nextjs.onRequestError,
|
|
9
|
-
handled: false,
|
|
10
|
-
synthetic: false
|
|
11
|
-
};
|
|
12
|
-
const DEFAULT_CAPTURE_ERROR_MECHANISM = {
|
|
13
|
-
type: MECHANISM_TYPE.nextjs.captureError,
|
|
14
|
-
handled: true
|
|
15
|
-
};
|
|
16
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
17
|
-
function buildAttrs(value, mechanism, context) {
|
|
18
|
-
const attrs = {
|
|
19
|
-
"interfere.exception.mechanism": mechanism.type,
|
|
20
|
-
"interfere.exception.handled": String(mechanism.handled)
|
|
21
|
-
};
|
|
22
|
-
const digest = context?.nextjs?.errorDigest;
|
|
23
|
-
if (digest) attrs["interfere.error.digest"] = digest;
|
|
24
|
-
if (context?.nextjs?.requestMethod) attrs["http.request.method"] = context.nextjs.requestMethod;
|
|
25
|
-
if (context?.nextjs?.requestPath) attrs["url.path"] = context.nextjs.requestPath;
|
|
26
|
-
if (isNonErrorException(value)) {
|
|
27
|
-
attrs["exception.type"] = value.type;
|
|
28
|
-
attrs["exception.message"] = value.value;
|
|
29
|
-
attrs["interfere.exception.kind"] = "non-error";
|
|
30
|
-
return attrs;
|
|
31
|
-
}
|
|
32
|
-
attrs["exception.type"] = value.name;
|
|
33
|
-
attrs["exception.message"] = value.message;
|
|
34
|
-
if (value.stack) attrs["exception.stacktrace"] = value.stack;
|
|
35
|
-
return attrs;
|
|
36
|
-
}
|
|
37
|
-
function recordException({ error, mechanism, context }) {
|
|
38
|
-
const value = toException(error);
|
|
39
|
-
const attrs = buildAttrs(value, mechanism, context);
|
|
40
|
-
const tracer = trace.getTracer(TRACER_NAME);
|
|
41
|
-
const active = trace.getActiveSpan();
|
|
42
|
-
if (active) {
|
|
43
|
-
active.addEvent("exception", attrs);
|
|
44
|
-
if (!mechanism.handled) active.setStatus({
|
|
45
|
-
code: SpanStatusCode.ERROR,
|
|
46
|
-
message: isNonErrorException(value) ? value.value : value.message
|
|
47
|
-
});
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
const span = tracer.startSpan("interfere.captureError", { kind: SpanKind.INTERNAL });
|
|
51
|
-
span.addEvent("exception", attrs);
|
|
52
|
-
if (!mechanism.handled) span.setStatus({
|
|
53
|
-
code: SpanStatusCode.ERROR,
|
|
54
|
-
message: isNonErrorException(value) ? value.value : value.message
|
|
55
|
-
});
|
|
56
|
-
span.end();
|
|
57
|
-
}
|
|
58
|
-
function captureError(error, _request, context) {
|
|
59
|
-
if (!isEnabledOnServer()) return;
|
|
60
|
-
if (!isPluginEnabled("errors")) return;
|
|
61
|
-
if (error instanceof Error) {
|
|
62
|
-
if (seen.has(error)) return;
|
|
63
|
-
seen.add(error);
|
|
64
|
-
}
|
|
65
|
-
recordException({
|
|
66
|
-
error,
|
|
67
|
-
mechanism: context?.mechanism ?? DEFAULT_CAPTURE_ERROR_MECHANISM,
|
|
68
|
-
context
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
function onRequestError(error, _request, context) {
|
|
72
|
-
if (seen.has(error)) return;
|
|
73
|
-
seen.add(error);
|
|
74
|
-
if (!isEnabledOnServer()) return;
|
|
75
|
-
if (!isPluginEnabled("errors")) return;
|
|
76
|
-
recordException({
|
|
77
|
-
error,
|
|
78
|
-
mechanism: ON_REQUEST_ERROR_MECHANISM,
|
|
79
|
-
context: {
|
|
80
|
-
mechanism: ON_REQUEST_ERROR_MECHANISM,
|
|
81
|
-
nextjs: {
|
|
82
|
-
...context,
|
|
83
|
-
...error.digest ? { errorDigest: error.digest } : {}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
//#endregion
|
|
89
|
-
export { captureError, onRequestError };
|
|
1
|
+
import{isEnabledOnServer}from"../env.mjs";import{isPluginEnabled}from"./remote-config.mjs";import{SpanKind,SpanStatusCode,trace}from"@opentelemetry/api";import{MECHANISM_TYPE,isNonErrorException,toException}from"@interfere/types/sdk/errors";const ON_REQUEST_ERROR_MECHANISM={type:MECHANISM_TYPE.nextjs.onRequestError,handled:!1,synthetic:!1},DEFAULT_CAPTURE_ERROR_MECHANISM={type:MECHANISM_TYPE.nextjs.captureError,handled:!0},seen=new WeakSet;function buildAttrs(value,mechanism,context){let attrs={"interfere.exception.mechanism":mechanism.type,"interfere.exception.handled":String(mechanism.handled)},digest=context?.nextjs?.errorDigest;return digest&&(attrs[`interfere.error.digest`]=digest),context?.nextjs?.requestMethod&&(attrs[`http.request.method`]=context.nextjs.requestMethod),context?.nextjs?.requestPath&&(attrs[`url.path`]=context.nextjs.requestPath),isNonErrorException(value)?(attrs[`exception.type`]=value.type,attrs[`exception.message`]=value.value,attrs[`interfere.exception.kind`]=`non-error`,attrs):(attrs[`exception.type`]=value.name,attrs[`exception.message`]=value.message,value.stack&&(attrs[`exception.stacktrace`]=value.stack),attrs)}function recordException({error,mechanism,context}){let value=toException(error),attrs=buildAttrs(value,mechanism,context),tracer=trace.getTracer(`@interfere/next/server`),active=trace.getActiveSpan();if(active){active.addEvent(`exception`,attrs),mechanism.handled||active.setStatus({code:SpanStatusCode.ERROR,message:isNonErrorException(value)?value.value:value.message});return}let span=tracer.startSpan(`interfere.captureError`,{kind:SpanKind.INTERNAL});span.addEvent(`exception`,attrs),mechanism.handled||span.setStatus({code:SpanStatusCode.ERROR,message:isNonErrorException(value)?value.value:value.message}),span.end()}function captureError(error,_request,context){if(isEnabledOnServer()&&isPluginEnabled(`errors`)){if(error instanceof Error){if(seen.has(error))return;seen.add(error)}recordException({error,mechanism:context?.mechanism??DEFAULT_CAPTURE_ERROR_MECHANISM,context})}}function onRequestError(error,_request,context){seen.has(error)||(seen.add(error),isEnabledOnServer()&&isPluginEnabled(`errors`)&&recordException({error,mechanism:ON_REQUEST_ERROR_MECHANISM,context:{mechanism:ON_REQUEST_ERROR_MECHANISM,nextjs:{...context,...error.digest?{errorDigest:error.digest}:{}}}}))}export{captureError,onRequestError};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.mjs","names":[],"sources":["../../../src/internal/server/capture.ts"],"sourcesContent":["import {\n isNonErrorException,\n MECHANISM_TYPE,\n type NonErrorException,\n toException,\n} from \"@interfere/types/sdk/errors\";\nimport type { ErrorMechanism } from \"@interfere/types/sdk/plugins/payload/errors\";\n\nimport { SpanKind, SpanStatusCode, trace } from \"@opentelemetry/api\";\n\nimport { isEnabledOnServer } from \"../env.js\";\nimport { isPluginEnabled } from \"./remote-config.js\";\nimport type { CaptureErrorContext, OnRequestErrorContext } from \"./types.js\";\n\nconst TRACER_NAME = \"@interfere/next/server\";\n\nconst ON_REQUEST_ERROR_MECHANISM: ErrorMechanism = {\n type: MECHANISM_TYPE.nextjs.onRequestError,\n handled: false,\n synthetic: false,\n};\n\nconst DEFAULT_CAPTURE_ERROR_MECHANISM: ErrorMechanism = {\n type: MECHANISM_TYPE.nextjs.captureError,\n handled: true,\n};\n\n// Per-process dedupe so a thrown route-handler error captured by both the\n// caller's `try { } catch { captureError(e) }` and Next's own\n// `onRequestError` hook only emits one exception event. WeakSet so GC'd\n// errors don't pin memory.\nconst seen = new WeakSet<Error>();\n\ninterface RecordExceptionInput {\n context?: CaptureErrorContext | undefined;\n error: unknown;\n mechanism: ErrorMechanism;\n}\n\nfunction buildAttrs(\n value: Error | NonErrorException,\n mechanism: ErrorMechanism,\n context: CaptureErrorContext | undefined\n): Record<string, string> {\n const attrs: Record<string, string> = {\n \"interfere.exception.mechanism\": mechanism.type,\n \"interfere.exception.handled\": String(mechanism.handled),\n };\n const digest = context?.nextjs?.errorDigest;\n if (digest) {\n attrs[\"interfere.error.digest\"] = digest;\n }\n if (context?.nextjs?.requestMethod) {\n attrs[\"http.request.method\"] = context.nextjs.requestMethod;\n }\n if (context?.nextjs?.requestPath) {\n attrs[\"url.path\"] = context.nextjs.requestPath;\n }\n if (isNonErrorException(value)) {\n attrs[\"exception.type\"] = value.type;\n attrs[\"exception.message\"] = value.value;\n attrs[\"interfere.exception.kind\"] = \"non-error\";\n return attrs;\n }\n attrs[\"exception.type\"] = value.name;\n attrs[\"exception.message\"] = value.message;\n if (value.stack) {\n attrs[\"exception.stacktrace\"] = value.stack;\n }\n return attrs;\n}\n\nfunction recordException({\n error,\n mechanism,\n context,\n}: RecordExceptionInput): void {\n const value = toException(error);\n const attrs = buildAttrs(value, mechanism, context);\n\n // Prefer the active span (route handler / Next request span). Fall\n // back to a fresh single-event span when no active span is available\n // — Next sometimes calls `onRequestError` from background contexts\n // (background revalidation, failed prerender) where the request span\n // is no longer current.\n const tracer = trace.getTracer(TRACER_NAME);\n const active = trace.getActiveSpan();\n if (active) {\n active.addEvent(\"exception\", attrs);\n if (!mechanism.handled) {\n active.setStatus({\n code: SpanStatusCode.ERROR,\n message: isNonErrorException(value) ? value.value : value.message,\n });\n }\n return;\n }\n\n const span = tracer.startSpan(\"interfere.captureError\", {\n kind: SpanKind.INTERNAL,\n });\n span.addEvent(\"exception\", attrs);\n if (!mechanism.handled) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: isNonErrorException(value) ? value.value : value.message,\n });\n }\n span.end();\n}\n\nexport function captureError(\n error: unknown,\n _request?: unknown,\n context?: CaptureErrorContext\n): void {\n if (!isEnabledOnServer()) {\n return;\n }\n\n if (!isPluginEnabled(\"errors\")) {\n return;\n }\n\n if (error instanceof Error) {\n if (seen.has(error)) {\n return;\n }\n seen.add(error);\n }\n\n recordException({\n error,\n mechanism: context?.mechanism ?? DEFAULT_CAPTURE_ERROR_MECHANISM,\n context,\n });\n}\n\nexport function onRequestError(\n error: Error & { digest?: string },\n _request: unknown,\n context: OnRequestErrorContext\n): void {\n if (seen.has(error)) {\n return;\n }\n seen.add(error);\n\n if (!isEnabledOnServer()) {\n return;\n }\n if (!isPluginEnabled(\"errors\")) {\n return;\n }\n\n recordException({\n error,\n mechanism: ON_REQUEST_ERROR_MECHANISM,\n context: {\n mechanism: ON_REQUEST_ERROR_MECHANISM,\n nextjs: {\n ...context,\n ...(error.digest ? { errorDigest: error.digest } : {}),\n },\n },\n });\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"capture.mjs","names":[],"sources":["../../../src/internal/server/capture.ts"],"sourcesContent":["import {\n isNonErrorException,\n MECHANISM_TYPE,\n type NonErrorException,\n toException,\n} from \"@interfere/types/sdk/errors\";\nimport type { ErrorMechanism } from \"@interfere/types/sdk/plugins/payload/errors\";\n\nimport { SpanKind, SpanStatusCode, trace } from \"@opentelemetry/api\";\n\nimport { isEnabledOnServer } from \"../env.js\";\nimport { isPluginEnabled } from \"./remote-config.js\";\nimport type { CaptureErrorContext, OnRequestErrorContext } from \"./types.js\";\n\nconst TRACER_NAME = \"@interfere/next/server\";\n\nconst ON_REQUEST_ERROR_MECHANISM: ErrorMechanism = {\n type: MECHANISM_TYPE.nextjs.onRequestError,\n handled: false,\n synthetic: false,\n};\n\nconst DEFAULT_CAPTURE_ERROR_MECHANISM: ErrorMechanism = {\n type: MECHANISM_TYPE.nextjs.captureError,\n handled: true,\n};\n\n// Per-process dedupe so a thrown route-handler error captured by both the\n// caller's `try { } catch { captureError(e) }` and Next's own\n// `onRequestError` hook only emits one exception event. WeakSet so GC'd\n// errors don't pin memory.\nconst seen = new WeakSet<Error>();\n\ninterface RecordExceptionInput {\n context?: CaptureErrorContext | undefined;\n error: unknown;\n mechanism: ErrorMechanism;\n}\n\nfunction buildAttrs(\n value: Error | NonErrorException,\n mechanism: ErrorMechanism,\n context: CaptureErrorContext | undefined\n): Record<string, string> {\n const attrs: Record<string, string> = {\n \"interfere.exception.mechanism\": mechanism.type,\n \"interfere.exception.handled\": String(mechanism.handled),\n };\n const digest = context?.nextjs?.errorDigest;\n if (digest) {\n attrs[\"interfere.error.digest\"] = digest;\n }\n if (context?.nextjs?.requestMethod) {\n attrs[\"http.request.method\"] = context.nextjs.requestMethod;\n }\n if (context?.nextjs?.requestPath) {\n attrs[\"url.path\"] = context.nextjs.requestPath;\n }\n if (isNonErrorException(value)) {\n attrs[\"exception.type\"] = value.type;\n attrs[\"exception.message\"] = value.value;\n attrs[\"interfere.exception.kind\"] = \"non-error\";\n return attrs;\n }\n attrs[\"exception.type\"] = value.name;\n attrs[\"exception.message\"] = value.message;\n if (value.stack) {\n attrs[\"exception.stacktrace\"] = value.stack;\n }\n return attrs;\n}\n\nfunction recordException({\n error,\n mechanism,\n context,\n}: RecordExceptionInput): void {\n const value = toException(error);\n const attrs = buildAttrs(value, mechanism, context);\n\n // Prefer the active span (route handler / Next request span). Fall\n // back to a fresh single-event span when no active span is available\n // — Next sometimes calls `onRequestError` from background contexts\n // (background revalidation, failed prerender) where the request span\n // is no longer current.\n const tracer = trace.getTracer(TRACER_NAME);\n const active = trace.getActiveSpan();\n if (active) {\n active.addEvent(\"exception\", attrs);\n if (!mechanism.handled) {\n active.setStatus({\n code: SpanStatusCode.ERROR,\n message: isNonErrorException(value) ? value.value : value.message,\n });\n }\n return;\n }\n\n const span = tracer.startSpan(\"interfere.captureError\", {\n kind: SpanKind.INTERNAL,\n });\n span.addEvent(\"exception\", attrs);\n if (!mechanism.handled) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: isNonErrorException(value) ? value.value : value.message,\n });\n }\n span.end();\n}\n\nexport function captureError(\n error: unknown,\n _request?: unknown,\n context?: CaptureErrorContext\n): void {\n if (!isEnabledOnServer()) {\n return;\n }\n\n if (!isPluginEnabled(\"errors\")) {\n return;\n }\n\n if (error instanceof Error) {\n if (seen.has(error)) {\n return;\n }\n seen.add(error);\n }\n\n recordException({\n error,\n mechanism: context?.mechanism ?? DEFAULT_CAPTURE_ERROR_MECHANISM,\n context,\n });\n}\n\nexport function onRequestError(\n error: Error & { digest?: string },\n _request: unknown,\n context: OnRequestErrorContext\n): void {\n if (seen.has(error)) {\n return;\n }\n seen.add(error);\n\n if (!isEnabledOnServer()) {\n return;\n }\n if (!isPluginEnabled(\"errors\")) {\n return;\n }\n\n recordException({\n error,\n mechanism: ON_REQUEST_ERROR_MECHANISM,\n context: {\n mechanism: ON_REQUEST_ERROR_MECHANISM,\n nextjs: {\n ...context,\n ...(error.digest ? { errorDigest: error.digest } : {}),\n },\n },\n });\n}\n"],"mappings":"iPAcA,MAEM,2BAA6C,CACjD,KAAM,eAAe,OAAO,eAC5B,QAAS,GACT,UAAW,EACb,EAEM,gCAAkD,CACtD,KAAM,eAAe,OAAO,aAC5B,QAAS,EACX,EAMM,KAAO,IAAI,QAQjB,SAAS,WACP,MACA,UACA,QACwB,CACxB,IAAM,MAAgC,CACpC,gCAAiC,UAAU,KAC3C,8BAA+B,OAAO,UAAU,OAAO,CACzD,EACM,OAAS,SAAS,QAAQ,YAqBhC,OApBI,SACF,MAAM,0BAA4B,QAEhC,SAAS,QAAQ,gBACnB,MAAM,uBAAyB,QAAQ,OAAO,eAE5C,SAAS,QAAQ,cACnB,MAAM,YAAc,QAAQ,OAAO,aAEjC,oBAAoB,KAAK,GAC3B,MAAM,kBAAoB,MAAM,KAChC,MAAM,qBAAuB,MAAM,MACnC,MAAM,4BAA8B,YAC7B,QAET,MAAM,kBAAoB,MAAM,KAChC,MAAM,qBAAuB,MAAM,QAC/B,MAAM,QACR,MAAM,wBAA0B,MAAM,OAEjC,MACT,CAEA,SAAS,gBAAgB,CACvB,MACA,UACA,SAC6B,CAC7B,IAAM,MAAQ,YAAY,KAAK,EACzB,MAAQ,WAAW,MAAO,UAAW,OAAO,EAO5C,OAAS,MAAM,UAAU,wBAAW,EACpC,OAAS,MAAM,cAAc,EACnC,GAAI,OAAQ,CACV,OAAO,SAAS,YAAa,KAAK,EAC7B,UAAU,SACb,OAAO,UAAU,CACf,KAAM,eAAe,MACrB,QAAS,oBAAoB,KAAK,EAAI,MAAM,MAAQ,MAAM,OAC5D,CAAC,EAEH,MACF,CAEA,IAAM,KAAO,OAAO,UAAU,yBAA0B,CACtD,KAAM,SAAS,QACjB,CAAC,EACD,KAAK,SAAS,YAAa,KAAK,EAC3B,UAAU,SACb,KAAK,UAAU,CACb,KAAM,eAAe,MACrB,QAAS,oBAAoB,KAAK,EAAI,MAAM,MAAQ,MAAM,OAC5D,CAAC,EAEH,KAAK,IAAI,CACX,CAEA,SAAgB,aACd,MACA,SACA,QACM,CACD,qBAAkB,GAIlB,gBAAgB,QAAQ,EAI7B,IAAI,iBAAiB,MAAO,CAC1B,GAAI,KAAK,IAAI,KAAK,EAChB,OAEF,KAAK,IAAI,KAAK,CAChB,CAEA,gBAAgB,CACd,MACA,UAAW,SAAS,WAAa,gCACjC,OACF,CAAC,CAND,CAOF,CAEA,SAAgB,eACd,MACA,SACA,QACM,CACF,KAAK,IAAI,KAAK,IAGlB,KAAK,IAAI,KAAK,EAET,kBAAkB,GAGlB,gBAAgB,QAAQ,GAI7B,gBAAgB,CACd,MACA,UAAW,2BACX,QAAS,CACP,UAAW,2BACX,OAAQ,CACN,GAAG,QACH,GAAI,MAAM,OAAS,CAAE,YAAa,MAAM,MAAO,EAAI,CAAC,CACtD,CACF,CACF,CAAC,EACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"console-bridge.d.mts","names":[],"sources":["../../../src/internal/server/console-bridge.ts"],"mappings":";;AAgFA
|
|
1
|
+
{"version":3,"file":"console-bridge.d.mts","names":[],"sources":["../../../src/internal/server/console-bridge.ts"],"mappings":";;AAgFA;;;;AAAmC;;;;;;;;;;iBAAnB,mBAAA,CAAA"}
|
|
@@ -1,112 +1 @@
|
|
|
1
|
-
import
|
|
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 };
|
|
1
|
+
import{context,trace}from"@opentelemetry/api";import{logs}from"@opentelemetry/api-logs";const CONSOLE_METHODS=[`debug`,`log`,`info`,`warn`,`error`],SEVERITY={debug:5,log:9,info:9,warn:13,error:17};function stringify(value){if(typeof value==`string`)return value;if(value instanceof Error)return value.stack??`${value.name}: ${value.message}`;try{return JSON.stringify(value)}catch{return String(value)}}function extractException(args){for(let arg of args)if(arg instanceof Error)return arg}function buildExceptionAttributes(args){let exception=extractException(args);if(!exception)return;let attrs={"exception.type":exception.name,"exception.message":exception.message};return exception.stack&&(attrs[`exception.stacktrace`]=exception.stack),attrs}function bridgeConsoleToOtel(){let logger=logs.getLogger(`@interfere/next/console`),emitting=!1,bridgeFailures=0,original={debug:console.debug,log:console.log,info:console.info,warn:console.warn,error:console.error};function emitLog(orig,severity,method,body,activeContext,activeSpan,attributes){if(!emitting){emitting=!0;try{logger.emit({severityNumber:severity,severityText:method.toUpperCase(),body,...activeSpan?{context:activeContext}:{},...attributes?{attributes}:{}})}catch(error){bridgeFailures++,bridgeFailures%100==1&&orig.call(console,`[interfere] console→OTel bridge emission failed (${bridgeFailures} total):`,error)}finally{emitting=!1}}}for(let method of CONSOLE_METHODS){let orig=original[method],severity=SEVERITY[method];console[method]=(...args)=>{orig.apply(console,args);let body=args.map(stringify).join(` `),activeContext=context.active(),activeSpan=trace.getSpan(activeContext),attributes=buildExceptionAttributes(args);queueMicrotask(()=>{emitLog(orig,severity,method,body,activeContext,activeSpan,attributes)})}}return()=>{for(let method of CONSOLE_METHODS)console[method]=original[method]}}export{bridgeConsoleToOtel};
|
|
@@ -1 +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":"
|
|
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":"wFAGA,MAIM,gBAAkB,CAAC,QAAS,MAAO,OAAQ,OAAQ,OAAO,EAG1D,SAAkD,CACtD,MAAO,EACP,IAAK,EACL,KAAM,EACN,KAAM,GACN,MAAO,EACT,EASA,SAAS,UAAU,MAAwB,CACzC,GAAI,OAAO,OAAU,SACnB,OAAO,MAET,GAAI,iBAAiB,MACnB,OAAO,MAAM,OAAS,GAAG,MAAM,KAAK,IAAI,MAAM,UAEhD,GAAI,CACF,OAAO,KAAK,UAAU,KAAK,CAC7B,MAAQ,CACN,OAAO,OAAO,KAAK,CACrB,CACF,CAEA,SAAS,iBAAiB,KAAoC,CAC5D,IAAK,IAAM,OAAO,KAChB,GAAI,eAAe,MACjB,OAAO,GAIb,CAEA,SAAS,yBACP,KACoC,CACpC,IAAM,UAAY,iBAAiB,IAAI,EACvC,GAAI,CAAC,UACH,OAEF,IAAM,MAAgC,CACnC,iBAAsB,UAAU,KAChC,oBAAyB,UAAU,OACtC,EAIA,OAHI,UAAU,QACZ,MAAM,wBAA6B,UAAU,OAExC,KACT,CAiBA,SAAgB,qBAAkC,CAChD,IAAM,OAAS,KAAK,UAAU,yBAAyB,EACnD,SAAW,GACX,eAAiB,EAEf,SAAgE,CACpE,MAAO,QAAQ,MACf,IAAK,QAAQ,IACb,KAAM,QAAQ,KACd,KAAM,QAAQ,KACd,MAAO,QAAQ,KACjB,EAEA,SAAS,QACP,KACA,SACA,OACA,KACA,cACA,WACA,WACM,CACF,aAGJ,UAAW,GACX,GAAI,CACF,OAAO,KAAK,CACV,eAAgB,SAChB,aAAc,OAAO,YAAY,EACjC,KACA,GAAI,WAAa,CAAE,QAAS,aAAc,EAAI,CAAC,EAC/C,GAAI,WAAa,CAAE,UAAW,EAAI,CAAC,CACrC,CAAC,CACH,OAAS,MAAO,CACd,iBACI,eAAiB,KAAgC,GACnD,KAAK,KACH,QACA,oDAAoD,eAAe,UACnE,KACF,CAEJ,QAAU,CACR,SAAW,EACb,CApBW,CAqBb,CAEA,IAAK,IAAM,UAAU,gBAAiB,CACpC,IAAM,KAAO,SAAS,QAChB,SAAW,SAAS,QAC1B,QAAQ,SAAW,GAAG,OAAoB,CACxC,KAAK,MAAM,QAAS,IAAI,EAExB,IAAM,KAAO,KAAK,IAAI,SAAS,EAAE,KAAK,GAAG,EACnC,cAAgB,QAAQ,OAAO,EAC/B,WAAa,MAAM,QAAQ,aAAa,EACxC,WAAa,yBAAyB,IAAI,EAQhD,mBAAqB,CACnB,QACE,KACA,SACA,OACA,KACA,cACA,WACA,UACF,CACF,CAAC,CACH,CACF,CAEA,UAAa,CACX,IAAK,IAAM,UAAU,gBACnB,QAAQ,QAAU,SAAS,OAE/B,CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"id-generator.d.mts","names":[],"sources":["../../../src/internal/server/id-generator.ts"],"mappings":";;;;;AAoCA
|
|
1
|
+
{"version":3,"file":"id-generator.d.mts","names":[],"sources":["../../../src/internal/server/id-generator.ts"],"mappings":";;;;;AAoCA;;;;;;;;;;;;;;;;AA0CwB;;;cA1CX,wBAAA,YAAoC,WAAW;EAAA,iBACzC,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"}
|
|
@@ -1,68 +1 @@
|
|
|
1
|
-
import
|
|
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 };
|
|
1
|
+
import{RandomIdGenerator}from"@opentelemetry/sdk-trace-base";import{randomBytes}from"node:crypto";const TRACE_COUNTER_MODULUS=2**32,SPAN_COUNTER_MODULUS=2**16;var PrerenderSafeIdGenerator=class{random=new RandomIdGenerator;tracePrefix;spanPrefix;traceCounter=0;spanCounter=0;constructor(){let seed=randomBytes(36/2);this.tracePrefix=seed.subarray(0,24/2).toString(`hex`),this.spanPrefix=seed.subarray(24/2).toString(`hex`)}generateTraceId(){try{return this.random.generateTraceId()}catch{return this.fallbackTraceId()}}generateSpanId(){try{return this.random.generateSpanId()}catch{return this.fallbackSpanId()}}fallbackTraceId(){return this.traceCounter=(this.traceCounter+1)%TRACE_COUNTER_MODULUS,this.tracePrefix+this.traceCounter.toString(16).padStart(8,`0`)}fallbackSpanId(){return this.spanCounter=(this.spanCounter+1)%SPAN_COUNTER_MODULUS,this.spanPrefix+this.spanCounter.toString(16).padStart(4,`0`)}};export{PrerenderSafeIdGenerator};
|