@interfere/react 10.0.0 → 10.0.1-canary.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/dist/api.d.mts.map +1 -1
- package/dist/api.mjs +1 -68
- package/dist/api.mjs.map +1 -1
- package/dist/error-boundary.d.mts +1 -2
- package/dist/error-boundary.d.mts.map +1 -1
- package/dist/error-boundary.mjs +1 -42
- package/dist/error-boundary.mjs.map +1 -1
- package/dist/internal/browser-context.d.mts.map +1 -1
- package/dist/internal/browser-context.mjs +1 -59
- package/dist/internal/browser-context.mjs.map +1 -1
- package/dist/internal/capture-boundary.d.mts +1 -2
- package/dist/internal/capture-boundary.d.mts.map +1 -1
- package/dist/internal/capture-boundary.mjs +1 -48
- package/dist/internal/capture-boundary.mjs.map +1 -1
- package/dist/internal/capture.mjs +1 -27
- package/dist/internal/capture.mjs.map +1 -1
- package/dist/internal/config.d.mts +3 -1
- package/dist/internal/config.d.mts.map +1 -1
- package/dist/internal/config.mjs +1 -33
- package/dist/internal/config.mjs.map +1 -1
- package/dist/internal/consent.d.mts.map +1 -1
- package/dist/internal/consent.mjs +1 -27
- package/dist/internal/consent.mjs.map +1 -1
- package/dist/internal/console-patch.d.mts.map +1 -1
- package/dist/internal/console-patch.mjs +1 -62
- package/dist/internal/console-patch.mjs.map +1 -1
- package/dist/internal/dom/actionable.d.mts.map +1 -1
- package/dist/internal/dom/actionable.mjs +1 -62
- package/dist/internal/dom/actionable.mjs.map +1 -1
- package/dist/internal/kernel-registry.d.mts.map +1 -1
- package/dist/internal/kernel-registry.mjs +1 -31
- package/dist/internal/kernel-registry.mjs.map +1 -1
- package/dist/internal/kernel.d.mts.map +1 -1
- package/dist/internal/kernel.mjs +1 -322
- package/dist/internal/kernel.mjs.map +1 -1
- package/dist/internal/otel/exporter.d.mts +4 -12
- package/dist/internal/otel/exporter.d.mts.map +1 -1
- package/dist/internal/otel/exporter.mjs +1 -212
- package/dist/internal/otel/exporter.mjs.map +1 -1
- package/dist/internal/otel/index.mjs +1 -6
- package/dist/internal/otel/instrumentations.d.mts.map +1 -1
- package/dist/internal/otel/instrumentations.mjs +1 -150
- package/dist/internal/otel/instrumentations.mjs.map +1 -1
- package/dist/internal/otel/page-scope-context-manager.d.mts.map +1 -1
- package/dist/internal/otel/page-scope-context-manager.mjs +1 -36
- package/dist/internal/otel/page-scope-context-manager.mjs.map +1 -1
- package/dist/internal/otel/propagation.d.mts.map +1 -1
- package/dist/internal/otel/propagation.mjs +1 -40
- package/dist/internal/otel/propagation.mjs.map +1 -1
- package/dist/internal/otel/provider.d.mts +1 -2
- package/dist/internal/otel/provider.d.mts.map +1 -1
- package/dist/internal/otel/provider.mjs +1 -151
- package/dist/internal/otel/provider.mjs.map +1 -1
- package/dist/internal/otel/web-vitals.d.mts.map +1 -1
- package/dist/internal/otel/web-vitals.mjs +1 -162
- package/dist/internal/otel/web-vitals.mjs.map +1 -1
- package/dist/internal/page-lifecycle.d.mts.map +1 -1
- package/dist/internal/page-lifecycle.mjs +1 -33
- package/dist/internal/page-lifecycle.mjs.map +1 -1
- package/dist/internal/plugin-runtime.mjs +1 -101
- package/dist/internal/plugin-runtime.mjs.map +1 -1
- package/dist/internal/react-context.d.mts +1 -2
- package/dist/internal/react-context.d.mts.map +1 -1
- package/dist/internal/react-context.mjs +1 -34
- package/dist/internal/react-context.mjs.map +1 -1
- package/dist/internal/sw.d.mts.map +1 -1
- package/dist/internal/sw.mjs +1 -37
- package/dist/internal/sw.mjs.map +1 -1
- package/dist/internal/version.mjs +1 -7
- package/dist/internal/version.mjs.map +1 -1
- package/dist/internal/wrapper-singleton.d.mts.map +1 -1
- package/dist/internal/wrapper-singleton.mjs +1 -73
- package/dist/internal/wrapper-singleton.mjs.map +1 -1
- package/dist/package.mjs +1 -5
- package/dist/plugins/errors.d.mts.map +1 -1
- package/dist/plugins/errors.mjs +1 -84
- package/dist/plugins/errors.mjs.map +1 -1
- package/dist/plugins/lib/loader.mjs +1 -34
- package/dist/plugins/lib/loader.mjs.map +1 -1
- package/dist/plugins/lib/types.d.mts.map +1 -1
- package/dist/plugins/lib/types.mjs +1 -1
- package/dist/plugins/logs.d.mts.map +1 -1
- package/dist/plugins/logs.mjs +1 -53
- package/dist/plugins/logs.mjs.map +1 -1
- package/dist/plugins/rage-clicks.d.mts.map +1 -1
- package/dist/plugins/rage-clicks.mjs +1 -55
- package/dist/plugins/rage-clicks.mjs.map +1 -1
- package/dist/plugins/replay.d.mts.map +1 -1
- package/dist/plugins/replay.mjs +1 -101
- package/dist/plugins/replay.mjs.map +1 -1
- package/dist/provider.d.mts.map +1 -1
- package/dist/provider.mjs +1 -31
- package/dist/provider.mjs.map +1 -1
- package/dist/react-error-handler.d.mts.map +1 -1
- package/dist/react-error-handler.mjs +1 -62
- package/dist/react-error-handler.mjs.map +1 -1
- package/dist/tracking/api.d.mts.map +1 -1
- package/dist/tracking/api.mjs +1 -152
- package/dist/tracking/api.mjs.map +1 -1
- package/dist/tracking/device.d.mts.map +1 -1
- package/dist/tracking/device.mjs +1 -104
- package/dist/tracking/device.mjs.map +1 -1
- package/dist/tracking/geo.d.mts.map +1 -1
- package/dist/tracking/geo.mjs +2 -48
- package/dist/tracking/geo.mjs.map +1 -1
- package/dist/tracking/session.d.mts.map +1 -1
- package/dist/tracking/session.mjs +1 -75
- package/dist/tracking/session.mjs.map +1 -1
- package/dist/util/bot.d.mts.map +1 -1
- package/dist/util/bot.mjs +1 -14
- package/dist/util/bot.mjs.map +1 -1
- package/dist/util/global.d.mts.map +1 -1
- package/dist/util/global.mjs +1 -12
- package/dist/util/global.mjs.map +1 -1
- package/dist/util/log.d.mts.map +1 -1
- package/dist/util/log.mjs +1 -44
- package/dist/util/log.mjs.map +1 -1
- package/dist/util/stringify.d.mts.map +1 -1
- package/dist/util/stringify.mjs +1 -16
- package/dist/util/stringify.mjs.map +1 -1
- package/package.json +34 -33
|
@@ -1,150 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { registerInstrumentations } from "@opentelemetry/instrumentation";
|
|
3
|
-
import { BrowserNavigationInstrumentation, defaultSanitizeUrl } from "@opentelemetry/instrumentation-browser-navigation";
|
|
4
|
-
import { DocumentLoadInstrumentation } from "@opentelemetry/instrumentation-document-load";
|
|
5
|
-
import { FetchInstrumentation } from "@opentelemetry/instrumentation-fetch";
|
|
6
|
-
import { LongTaskInstrumentation } from "@opentelemetry/instrumentation-long-task";
|
|
7
|
-
import { UserInteractionInstrumentation } from "@opentelemetry/instrumentation-user-interaction";
|
|
8
|
-
import { XMLHttpRequestInstrumentation } from "@opentelemetry/instrumentation-xml-http-request";
|
|
9
|
-
//#region src/internal/otel/instrumentations.ts
|
|
10
|
-
const ATTR_HTTP_ROUTE = "http.route";
|
|
11
|
-
const ATTR_URL_FULL = "url.full";
|
|
12
|
-
const ATTR_URL_PATH = "url.path";
|
|
13
|
-
/**
|
|
14
|
-
* Mirrors the `semconvStabilityOptIn` constant from the internal
|
|
15
|
-
* `observability/browser/rum.ts`. Pins the HTTP attribute set to the
|
|
16
|
-
* stable-namespace conventions (`url.path`, `server.address`, …) so
|
|
17
|
-
* dashboards that filter on those keys aren't broken by minor
|
|
18
|
-
* upgrades of the instrumentation packages.
|
|
19
|
-
*/
|
|
20
|
-
const SEMCONV_STABILITY_OPT_IN = "http,database,messaging,gen_ai_latest_experimental";
|
|
21
|
-
/**
|
|
22
|
-
* Hosts/paths whose CLIENT fetch spans we never want to emit: they
|
|
23
|
-
* always become 1-span orphan traces (no traceparent propagation to
|
|
24
|
-
* 3rd-party origins) and clutter live tail. Conservative, customer-
|
|
25
|
-
* generic list — common analytics / ads / auth / replay vendors that
|
|
26
|
-
* many apps use. Customer-specific noise (Statsig CDNs, our own
|
|
27
|
-
* BetterStack, etc.) belongs in the customer's own `ignoreUrls` config,
|
|
28
|
-
* not hardcoded into a public SDK.
|
|
29
|
-
*/
|
|
30
|
-
const THIRD_PARTY_IGNORE = [
|
|
31
|
-
/(?:^|\/\/)clerk\./,
|
|
32
|
-
/\.clerk\./,
|
|
33
|
-
/\.sentry\./,
|
|
34
|
-
/\.intercom\./,
|
|
35
|
-
/\.posthog\./,
|
|
36
|
-
/\.googletagmanager\.com/,
|
|
37
|
-
/\.google-analytics\.com/,
|
|
38
|
-
/\.googleapis\.com/,
|
|
39
|
-
/\.doubleclick\.net/,
|
|
40
|
-
/\.facebook\.com/,
|
|
41
|
-
/\.fbcdn\.net/,
|
|
42
|
-
/\.analytics\.google\.com/,
|
|
43
|
-
/px\.ads\.linkedin\.com/,
|
|
44
|
-
/[?&]_rsc=/
|
|
45
|
-
];
|
|
46
|
-
const NEVER_MATCH = /^$/;
|
|
47
|
-
const ESCAPE_RE = /[.*+?^${}()|[\]\\]/g;
|
|
48
|
-
function sameOriginPattern() {
|
|
49
|
-
if (typeof window === "undefined") return NEVER_MATCH;
|
|
50
|
-
const { protocol, host } = window.location;
|
|
51
|
-
return new RegExp(`^${protocol}//${host.replace(ESCAPE_RE, "\\$&")}`);
|
|
52
|
-
}
|
|
53
|
-
function stampInteractionAttrs(eventType, element, span) {
|
|
54
|
-
const desc = describeActionable(element);
|
|
55
|
-
span.setAttribute("ui.event_type", eventType);
|
|
56
|
-
span.setAttribute("ui.target.tag", desc.tag);
|
|
57
|
-
if (desc.id) span.setAttribute("ui.target.id", desc.id);
|
|
58
|
-
if (desc.name) span.setAttribute("ui.target.name", desc.name);
|
|
59
|
-
if (desc.role) span.setAttribute("ui.target.role", desc.role);
|
|
60
|
-
if (desc.ariaLabel) span.setAttribute("ui.target.aria_label", desc.ariaLabel);
|
|
61
|
-
if (desc.text) span.setAttribute("ui.target.text", desc.text);
|
|
62
|
-
}
|
|
63
|
-
function stampUrlAttrs(span, resolveRoute) {
|
|
64
|
-
if (typeof window === "undefined") return;
|
|
65
|
-
const pathname = window.location.pathname;
|
|
66
|
-
const route = resolveRoute?.(pathname);
|
|
67
|
-
span.setAttribute(ATTR_URL_PATH, route ?? pathname);
|
|
68
|
-
span.setAttribute(ATTR_URL_FULL, window.location.href);
|
|
69
|
-
if (route) span.setAttribute(ATTR_HTTP_ROUTE, route);
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Registers every browser auto-instrumentation against the kernel's
|
|
73
|
-
* private provider. Every instrumentation is enriched with the same
|
|
74
|
-
* route-template-aware URL attributes so dashboards can slice fetch /
|
|
75
|
-
* interaction / long-task / resource-load by `http.route` without
|
|
76
|
-
* cardinality blow-up from raw pathnames.
|
|
77
|
-
*
|
|
78
|
-
* Returns a disposer that unregisters everything; called by
|
|
79
|
-
* `kernel.dispose()`.
|
|
80
|
-
*/
|
|
81
|
-
function registerBundledInstrumentations(input) {
|
|
82
|
-
const { tracerProvider, ignoreUrls, propagateContextUrls = [] } = input;
|
|
83
|
-
const resolveRoute = input.resolveRoute;
|
|
84
|
-
const origin = sameOriginPattern();
|
|
85
|
-
const fetchIgnore = [...ignoreUrls, ...THIRD_PARTY_IGNORE];
|
|
86
|
-
return registerInstrumentations({
|
|
87
|
-
tracerProvider,
|
|
88
|
-
instrumentations: [
|
|
89
|
-
new FetchInstrumentation({
|
|
90
|
-
propagateTraceHeaderCorsUrls: [origin, ...propagateContextUrls],
|
|
91
|
-
ignoreUrls: fetchIgnore,
|
|
92
|
-
semconvStabilityOptIn: SEMCONV_STABILITY_OPT_IN,
|
|
93
|
-
measureRequestSize: true,
|
|
94
|
-
applyCustomAttributesOnSpan: (span, request) => {
|
|
95
|
-
const url = request instanceof Request ? request.url : request?.toString();
|
|
96
|
-
if (typeof url !== "string") return;
|
|
97
|
-
try {
|
|
98
|
-
const parsed = new URL(url);
|
|
99
|
-
span.setAttribute(ATTR_URL_PATH, parsed.pathname);
|
|
100
|
-
if (origin.test(url) || propagateContextUrls.some((re) => re instanceof RegExp ? re.test(url) : url.includes(re))) {
|
|
101
|
-
const method = (request instanceof Request ? request.method : void 0) ?? "GET";
|
|
102
|
-
const route = resolveRoute?.(parsed.pathname);
|
|
103
|
-
if (route) span.setAttribute(ATTR_HTTP_ROUTE, route);
|
|
104
|
-
span.updateName(`${method} ${route ?? parsed.pathname}`);
|
|
105
|
-
}
|
|
106
|
-
} catch {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}),
|
|
111
|
-
new XMLHttpRequestInstrumentation({
|
|
112
|
-
propagateTraceHeaderCorsUrls: [origin, ...propagateContextUrls],
|
|
113
|
-
ignoreUrls: fetchIgnore,
|
|
114
|
-
semconvStabilityOptIn: SEMCONV_STABILITY_OPT_IN
|
|
115
|
-
}),
|
|
116
|
-
new DocumentLoadInstrumentation({
|
|
117
|
-
semconvStabilityOptIn: SEMCONV_STABILITY_OPT_IN,
|
|
118
|
-
applyCustomAttributesOnSpan: { resourceFetch: (span, entry) => {
|
|
119
|
-
const name = entry?.name;
|
|
120
|
-
if (typeof name !== "string") return;
|
|
121
|
-
try {
|
|
122
|
-
const url = new URL(name);
|
|
123
|
-
const isCrossOrigin = typeof window !== "undefined" && url.origin !== window.location.origin;
|
|
124
|
-
span.updateName(isCrossOrigin ? `resource: ${url.host}${url.pathname}` : `resource: ${url.pathname}`);
|
|
125
|
-
span.setAttribute(ATTR_URL_FULL, name);
|
|
126
|
-
span.setAttribute(ATTR_URL_PATH, url.pathname);
|
|
127
|
-
span.setAttribute("server.address", url.host);
|
|
128
|
-
} catch {}
|
|
129
|
-
} }
|
|
130
|
-
}),
|
|
131
|
-
new BrowserNavigationInstrumentation({ sanitizeUrl: defaultSanitizeUrl }),
|
|
132
|
-
new UserInteractionInstrumentation({
|
|
133
|
-
eventNames: ["click", "submit"],
|
|
134
|
-
shouldPreventSpanCreation: (eventType, element, span) => {
|
|
135
|
-
if (eventType === "click" && !isActionable(element)) return true;
|
|
136
|
-
stampInteractionAttrs(eventType, element, span);
|
|
137
|
-
stampUrlAttrs(span, resolveRoute);
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
}),
|
|
141
|
-
new LongTaskInstrumentation({ observerCallback: (span, info) => {
|
|
142
|
-
stampUrlAttrs(span, resolveRoute);
|
|
143
|
-
const containerType = info.longtaskEntry.attribution[0]?.containerType;
|
|
144
|
-
if (containerType) span.setAttribute("longtask.container_type", containerType);
|
|
145
|
-
} })
|
|
146
|
-
]
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
//#endregion
|
|
150
|
-
export { registerBundledInstrumentations };
|
|
1
|
+
import{describeActionable,isActionable}from"../dom/actionable.mjs";import{registerInstrumentations}from"@opentelemetry/instrumentation";import{BrowserNavigationInstrumentation,defaultSanitizeUrl}from"@opentelemetry/instrumentation-browser-navigation";import{DocumentLoadInstrumentation}from"@opentelemetry/instrumentation-document-load";import{FetchInstrumentation}from"@opentelemetry/instrumentation-fetch";import{LongTaskInstrumentation}from"@opentelemetry/instrumentation-long-task";import{UserInteractionInstrumentation}from"@opentelemetry/instrumentation-user-interaction";import{XMLHttpRequestInstrumentation}from"@opentelemetry/instrumentation-xml-http-request";const ATTR_HTTP_ROUTE=`http.route`,ATTR_URL_FULL=`url.full`,ATTR_URL_PATH=`url.path`,SEMCONV_STABILITY_OPT_IN=`http,database,messaging,gen_ai_latest_experimental`,THIRD_PARTY_IGNORE=[/(?:^|\/\/)clerk\./,/\.clerk\./,/\.sentry\./,/\.intercom\./,/\.posthog\./,/\.googletagmanager\.com/,/\.google-analytics\.com/,/\.googleapis\.com/,/\.doubleclick\.net/,/\.facebook\.com/,/\.fbcdn\.net/,/\.analytics\.google\.com/,/px\.ads\.linkedin\.com/,/[?&]_rsc=/],NEVER_MATCH=/^$/,ESCAPE_RE=/[.*+?^${}()|[\]\\]/g;function sameOriginPattern(){if(typeof window>`u`)return NEVER_MATCH;let{protocol,host}=window.location;return RegExp(`^${protocol}//${host.replace(ESCAPE_RE,`\\$&`)}`)}function stampInteractionAttrs(eventType,element,span){let desc=describeActionable(element);span.setAttribute(`ui.event_type`,eventType),span.setAttribute(`ui.target.tag`,desc.tag),desc.id&&span.setAttribute(`ui.target.id`,desc.id),desc.name&&span.setAttribute(`ui.target.name`,desc.name),desc.role&&span.setAttribute(`ui.target.role`,desc.role),desc.ariaLabel&&span.setAttribute(`ui.target.aria_label`,desc.ariaLabel),desc.text&&span.setAttribute(`ui.target.text`,desc.text)}function stampUrlAttrs(span,resolveRoute){if(typeof window>`u`)return;let pathname=window.location.pathname,route=resolveRoute?.(pathname);span.setAttribute(ATTR_URL_PATH,route??pathname),span.setAttribute(ATTR_URL_FULL,window.location.href),route&&span.setAttribute(ATTR_HTTP_ROUTE,route)}function registerBundledInstrumentations(input){let{tracerProvider,ignoreUrls,propagateContextUrls=[]}=input,resolveRoute=input.resolveRoute,origin=sameOriginPattern(),fetchIgnore=[...ignoreUrls,...THIRD_PARTY_IGNORE];return registerInstrumentations({tracerProvider,instrumentations:[new FetchInstrumentation({propagateTraceHeaderCorsUrls:[origin,...propagateContextUrls],ignoreUrls:fetchIgnore,semconvStabilityOptIn:SEMCONV_STABILITY_OPT_IN,measureRequestSize:!0,applyCustomAttributesOnSpan:(span,request)=>{let url=request instanceof Request?request.url:request?.toString();if(typeof url==`string`)try{let parsed=new URL(url);if(span.setAttribute(ATTR_URL_PATH,parsed.pathname),origin.test(url)||propagateContextUrls.some(re=>re instanceof RegExp?re.test(url):url.includes(re))){let method=(request instanceof Request?request.method:void 0)??`GET`,route=resolveRoute?.(parsed.pathname);route&&span.setAttribute(ATTR_HTTP_ROUTE,route),span.updateName(`${method} ${route??parsed.pathname}`)}}catch{return}}}),new XMLHttpRequestInstrumentation({propagateTraceHeaderCorsUrls:[origin,...propagateContextUrls],ignoreUrls:fetchIgnore,semconvStabilityOptIn:SEMCONV_STABILITY_OPT_IN}),new DocumentLoadInstrumentation({semconvStabilityOptIn:SEMCONV_STABILITY_OPT_IN,applyCustomAttributesOnSpan:{resourceFetch:(span,entry)=>{let name=entry?.name;if(typeof name==`string`)try{let url=new URL(name),isCrossOrigin=typeof window<`u`&&url.origin!==window.location.origin;span.updateName(isCrossOrigin?`resource: ${url.host}${url.pathname}`:`resource: ${url.pathname}`),span.setAttribute(ATTR_URL_FULL,name),span.setAttribute(ATTR_URL_PATH,url.pathname),span.setAttribute(`server.address`,url.host)}catch{}}}}),new BrowserNavigationInstrumentation({sanitizeUrl:defaultSanitizeUrl}),new UserInteractionInstrumentation({eventNames:[`click`,`submit`],shouldPreventSpanCreation:(eventType,element,span)=>eventType===`click`&&!isActionable(element)?!0:(stampInteractionAttrs(eventType,element,span),stampUrlAttrs(span,resolveRoute),!1)}),new LongTaskInstrumentation({observerCallback:(span,info)=>{stampUrlAttrs(span,resolveRoute);let containerType=info.longtaskEntry.attribution[0]?.containerType;containerType&&span.setAttribute(`longtask.container_type`,containerType)}})]})}export{registerBundledInstrumentations};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentations.mjs","names":[],"sources":["../../../src/internal/otel/instrumentations.ts"],"sourcesContent":["import type { Span } from \"@opentelemetry/api\";\nimport { registerInstrumentations } from \"@opentelemetry/instrumentation\";\nimport {\n BrowserNavigationInstrumentation,\n defaultSanitizeUrl,\n} from \"@opentelemetry/instrumentation-browser-navigation\";\nimport { DocumentLoadInstrumentation } from \"@opentelemetry/instrumentation-document-load\";\nimport { FetchInstrumentation } from \"@opentelemetry/instrumentation-fetch\";\nimport {\n LongTaskInstrumentation,\n type ObserverCallbackInformation,\n} from \"@opentelemetry/instrumentation-long-task\";\nimport { UserInteractionInstrumentation } from \"@opentelemetry/instrumentation-user-interaction\";\nimport { XMLHttpRequestInstrumentation } from \"@opentelemetry/instrumentation-xml-http-request\";\nimport type { WebTracerProvider } from \"@opentelemetry/sdk-trace-web\";\n\nimport { describeActionable, isActionable } from \"../dom/actionable.js\";\n\nconst ATTR_HTTP_ROUTE = \"http.route\" as const;\nconst ATTR_URL_FULL = \"url.full\" as const;\nconst ATTR_URL_PATH = \"url.path\" as const;\n\n/**\n * Mirrors the `semconvStabilityOptIn` constant from the internal\n * `observability/browser/rum.ts`. Pins the HTTP attribute set to the\n * stable-namespace conventions (`url.path`, `server.address`, …) so\n * dashboards that filter on those keys aren't broken by minor\n * upgrades of the instrumentation packages.\n */\nconst SEMCONV_STABILITY_OPT_IN =\n \"http,database,messaging,gen_ai_latest_experimental\";\n\n/**\n * Hosts/paths whose CLIENT fetch spans we never want to emit: they\n * always become 1-span orphan traces (no traceparent propagation to\n * 3rd-party origins) and clutter live tail. Conservative, customer-\n * generic list — common analytics / ads / auth / replay vendors that\n * many apps use. Customer-specific noise (Statsig CDNs, our own\n * BetterStack, etc.) belongs in the customer's own `ignoreUrls` config,\n * not hardcoded into a public SDK.\n */\nconst THIRD_PARTY_IGNORE: RegExp[] = [\n /(?:^|\\/\\/)clerk\\./,\n /\\.clerk\\./,\n /\\.sentry\\./,\n /\\.intercom\\./,\n /\\.posthog\\./,\n /\\.googletagmanager\\.com/,\n /\\.google-analytics\\.com/,\n /\\.googleapis\\.com/,\n /\\.doubleclick\\.net/,\n /\\.facebook\\.com/,\n /\\.fbcdn\\.net/,\n /\\.analytics\\.google\\.com/,\n /px\\.ads\\.linkedin\\.com/,\n /[?&]_rsc=/,\n];\n\nconst NEVER_MATCH = /^$/;\nconst ESCAPE_RE = /[.*+?^${}()|[\\]\\\\]/g;\n\nfunction sameOriginPattern(): RegExp {\n if (typeof window === \"undefined\") {\n return NEVER_MATCH;\n }\n const { protocol, host } = window.location;\n return new RegExp(`^${protocol}//${host.replace(ESCAPE_RE, \"\\\\$&\")}`);\n}\n\nfunction stampInteractionAttrs(\n eventType: string,\n element: HTMLElement,\n span: Span\n): void {\n const desc = describeActionable(element);\n span.setAttribute(\"ui.event_type\", eventType);\n span.setAttribute(\"ui.target.tag\", desc.tag);\n if (desc.id) {\n span.setAttribute(\"ui.target.id\", desc.id);\n }\n if (desc.name) {\n span.setAttribute(\"ui.target.name\", desc.name);\n }\n if (desc.role) {\n span.setAttribute(\"ui.target.role\", desc.role);\n }\n if (desc.ariaLabel) {\n span.setAttribute(\"ui.target.aria_label\", desc.ariaLabel);\n }\n // Truncated visible label — useful for triage without exfiltrating\n // full DOM contents. Only meaningful on the direct target.\n if (desc.text) {\n span.setAttribute(\"ui.target.text\", desc.text);\n }\n}\n\nfunction stampUrlAttrs(\n span: Span,\n resolveRoute: ((pathname: string) => string | undefined) | undefined\n): void {\n if (typeof window === \"undefined\") {\n return;\n }\n const pathname = window.location.pathname;\n const route = resolveRoute?.(pathname);\n span.setAttribute(ATTR_URL_PATH, route ?? pathname);\n span.setAttribute(ATTR_URL_FULL, window.location.href);\n if (route) {\n span.setAttribute(ATTR_HTTP_ROUTE, route);\n }\n}\n\nexport interface InstrumentationsInput {\n /**\n * URLs the SDK exempts from `fetch` and `XHR` instrumentation —\n * typically our own ingest/OTLP endpoints, so the SDK doesn't trace\n * its own export requests in an infinite loop. Combined with the\n * `THIRD_PARTY_IGNORE` defaults and the customer's `ignoreUrls`.\n */\n ignoreUrls: (string | RegExp)[];\n /**\n * Customer-supplied URL patterns the fetch + XHR instrumentations\n * inject `traceparent` + `baggage` headers on. Same-origin requests\n * always propagate.\n */\n propagateContextUrls?: (string | RegExp)[];\n /**\n * Pathname → low-cardinality route template (e.g. `/blog/[slug]`).\n * Drives span renaming + `url.path` / `http.route` attrs across\n * fetch, document-load, user-interaction, and long-task.\n */\n resolveRoute?: (pathname: string) => string | undefined;\n /**\n * The kernel's private provider — instrumentations register against\n * it, not the global one, so customer OTel setups stay untouched.\n */\n tracerProvider: WebTracerProvider;\n}\n\n/**\n * Registers every browser auto-instrumentation against the kernel's\n * private provider. Every instrumentation is enriched with the same\n * route-template-aware URL attributes so dashboards can slice fetch /\n * interaction / long-task / resource-load by `http.route` without\n * cardinality blow-up from raw pathnames.\n *\n * Returns a disposer that unregisters everything; called by\n * `kernel.dispose()`.\n */\nexport function registerBundledInstrumentations(\n input: InstrumentationsInput\n): () => void {\n const { tracerProvider, ignoreUrls, propagateContextUrls = [] } = input;\n const resolveRoute = input.resolveRoute;\n const origin = sameOriginPattern();\n const fetchIgnore = [...ignoreUrls, ...THIRD_PARTY_IGNORE];\n\n return registerInstrumentations({\n tracerProvider,\n instrumentations: [\n new FetchInstrumentation({\n propagateTraceHeaderCorsUrls: [origin, ...propagateContextUrls],\n ignoreUrls: fetchIgnore,\n semconvStabilityOptIn: SEMCONV_STABILITY_OPT_IN,\n measureRequestSize: true,\n applyCustomAttributesOnSpan: (\n span: Span,\n request: Request | RequestInit\n ) => {\n const url =\n request instanceof Request ? request.url : request?.toString();\n if (typeof url !== \"string\") {\n return;\n }\n try {\n const parsed = new URL(url);\n span.setAttribute(ATTR_URL_PATH, parsed.pathname);\n const isTracked =\n origin.test(url) ||\n propagateContextUrls.some((re) =>\n re instanceof RegExp ? re.test(url) : url.includes(re)\n );\n if (isTracked) {\n const method =\n (request instanceof Request ? request.method : undefined) ??\n \"GET\";\n const route = resolveRoute?.(parsed.pathname);\n if (route) {\n span.setAttribute(ATTR_HTTP_ROUTE, route);\n }\n span.updateName(`${method} ${route ?? parsed.pathname}`);\n }\n } catch {\n return;\n }\n },\n }),\n new XMLHttpRequestInstrumentation({\n propagateTraceHeaderCorsUrls: [origin, ...propagateContextUrls],\n ignoreUrls: fetchIgnore,\n semconvStabilityOptIn: SEMCONV_STABILITY_OPT_IN,\n }),\n new DocumentLoadInstrumentation({\n semconvStabilityOptIn: SEMCONV_STABILITY_OPT_IN,\n // Default `resourceFetch` span name is the literal string\n // `\"resourceFetch\"` for every entry — useless when a single\n // page load emits 30 of them. Replace with the pathname (or\n // origin+path for cross-origin) so the waterfall is scannable.\n applyCustomAttributesOnSpan: {\n resourceFetch: (span, entry) => {\n const name = entry?.name;\n if (typeof name !== \"string\") {\n return;\n }\n try {\n const url = new URL(name);\n const isCrossOrigin =\n typeof window !== \"undefined\" &&\n url.origin !== window.location.origin;\n span.updateName(\n isCrossOrigin\n ? `resource: ${url.host}${url.pathname}`\n : `resource: ${url.pathname}`\n );\n span.setAttribute(ATTR_URL_FULL, name);\n span.setAttribute(ATTR_URL_PATH, url.pathname);\n span.setAttribute(\"server.address\", url.host);\n } catch {\n // Malformed/relative URL — leave the default name and\n // let the entry's own attributes carry whatever info\n // the SDK already stamped.\n }\n },\n },\n }),\n // `BrowserNavigationInstrumentation` emits a `browser.navigation`\n // log record per hard navigation (page load) and soft navigation\n // (history.pushState, hash change, back/forward). The default\n // sanitizer redacts credentials in the URL and common sensitive\n // query params (`token`, `api_key`, `secret`, …) before emit.\n new BrowserNavigationInstrumentation({\n sanitizeUrl: defaultSanitizeUrl,\n }),\n new UserInteractionInstrumentation({\n eventNames: [\"click\", \"submit\"],\n // Despite the name, `shouldPreventSpanCreation` is also the\n // hook for *enriching* spans (return falsy → create the span;\n // the callback may set attributes on the way through). We use\n // it as both: stamp a stable target descriptor for\n // dashboarding, then suppress spans on elements that aren't\n // actionable (scroll containers, body clicks, etc.).\n shouldPreventSpanCreation: (eventType, element, span) => {\n if (eventType === \"click\" && !isActionable(element)) {\n return true;\n }\n stampInteractionAttrs(eventType, element, span);\n stampUrlAttrs(span, resolveRoute);\n return false;\n },\n }),\n // Long-task spans default to a generic `longtask` name with\n // attribution embedded as attributes — but no page context.\n // Empty `url.path` makes it impossible to triage which routes\n // are jank-heavy. Stamp it in the observer callback so\n // dashboards can slice by route.\n new LongTaskInstrumentation({\n observerCallback: (span, info: ObserverCallbackInformation) => {\n stampUrlAttrs(span, resolveRoute);\n // PerformanceLongTaskTiming attribution[0].containerType is\n // the surface that hosted the offending task. Most are\n // `\"window\"` (main frame); the few that aren't (`\"iframe\"`,\n // `\"embed\"`, `\"object\"`) are the actionable ones — surface\n // them as a top-level attr instead of leaving them buried\n // in `longtask.attribution.container_type`.\n const containerType =\n info.longtaskEntry.attribution[0]?.containerType;\n if (containerType) {\n span.setAttribute(\"longtask.container_type\", containerType);\n }\n },\n }),\n ],\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"instrumentations.mjs","names":[],"sources":["../../../src/internal/otel/instrumentations.ts"],"sourcesContent":["import type { Span } from \"@opentelemetry/api\";\nimport { registerInstrumentations } from \"@opentelemetry/instrumentation\";\nimport {\n BrowserNavigationInstrumentation,\n defaultSanitizeUrl,\n} from \"@opentelemetry/instrumentation-browser-navigation\";\nimport { DocumentLoadInstrumentation } from \"@opentelemetry/instrumentation-document-load\";\nimport { FetchInstrumentation } from \"@opentelemetry/instrumentation-fetch\";\nimport {\n LongTaskInstrumentation,\n type ObserverCallbackInformation,\n} from \"@opentelemetry/instrumentation-long-task\";\nimport { UserInteractionInstrumentation } from \"@opentelemetry/instrumentation-user-interaction\";\nimport { XMLHttpRequestInstrumentation } from \"@opentelemetry/instrumentation-xml-http-request\";\nimport type { WebTracerProvider } from \"@opentelemetry/sdk-trace-web\";\n\nimport { describeActionable, isActionable } from \"../dom/actionable.js\";\n\nconst ATTR_HTTP_ROUTE = \"http.route\" as const;\nconst ATTR_URL_FULL = \"url.full\" as const;\nconst ATTR_URL_PATH = \"url.path\" as const;\n\n/**\n * Mirrors the `semconvStabilityOptIn` constant from the internal\n * `observability/browser/rum.ts`. Pins the HTTP attribute set to the\n * stable-namespace conventions (`url.path`, `server.address`, …) so\n * dashboards that filter on those keys aren't broken by minor\n * upgrades of the instrumentation packages.\n */\nconst SEMCONV_STABILITY_OPT_IN =\n \"http,database,messaging,gen_ai_latest_experimental\";\n\n/**\n * Hosts/paths whose CLIENT fetch spans we never want to emit: they\n * always become 1-span orphan traces (no traceparent propagation to\n * 3rd-party origins) and clutter live tail. Conservative, customer-\n * generic list — common analytics / ads / auth / replay vendors that\n * many apps use. Customer-specific noise (feature-flag CDNs, our own\n * BetterStack, etc.) belongs in the customer's own `ignoreUrls` config,\n * not hardcoded into a public SDK.\n */\nconst THIRD_PARTY_IGNORE: RegExp[] = [\n /(?:^|\\/\\/)clerk\\./,\n /\\.clerk\\./,\n /\\.sentry\\./,\n /\\.intercom\\./,\n /\\.posthog\\./,\n /\\.googletagmanager\\.com/,\n /\\.google-analytics\\.com/,\n /\\.googleapis\\.com/,\n /\\.doubleclick\\.net/,\n /\\.facebook\\.com/,\n /\\.fbcdn\\.net/,\n /\\.analytics\\.google\\.com/,\n /px\\.ads\\.linkedin\\.com/,\n /[?&]_rsc=/,\n];\n\nconst NEVER_MATCH = /^$/;\nconst ESCAPE_RE = /[.*+?^${}()|[\\]\\\\]/g;\n\nfunction sameOriginPattern(): RegExp {\n if (typeof window === \"undefined\") {\n return NEVER_MATCH;\n }\n const { protocol, host } = window.location;\n return new RegExp(`^${protocol}//${host.replace(ESCAPE_RE, \"\\\\$&\")}`);\n}\n\nfunction stampInteractionAttrs(\n eventType: string,\n element: HTMLElement,\n span: Span\n): void {\n const desc = describeActionable(element);\n span.setAttribute(\"ui.event_type\", eventType);\n span.setAttribute(\"ui.target.tag\", desc.tag);\n if (desc.id) {\n span.setAttribute(\"ui.target.id\", desc.id);\n }\n if (desc.name) {\n span.setAttribute(\"ui.target.name\", desc.name);\n }\n if (desc.role) {\n span.setAttribute(\"ui.target.role\", desc.role);\n }\n if (desc.ariaLabel) {\n span.setAttribute(\"ui.target.aria_label\", desc.ariaLabel);\n }\n // Truncated visible label — useful for triage without exfiltrating\n // full DOM contents. Only meaningful on the direct target.\n if (desc.text) {\n span.setAttribute(\"ui.target.text\", desc.text);\n }\n}\n\nfunction stampUrlAttrs(\n span: Span,\n resolveRoute: ((pathname: string) => string | undefined) | undefined\n): void {\n if (typeof window === \"undefined\") {\n return;\n }\n const pathname = window.location.pathname;\n const route = resolveRoute?.(pathname);\n span.setAttribute(ATTR_URL_PATH, route ?? pathname);\n span.setAttribute(ATTR_URL_FULL, window.location.href);\n if (route) {\n span.setAttribute(ATTR_HTTP_ROUTE, route);\n }\n}\n\nexport interface InstrumentationsInput {\n /**\n * URLs the SDK exempts from `fetch` and `XHR` instrumentation —\n * typically our own ingest/OTLP endpoints, so the SDK doesn't trace\n * its own export requests in an infinite loop. Combined with the\n * `THIRD_PARTY_IGNORE` defaults and the customer's `ignoreUrls`.\n */\n ignoreUrls: (string | RegExp)[];\n /**\n * Customer-supplied URL patterns the fetch + XHR instrumentations\n * inject `traceparent` + `baggage` headers on. Same-origin requests\n * always propagate.\n */\n propagateContextUrls?: (string | RegExp)[];\n /**\n * Pathname → low-cardinality route template (e.g. `/blog/[slug]`).\n * Drives span renaming + `url.path` / `http.route` attrs across\n * fetch, document-load, user-interaction, and long-task.\n */\n resolveRoute?: (pathname: string) => string | undefined;\n /**\n * The kernel's private provider — instrumentations register against\n * it, not the global one, so customer OTel setups stay untouched.\n */\n tracerProvider: WebTracerProvider;\n}\n\n/**\n * Registers every browser auto-instrumentation against the kernel's\n * private provider. Every instrumentation is enriched with the same\n * route-template-aware URL attributes so dashboards can slice fetch /\n * interaction / long-task / resource-load by `http.route` without\n * cardinality blow-up from raw pathnames.\n *\n * Returns a disposer that unregisters everything; called by\n * `kernel.dispose()`.\n */\nexport function registerBundledInstrumentations(\n input: InstrumentationsInput\n): () => void {\n const { tracerProvider, ignoreUrls, propagateContextUrls = [] } = input;\n const resolveRoute = input.resolveRoute;\n const origin = sameOriginPattern();\n const fetchIgnore = [...ignoreUrls, ...THIRD_PARTY_IGNORE];\n\n return registerInstrumentations({\n tracerProvider,\n instrumentations: [\n new FetchInstrumentation({\n propagateTraceHeaderCorsUrls: [origin, ...propagateContextUrls],\n ignoreUrls: fetchIgnore,\n semconvStabilityOptIn: SEMCONV_STABILITY_OPT_IN,\n measureRequestSize: true,\n applyCustomAttributesOnSpan: (\n span: Span,\n request: Request | RequestInit\n ) => {\n const url =\n request instanceof Request ? request.url : request?.toString();\n if (typeof url !== \"string\") {\n return;\n }\n try {\n const parsed = new URL(url);\n span.setAttribute(ATTR_URL_PATH, parsed.pathname);\n const isTracked =\n origin.test(url) ||\n propagateContextUrls.some((re) =>\n re instanceof RegExp ? re.test(url) : url.includes(re)\n );\n if (isTracked) {\n const method =\n (request instanceof Request ? request.method : undefined) ??\n \"GET\";\n const route = resolveRoute?.(parsed.pathname);\n if (route) {\n span.setAttribute(ATTR_HTTP_ROUTE, route);\n }\n span.updateName(`${method} ${route ?? parsed.pathname}`);\n }\n } catch {\n return;\n }\n },\n }),\n new XMLHttpRequestInstrumentation({\n propagateTraceHeaderCorsUrls: [origin, ...propagateContextUrls],\n ignoreUrls: fetchIgnore,\n semconvStabilityOptIn: SEMCONV_STABILITY_OPT_IN,\n }),\n new DocumentLoadInstrumentation({\n semconvStabilityOptIn: SEMCONV_STABILITY_OPT_IN,\n // Default `resourceFetch` span name is the literal string\n // `\"resourceFetch\"` for every entry — useless when a single\n // page load emits 30 of them. Replace with the pathname (or\n // origin+path for cross-origin) so the waterfall is scannable.\n applyCustomAttributesOnSpan: {\n resourceFetch: (span, entry) => {\n const name = entry?.name;\n if (typeof name !== \"string\") {\n return;\n }\n try {\n const url = new URL(name);\n const isCrossOrigin =\n typeof window !== \"undefined\" &&\n url.origin !== window.location.origin;\n span.updateName(\n isCrossOrigin\n ? `resource: ${url.host}${url.pathname}`\n : `resource: ${url.pathname}`\n );\n span.setAttribute(ATTR_URL_FULL, name);\n span.setAttribute(ATTR_URL_PATH, url.pathname);\n span.setAttribute(\"server.address\", url.host);\n } catch {\n // Malformed/relative URL — leave the default name and\n // let the entry's own attributes carry whatever info\n // the SDK already stamped.\n }\n },\n },\n }),\n // `BrowserNavigationInstrumentation` emits a `browser.navigation`\n // log record per hard navigation (page load) and soft navigation\n // (history.pushState, hash change, back/forward). The default\n // sanitizer redacts credentials in the URL and common sensitive\n // query params (`token`, `api_key`, `secret`, …) before emit.\n new BrowserNavigationInstrumentation({\n sanitizeUrl: defaultSanitizeUrl,\n }),\n new UserInteractionInstrumentation({\n eventNames: [\"click\", \"submit\"],\n // Despite the name, `shouldPreventSpanCreation` is also the\n // hook for *enriching* spans (return falsy → create the span;\n // the callback may set attributes on the way through). We use\n // it as both: stamp a stable target descriptor for\n // dashboarding, then suppress spans on elements that aren't\n // actionable (scroll containers, body clicks, etc.).\n shouldPreventSpanCreation: (eventType, element, span) => {\n if (eventType === \"click\" && !isActionable(element)) {\n return true;\n }\n stampInteractionAttrs(eventType, element, span);\n stampUrlAttrs(span, resolveRoute);\n return false;\n },\n }),\n // Long-task spans default to a generic `longtask` name with\n // attribution embedded as attributes — but no page context.\n // Empty `url.path` makes it impossible to triage which routes\n // are jank-heavy. Stamp it in the observer callback so\n // dashboards can slice by route.\n new LongTaskInstrumentation({\n observerCallback: (span, info: ObserverCallbackInformation) => {\n stampUrlAttrs(span, resolveRoute);\n // PerformanceLongTaskTiming attribution[0].containerType is\n // the surface that hosted the offending task. Most are\n // `\"window\"` (main frame); the few that aren't (`\"iframe\"`,\n // `\"embed\"`, `\"object\"`) are the actionable ones — surface\n // them as a top-level attr instead of leaving them buried\n // in `longtask.attribution.container_type`.\n const containerType =\n info.longtaskEntry.attribution[0]?.containerType;\n if (containerType) {\n span.setAttribute(\"longtask.container_type\", containerType);\n }\n },\n }),\n ],\n });\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-scope-context-manager.d.mts","names":[],"sources":["../../../src/internal/otel/page-scope-context-manager.ts"],"mappings":";;;;;;AAyBA
|
|
1
|
+
{"version":3,"file":"page-scope-context-manager.d.mts","names":[],"sources":["../../../src/internal/otel/page-scope-context-manager.ts"],"mappings":";;;;;;AAyBA;;;;;;;;;;;;;;;;AAO4B;;;cAPf,uBAAA,SAAgC,kBAAA;EAAA,QACnC,SAAA;EAER,YAAA,CAAa,GAAA,EAAK,OAAA;EAIT,MAAA,CAAA,GAAU,OAAA;AAAA"}
|
|
@@ -1,36 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ZoneContextManager } from "@opentelemetry/context-zone";
|
|
3
|
-
//#region src/internal/otel/page-scope-context-manager.ts
|
|
4
|
-
/**
|
|
5
|
-
* `ZoneContextManager` returns `ROOT_CONTEXT` whenever `context.active()` is
|
|
6
|
-
* called outside a zone the manager itself created — which in practice is
|
|
7
|
-
* most of the time:
|
|
8
|
-
*
|
|
9
|
-
* - app code calling `fetch(...)` from a `useEffect`
|
|
10
|
-
* - `PerformanceObserver` callbacks (long-task, paint timing, …)
|
|
11
|
-
* - top-level await / module init code
|
|
12
|
-
* - listeners attached before the SDK booted
|
|
13
|
-
*
|
|
14
|
-
* Spans created from those code paths default to "no parent" → root, which
|
|
15
|
-
* is why a single page load produces N disjoint traces (one per
|
|
16
|
-
* instrumentation) instead of one waterfall.
|
|
17
|
-
*
|
|
18
|
-
* This subclass adds a single fallback: if the underlying zone lookup yields
|
|
19
|
-
* `ROOT_CONTEXT`, return the page-scope context instead. The page-scope
|
|
20
|
-
* context is built once at SDK init from the `<meta name="traceparent">`
|
|
21
|
-
* tag emitted by the SSR layout. If the meta tag is absent the page scope
|
|
22
|
-
* stays at `ROOT_CONTEXT` and the manager behaves like a stock
|
|
23
|
-
* `ZoneContextManager`.
|
|
24
|
-
*/
|
|
25
|
-
var PageScopeContextManager = class extends ZoneContextManager {
|
|
26
|
-
pageScope = ROOT_CONTEXT;
|
|
27
|
-
setPageScope(ctx) {
|
|
28
|
-
this.pageScope = ctx;
|
|
29
|
-
}
|
|
30
|
-
active() {
|
|
31
|
-
const ctx = super.active();
|
|
32
|
-
return ctx === ROOT_CONTEXT ? this.pageScope : ctx;
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
//#endregion
|
|
36
|
-
export { PageScopeContextManager };
|
|
1
|
+
import{ROOT_CONTEXT}from"@opentelemetry/api";import{ZoneContextManager}from"@opentelemetry/context-zone";var PageScopeContextManager=class extends ZoneContextManager{pageScope=ROOT_CONTEXT;setPageScope(ctx){this.pageScope=ctx}active(){let ctx=super.active();return ctx===ROOT_CONTEXT?this.pageScope:ctx}};export{PageScopeContextManager};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-scope-context-manager.mjs","names":[],"sources":["../../../src/internal/otel/page-scope-context-manager.ts"],"sourcesContent":["import type { Context } from \"@opentelemetry/api\";\nimport { ROOT_CONTEXT } from \"@opentelemetry/api\";\nimport { ZoneContextManager } from \"@opentelemetry/context-zone\";\n\n/**\n * `ZoneContextManager` returns `ROOT_CONTEXT` whenever `context.active()` is\n * called outside a zone the manager itself created — which in practice is\n * most of the time:\n *\n * - app code calling `fetch(...)` from a `useEffect`\n * - `PerformanceObserver` callbacks (long-task, paint timing, …)\n * - top-level await / module init code\n * - listeners attached before the SDK booted\n *\n * Spans created from those code paths default to \"no parent\" → root, which\n * is why a single page load produces N disjoint traces (one per\n * instrumentation) instead of one waterfall.\n *\n * This subclass adds a single fallback: if the underlying zone lookup yields\n * `ROOT_CONTEXT`, return the page-scope context instead. The page-scope\n * context is built once at SDK init from the `<meta name=\"traceparent\">`\n * tag emitted by the SSR layout. If the meta tag is absent the page scope\n * stays at `ROOT_CONTEXT` and the manager behaves like a stock\n * `ZoneContextManager`.\n */\nexport class PageScopeContextManager extends ZoneContextManager {\n private pageScope: Context = ROOT_CONTEXT;\n\n setPageScope(ctx: Context): void {\n this.pageScope = ctx;\n }\n\n override active(): Context {\n const ctx = super.active();\n return ctx === ROOT_CONTEXT ? this.pageScope : ctx;\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"page-scope-context-manager.mjs","names":[],"sources":["../../../src/internal/otel/page-scope-context-manager.ts"],"sourcesContent":["import type { Context } from \"@opentelemetry/api\";\nimport { ROOT_CONTEXT } from \"@opentelemetry/api\";\nimport { ZoneContextManager } from \"@opentelemetry/context-zone\";\n\n/**\n * `ZoneContextManager` returns `ROOT_CONTEXT` whenever `context.active()` is\n * called outside a zone the manager itself created — which in practice is\n * most of the time:\n *\n * - app code calling `fetch(...)` from a `useEffect`\n * - `PerformanceObserver` callbacks (long-task, paint timing, …)\n * - top-level await / module init code\n * - listeners attached before the SDK booted\n *\n * Spans created from those code paths default to \"no parent\" → root, which\n * is why a single page load produces N disjoint traces (one per\n * instrumentation) instead of one waterfall.\n *\n * This subclass adds a single fallback: if the underlying zone lookup yields\n * `ROOT_CONTEXT`, return the page-scope context instead. The page-scope\n * context is built once at SDK init from the `<meta name=\"traceparent\">`\n * tag emitted by the SSR layout. If the meta tag is absent the page scope\n * stays at `ROOT_CONTEXT` and the manager behaves like a stock\n * `ZoneContextManager`.\n */\nexport class PageScopeContextManager extends ZoneContextManager {\n private pageScope: Context = ROOT_CONTEXT;\n\n setPageScope(ctx: Context): void {\n this.pageScope = ctx;\n }\n\n override active(): Context {\n const ctx = super.active();\n return ctx === ROOT_CONTEXT ? this.pageScope : ctx;\n }\n}\n"],"mappings":"yGAyBA,IAAa,wBAAb,cAA6C,kBAAmB,CAC9D,UAA6B,aAE7B,aAAa,IAAoB,CAC/B,KAAK,UAAY,GACnB,CAEA,QAA2B,CACzB,IAAM,IAAM,MAAM,OAAO,EACzB,OAAO,MAAQ,aAAe,KAAK,UAAY,GACjD,CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"propagation.d.mts","names":[],"sources":["../../../src/internal/otel/propagation.ts"],"mappings":";;;;;AAuCA
|
|
1
|
+
{"version":3,"file":"propagation.d.mts","names":[],"sources":["../../../src/internal/otel/propagation.ts"],"mappings":";;;;;AAuCA;;;;AAAsD;;;;;;;;;iBAAtC,2BAAA,CAAA,GAA+B,OAAO"}
|
|
@@ -1,40 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { W3CTraceContextPropagator } from "@opentelemetry/core";
|
|
3
|
-
//#region src/internal/otel/propagation.ts
|
|
4
|
-
const TRACEPARENT_META = "traceparent";
|
|
5
|
-
const TRACESTATE_META = "tracestate";
|
|
6
|
-
const INVALID_TRACE_ID = "00000000000000000000000000000000";
|
|
7
|
-
const TRACE_FLAGS_SAMPLED = 1;
|
|
8
|
-
const W3C_PROPAGATOR = new W3CTraceContextPropagator();
|
|
9
|
-
function readMeta(name) {
|
|
10
|
-
if (typeof document === "undefined") return null;
|
|
11
|
-
return document.querySelector(`meta[name="${name}"]`)?.getAttribute("content") ?? null;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Reads the W3C `traceparent` (and optional `tracestate`) meta tag from the
|
|
15
|
-
* document head and extracts an OTel `Context`. SSR renderers (Next, Vite,
|
|
16
|
-
* …) inject the server-side request span's context so the client SDK can
|
|
17
|
-
* stitch its spans onto the same trace.
|
|
18
|
-
*
|
|
19
|
-
* Returns `null` when:
|
|
20
|
-
* - called server-side (no `document`)
|
|
21
|
-
* - no `traceparent` meta tag is present
|
|
22
|
-
* - the extracted context has an invalid trace id
|
|
23
|
-
* - the `sampled` flag is unset — the browser doesn't honor the bit when
|
|
24
|
-
* creating child spans, so an unsampled parent would orphan every
|
|
25
|
-
* browser span under a trace with no recorded server segments. Drop
|
|
26
|
-
* instead so spans fall back to a fresh root.
|
|
27
|
-
*/
|
|
28
|
-
function readPropagationFromDocument() {
|
|
29
|
-
const traceparent = readMeta(TRACEPARENT_META);
|
|
30
|
-
if (!traceparent) return null;
|
|
31
|
-
const carrier = { traceparent };
|
|
32
|
-
const tracestate = readMeta(TRACESTATE_META);
|
|
33
|
-
if (tracestate) carrier[TRACESTATE_META] = tracestate;
|
|
34
|
-
const extracted = W3C_PROPAGATOR.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
|
|
35
|
-
const spanCtx = trace.getSpanContext(extracted);
|
|
36
|
-
if (!spanCtx || spanCtx.traceId === INVALID_TRACE_ID || (spanCtx.traceFlags & TRACE_FLAGS_SAMPLED) === 0) return null;
|
|
37
|
-
return extracted;
|
|
38
|
-
}
|
|
39
|
-
//#endregion
|
|
40
|
-
export { readPropagationFromDocument };
|
|
1
|
+
import{ROOT_CONTEXT,defaultTextMapGetter,trace}from"@opentelemetry/api";import{W3CTraceContextPropagator}from"@opentelemetry/core";const TRACESTATE_META=`tracestate`,W3C_PROPAGATOR=new W3CTraceContextPropagator;function readMeta(name){return typeof document>`u`?null:document.querySelector(`meta[name="${name}"]`)?.getAttribute(`content`)??null}function readPropagationFromDocument(){let traceparent=readMeta(`traceparent`);if(!traceparent)return null;let carrier={traceparent},tracestate=readMeta(TRACESTATE_META);tracestate&&(carrier[TRACESTATE_META]=tracestate);let extracted=W3C_PROPAGATOR.extract(ROOT_CONTEXT,carrier,defaultTextMapGetter),spanCtx=trace.getSpanContext(extracted);return!spanCtx||spanCtx.traceId===`00000000000000000000000000000000`||!(spanCtx.traceFlags&1)?null:extracted}export{readPropagationFromDocument};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"propagation.mjs","names":[],"sources":["../../../src/internal/otel/propagation.ts"],"sourcesContent":["import {\n type Context,\n defaultTextMapGetter,\n ROOT_CONTEXT,\n trace,\n} from \"@opentelemetry/api\";\nimport { W3CTraceContextPropagator } from \"@opentelemetry/core\";\n\nconst TRACEPARENT_META = \"traceparent\";\nconst TRACESTATE_META = \"tracestate\";\nconst INVALID_TRACE_ID = \"00000000000000000000000000000000\";\nconst TRACE_FLAGS_SAMPLED = 0x1;\n// Local instance — extraction must not depend on whether the customer (or\n// our own bootstrap) set a global propagator.\nconst W3C_PROPAGATOR = new W3CTraceContextPropagator();\n\nfunction readMeta(name: string): string | null {\n if (typeof document === \"undefined\") {\n return null;\n }\n const el = document.querySelector(`meta[name=\"${name}\"]`);\n return el?.getAttribute(\"content\") ?? null;\n}\n\n/**\n * Reads the W3C `traceparent` (and optional `tracestate`) meta tag from the\n * document head and extracts an OTel `Context`. SSR renderers (Next, Vite,\n * …) inject the server-side request span's context so the client SDK can\n * stitch its spans onto the same trace.\n *\n * Returns `null` when:\n * - called server-side (no `document`)\n * - no `traceparent` meta tag is present\n * - the extracted context has an invalid trace id\n * - the `sampled` flag is unset — the browser doesn't honor the bit when\n * creating child spans, so an unsampled parent would orphan every\n * browser span under a trace with no recorded server segments. Drop\n * instead so spans fall back to a fresh root.\n */\nexport function readPropagationFromDocument(): Context | null {\n const traceparent = readMeta(TRACEPARENT_META);\n if (!traceparent) {\n return null;\n }\n\n const carrier: Record<string, string> = { traceparent };\n const tracestate = readMeta(TRACESTATE_META);\n if (tracestate) {\n carrier[TRACESTATE_META] = tracestate;\n }\n\n const extracted = W3C_PROPAGATOR.extract(\n ROOT_CONTEXT,\n carrier,\n defaultTextMapGetter\n );\n const spanCtx = trace.getSpanContext(extracted);\n // octet of flag bits per the W3C trace-context spec; bit 0 is the sampled\n // bit. Bitwise AND is the canonical extraction.\n if (\n !spanCtx ||\n spanCtx.traceId === INVALID_TRACE_ID ||\n // biome-ignore lint/suspicious/noBitwiseOperators: traceFlags is a single\n (spanCtx.traceFlags & TRACE_FLAGS_SAMPLED) === 0\n ) {\n return null;\n }\n return extracted;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"propagation.mjs","names":[],"sources":["../../../src/internal/otel/propagation.ts"],"sourcesContent":["import {\n type Context,\n defaultTextMapGetter,\n ROOT_CONTEXT,\n trace,\n} from \"@opentelemetry/api\";\nimport { W3CTraceContextPropagator } from \"@opentelemetry/core\";\n\nconst TRACEPARENT_META = \"traceparent\";\nconst TRACESTATE_META = \"tracestate\";\nconst INVALID_TRACE_ID = \"00000000000000000000000000000000\";\nconst TRACE_FLAGS_SAMPLED = 0x1;\n// Local instance — extraction must not depend on whether the customer (or\n// our own bootstrap) set a global propagator.\nconst W3C_PROPAGATOR = new W3CTraceContextPropagator();\n\nfunction readMeta(name: string): string | null {\n if (typeof document === \"undefined\") {\n return null;\n }\n const el = document.querySelector(`meta[name=\"${name}\"]`);\n return el?.getAttribute(\"content\") ?? null;\n}\n\n/**\n * Reads the W3C `traceparent` (and optional `tracestate`) meta tag from the\n * document head and extracts an OTel `Context`. SSR renderers (Next, Vite,\n * …) inject the server-side request span's context so the client SDK can\n * stitch its spans onto the same trace.\n *\n * Returns `null` when:\n * - called server-side (no `document`)\n * - no `traceparent` meta tag is present\n * - the extracted context has an invalid trace id\n * - the `sampled` flag is unset — the browser doesn't honor the bit when\n * creating child spans, so an unsampled parent would orphan every\n * browser span under a trace with no recorded server segments. Drop\n * instead so spans fall back to a fresh root.\n */\nexport function readPropagationFromDocument(): Context | null {\n const traceparent = readMeta(TRACEPARENT_META);\n if (!traceparent) {\n return null;\n }\n\n const carrier: Record<string, string> = { traceparent };\n const tracestate = readMeta(TRACESTATE_META);\n if (tracestate) {\n carrier[TRACESTATE_META] = tracestate;\n }\n\n const extracted = W3C_PROPAGATOR.extract(\n ROOT_CONTEXT,\n carrier,\n defaultTextMapGetter\n );\n const spanCtx = trace.getSpanContext(extracted);\n // octet of flag bits per the W3C trace-context spec; bit 0 is the sampled\n // bit. Bitwise AND is the canonical extraction.\n if (\n !spanCtx ||\n spanCtx.traceId === INVALID_TRACE_ID ||\n // biome-ignore lint/suspicious/noBitwiseOperators: traceFlags is a single\n (spanCtx.traceFlags & TRACE_FLAGS_SAMPLED) === 0\n ) {\n return null;\n }\n return extracted;\n}\n"],"mappings":"mIAQA,MACM,gBAAkB,aAKlB,eAAiB,IAAI,0BAE3B,SAAS,SAAS,KAA6B,CAK7C,OAJI,OAAO,SAAa,IACf,KAEE,SAAS,cAAc,cAAc,KAAK,GAC7C,GAAG,aAAa,SAAS,GAAK,IACxC,CAiBA,SAAgB,6BAA8C,CAC5D,IAAM,YAAc,SAAS,aAAgB,EAC7C,GAAI,CAAC,YACH,OAAO,KAGT,IAAM,QAAkC,CAAE,WAAY,EAChD,WAAa,SAAS,eAAe,EACvC,aACF,QAAQ,iBAAmB,YAG7B,IAAM,UAAY,eAAe,QAC/B,aACA,QACA,oBACF,EACM,QAAU,MAAM,eAAe,SAAS,EAW9C,MAPE,CAAC,SACD,QAAQ,UAAY,oCAAA,EAEnB,QAAQ,WAAa,GAEf,KAEF,SACT"}
|
|
@@ -40,8 +40,7 @@ interface OtelProviderInput {
|
|
|
40
40
|
/**
|
|
41
41
|
* Auth + content headers for OTLP exports. Sourced from the kernel's
|
|
42
42
|
* already-resolved ingest target so the OTLP path inherits the same
|
|
43
|
-
*
|
|
44
|
-
* uses — no separate auth knob.
|
|
43
|
+
* transport metadata the rest of the SDK uses — no separate auth knob.
|
|
45
44
|
*/
|
|
46
45
|
authHeaders: Headers;
|
|
47
46
|
/** OTLP base URL — the opaque sink path is appended by the exporters. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.mts","names":[],"sources":["../../../src/internal/otel/provider.ts"],"mappings":";;;;;;;;;UA0GiB,iBAAA;;AAAjB;;;;;;;;;EAWE,6BAAA,GAAgC,kBAAA;
|
|
1
|
+
{"version":3,"file":"provider.d.mts","names":[],"sources":["../../../src/internal/otel/provider.ts"],"mappings":";;;;;;;;;UA0GiB,iBAAA;;AAAjB;;;;;;;;;EAWE,6BAAA,GAAgC,kBAAA;EA8CP;;;;;;;;;;EAnCzB,uBAAA,GAA0B,YAAA;EAiC1B;;;;;;EA1BA,wBAAA,GAA2B,aAAA;EAuChB;AAGb;;;;EApCE,WAAA,EAAa,OAAA;EA6CG;EA3ChB,YAAA;EA6Cc;;;;;;;;;EAnCd,QAAA;EAiCgB;;;;;;;EAzBhB,YAAA,QAAoB,SAAA;EA8BJ;EA5BhB,WAAA,GAAc,WAAA;EA4BmB;AAWnC;;;;EAjCE,QAAA;EAkCA;;;AACmB;EA9BnB,WAAA;AAAA;AAAA,UAGe,kBAAA;;;;;;EAMf,cAAA,EAAgB,uBAAA;;EAEhB,KAAA,IAAS,OAAA;EACT,cAAA,EAAgB,cAAA;EAChB,aAAA,EAAe,aAAA;EACf,YAAA,EAAc,YAAA;;EAEd,QAAA,IAAY,OAAA;EACZ,cAAA,EAAgB,iBAAA;AAAA;;;;;;;;;iBAWF,iBAAA,CACd,KAAA,EAAO,iBAAA,GACN,kBAAkB"}
|
|
@@ -1,151 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { createBeaconLogExporter, createBeaconMetricExporter, createBeaconTraceExporter } from "./exporter.mjs";
|
|
3
|
-
import { PageScopeContextManager } from "./page-scope-context-manager.mjs";
|
|
4
|
-
import { propagation } from "@opentelemetry/api";
|
|
5
|
-
import { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator } from "@opentelemetry/core";
|
|
6
|
-
import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
7
|
-
import { normalizeEnv } from "@interfere/types/sdk/runtime";
|
|
8
|
-
import { browserDetector } from "@opentelemetry/opentelemetry-browser-detector";
|
|
9
|
-
import { detectResources, resourceFromAttributes } from "@opentelemetry/resources";
|
|
10
|
-
import { BatchLogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs";
|
|
11
|
-
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
12
|
-
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
|
|
13
|
-
import { createSessionLogRecordProcessor, createSessionSpanProcessor } from "@opentelemetry/web-common";
|
|
14
|
-
//#region src/internal/otel/provider.ts
|
|
15
|
-
const SERVICE_NAMESPACE = "interfere";
|
|
16
|
-
const ATTR_SERVICE_NAME = "service.name";
|
|
17
|
-
const ATTR_SERVICE_NAMESPACE = "service.namespace";
|
|
18
|
-
const ATTR_DEPLOYMENT_ENVIRONMENT_NAME = "deployment.environment.name";
|
|
19
|
-
const ATTR_TELEMETRY_SDK_LANGUAGE = "telemetry.sdk.language";
|
|
20
|
-
const ATTR_RELEASE_SLUG = "release.slug";
|
|
21
|
-
const ATTR_INTERFERE_SDK_STACK = "interfere.sdk.stack";
|
|
22
|
-
const ATTR_INTERFERE_SDK_NAME = "interfere.sdk.name";
|
|
23
|
-
const ATTR_INTERFERE_SDK_VERSION = "interfere.sdk.version";
|
|
24
|
-
const ATTR_DEVICE_ID = "device.id";
|
|
25
|
-
const DEFAULT_SERVICE_NAME = "interfere-sdk";
|
|
26
|
-
/**
|
|
27
|
-
* Tuned for `navigator.sendBeacon`'s per-call payload ceiling.
|
|
28
|
-
*
|
|
29
|
-
* The browser primary exporters dispatch every export via beacon
|
|
30
|
-
* (see `BeaconTraceExporter` / `BeaconLogExporter` /
|
|
31
|
-
* `BeaconMetricExporter` in `exporter.ts`) — sendBeacon is the only
|
|
32
|
-
* browser transport that reliably commits a request the page is also
|
|
33
|
-
* tearing down. It's not free though: most browsers cap each beacon
|
|
34
|
-
* payload at 64KiB (Chrome / Firefox) and reject anything larger
|
|
35
|
-
* with a `false` return from `navigator.sendBeacon` — at which point
|
|
36
|
-
* the BSP just drops the batch (no retry: the SW backstop intercepts
|
|
37
|
-
* 5xx / network failures, not user-agent rejections).
|
|
38
|
-
*
|
|
39
|
-
* The numbers below keep payloads comfortably under that ceiling,
|
|
40
|
-
* even on jank-heavy pages (production data on 2026-05-11 saw 831
|
|
41
|
-
* `longtask` spans piled up on a single page-hide, observed via the
|
|
42
|
-
* BetterStack dual-write) where a 100-span batch would already top
|
|
43
|
-
* 100KiB:
|
|
44
|
-
*
|
|
45
|
-
* - `maxExportBatchSize: 10` — caps each beacon at ~10–20KiB,
|
|
46
|
-
* leaving headroom for span attribute bloat without flirting
|
|
47
|
-
* with the per-call ceiling.
|
|
48
|
-
* - `maxQueueSize: 50` — caps the worst-case unload payload at
|
|
49
|
-
* ~50–100KiB; the BSP drains across 5 beacons when forced-
|
|
50
|
-
* flushed on `visibilitychange→hidden`, all of which queue
|
|
51
|
-
* synchronously inside the user agent.
|
|
52
|
-
* - `scheduledDelayMillis: 2000` — quieter pages keep less in
|
|
53
|
-
* buffer at any moment, so a hidden→visible→hidden bounce
|
|
54
|
-
* doesn't accumulate 5s of telemetry waiting to ship.
|
|
55
|
-
* - `exportTimeoutMillis: 10_000` — unchanged. Beacon dispatches
|
|
56
|
-
* are essentially synchronous (the user agent enqueues into its
|
|
57
|
-
* own send loop), so the timeout is mostly defensive against
|
|
58
|
-
* pathological `JsonTraceSerializer.serializeRequest` calls.
|
|
59
|
-
*/
|
|
60
|
-
const BROWSER_BATCH_OPTIONS = {
|
|
61
|
-
maxQueueSize: 50,
|
|
62
|
-
maxExportBatchSize: 10,
|
|
63
|
-
scheduledDelayMillis: 2e3,
|
|
64
|
-
exportTimeoutMillis: 1e4
|
|
65
|
-
};
|
|
66
|
-
/**
|
|
67
|
-
* Constructs the SDK's **private** OTel providers — never registers globally
|
|
68
|
-
* via `provider.register()` or `trace.setGlobalTracerProvider()`. Customers
|
|
69
|
-
* with their own OTel setup (DataDog, Vercel, etc.) cohabit cleanly.
|
|
70
|
-
*
|
|
71
|
-
* Splitting this out from `Kernel` keeps the OTel module lazy-loadable so
|
|
72
|
-
* the error-only bundle path (no tracing) never imports the OTel SDK.
|
|
73
|
-
*/
|
|
74
|
-
function buildOtelProvider(input) {
|
|
75
|
-
const environment = normalizeEnv(typeof process === "undefined" ? void 0 : process.env["NODE_ENV"]) ?? "unknown";
|
|
76
|
-
const baseResource = resourceFromAttributes({
|
|
77
|
-
[ATTR_SERVICE_NAME]: input.serviceName ?? DEFAULT_SERVICE_NAME,
|
|
78
|
-
[ATTR_SERVICE_NAMESPACE]: SERVICE_NAMESPACE,
|
|
79
|
-
[ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: environment,
|
|
80
|
-
[ATTR_TELEMETRY_SDK_LANGUAGE]: "webjs",
|
|
81
|
-
[ATTR_INTERFERE_SDK_NAME]: SDK_NAME,
|
|
82
|
-
[ATTR_INTERFERE_SDK_VERSION]: SDK_VERSION,
|
|
83
|
-
[ATTR_INTERFERE_SDK_STACK]: input.sdkStack.join(", "),
|
|
84
|
-
...input.releaseSlug ? { [ATTR_RELEASE_SLUG]: input.releaseSlug } : {},
|
|
85
|
-
...input.deviceId ? { [ATTR_DEVICE_ID]: input.deviceId } : {}
|
|
86
|
-
});
|
|
87
|
-
const detected = detectResources({ detectors: [browserDetector] });
|
|
88
|
-
const resource = baseResource.merge(detected);
|
|
89
|
-
const traceExporter = createBeaconTraceExporter({
|
|
90
|
-
collectorUrl: input.collectorUrl,
|
|
91
|
-
authHeaders: input.authHeaders
|
|
92
|
-
});
|
|
93
|
-
const getSessionId = input.getSessionId;
|
|
94
|
-
const tracerProvider = new WebTracerProvider({
|
|
95
|
-
resource,
|
|
96
|
-
spanProcessors: [
|
|
97
|
-
createSessionSpanProcessor({ getSessionId }),
|
|
98
|
-
new BatchSpanProcessor(traceExporter, BROWSER_BATCH_OPTIONS),
|
|
99
|
-
...input.additionalSpanProcessors ?? []
|
|
100
|
-
]
|
|
101
|
-
});
|
|
102
|
-
const contextManager = new PageScopeContextManager();
|
|
103
|
-
tracerProvider.register({ contextManager });
|
|
104
|
-
if (propagation.fields().length === 0) propagation.setGlobalPropagator(new CompositePropagator({ propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()] }));
|
|
105
|
-
const metricReader = new PeriodicExportingMetricReader({
|
|
106
|
-
exporter: createBeaconMetricExporter({
|
|
107
|
-
collectorUrl: input.collectorUrl,
|
|
108
|
-
authHeaders: input.authHeaders
|
|
109
|
-
}),
|
|
110
|
-
exportIntervalMillis: 3e4
|
|
111
|
-
});
|
|
112
|
-
const meterProvider = new MeterProvider({
|
|
113
|
-
resource,
|
|
114
|
-
readers: [metricReader, ...input.additionalMetricReaders ?? []]
|
|
115
|
-
});
|
|
116
|
-
const logExporter = createBeaconLogExporter({
|
|
117
|
-
collectorUrl: input.collectorUrl,
|
|
118
|
-
authHeaders: input.authHeaders
|
|
119
|
-
});
|
|
120
|
-
const loggerProvider = new LoggerProvider({
|
|
121
|
-
resource,
|
|
122
|
-
processors: [
|
|
123
|
-
createSessionLogRecordProcessor({ getSessionId }),
|
|
124
|
-
new BatchLogRecordProcessor(logExporter, BROWSER_BATCH_OPTIONS),
|
|
125
|
-
...input.additionalLogRecordProcessors ?? []
|
|
126
|
-
]
|
|
127
|
-
});
|
|
128
|
-
return {
|
|
129
|
-
contextManager,
|
|
130
|
-
tracerProvider,
|
|
131
|
-
meterProvider,
|
|
132
|
-
metricReader,
|
|
133
|
-
loggerProvider,
|
|
134
|
-
flush: async () => {
|
|
135
|
-
await Promise.all([
|
|
136
|
-
tracerProvider.forceFlush(),
|
|
137
|
-
metricReader.forceFlush(),
|
|
138
|
-
loggerProvider.forceFlush()
|
|
139
|
-
]);
|
|
140
|
-
},
|
|
141
|
-
shutdown: async () => {
|
|
142
|
-
await Promise.all([
|
|
143
|
-
tracerProvider.shutdown(),
|
|
144
|
-
meterProvider.shutdown(),
|
|
145
|
-
loggerProvider.shutdown()
|
|
146
|
-
]);
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
//#endregion
|
|
151
|
-
export { buildOtelProvider };
|
|
1
|
+
import{SDK_NAME,SDK_VERSION}from"../version.mjs";import{createBeaconLogExporter,createBeaconMetricExporter,createBeaconTraceExporter}from"./exporter.mjs";import{PageScopeContextManager}from"./page-scope-context-manager.mjs";import{propagation}from"@opentelemetry/api";import{CompositePropagator,W3CBaggagePropagator,W3CTraceContextPropagator}from"@opentelemetry/core";import{MeterProvider,PeriodicExportingMetricReader}from"@opentelemetry/sdk-metrics";import{normalizeEnv}from"@interfere/types/sdk/runtime";import{browserDetector}from"@opentelemetry/opentelemetry-browser-detector";import{detectResources,resourceFromAttributes}from"@opentelemetry/resources";import{BatchLogRecordProcessor,LoggerProvider}from"@opentelemetry/sdk-logs";import{BatchSpanProcessor}from"@opentelemetry/sdk-trace-base";import{WebTracerProvider}from"@opentelemetry/sdk-trace-web";import{createSessionLogRecordProcessor,createSessionSpanProcessor}from"@opentelemetry/web-common";const BROWSER_BATCH_OPTIONS={maxQueueSize:50,maxExportBatchSize:10,scheduledDelayMillis:2e3,exportTimeoutMillis:1e4};function buildOtelProvider(input){let environment=normalizeEnv(typeof process>`u`?void 0:process.env.NODE_ENV)??`unknown`,baseResource=resourceFromAttributes({"service.name":input.serviceName??`interfere-sdk`,"service.namespace":`interfere`,"deployment.environment.name":environment,"telemetry.sdk.language":`webjs`,"interfere.sdk.name":SDK_NAME,"interfere.sdk.version":SDK_VERSION,"interfere.sdk.stack":input.sdkStack.join(`, `),...input.releaseSlug?{"release.slug":input.releaseSlug}:{},...input.deviceId?{"device.id":input.deviceId}:{}}),detected=detectResources({detectors:[browserDetector]}),resource=baseResource.merge(detected),traceExporter=createBeaconTraceExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),getSessionId=input.getSessionId,tracerProvider=new WebTracerProvider({resource,spanProcessors:[createSessionSpanProcessor({getSessionId}),new BatchSpanProcessor(traceExporter,BROWSER_BATCH_OPTIONS),...input.additionalSpanProcessors??[]]}),contextManager=new PageScopeContextManager;tracerProvider.register({contextManager}),propagation.fields().length===0&&propagation.setGlobalPropagator(new CompositePropagator({propagators:[new W3CTraceContextPropagator,new W3CBaggagePropagator]}));let metricReader=new PeriodicExportingMetricReader({exporter:createBeaconMetricExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),exportIntervalMillis:3e4}),meterProvider=new MeterProvider({resource,readers:[metricReader,...input.additionalMetricReaders??[]]}),logExporter=createBeaconLogExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),loggerProvider=new LoggerProvider({resource,processors:[createSessionLogRecordProcessor({getSessionId}),new BatchLogRecordProcessor(logExporter,BROWSER_BATCH_OPTIONS),...input.additionalLogRecordProcessors??[]]});return{contextManager,tracerProvider,meterProvider,metricReader,loggerProvider,flush:async()=>{await Promise.all([tracerProvider.forceFlush(),metricReader.forceFlush(),loggerProvider.forceFlush()])},shutdown:async()=>{await Promise.all([tracerProvider.shutdown(),meterProvider.shutdown(),loggerProvider.shutdown()])}}}export{buildOtelProvider};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.mjs","names":[],"sources":["../../../src/internal/otel/provider.ts"],"sourcesContent":["import type { SessionId } from \"@interfere/types/data/session\";\nimport type { ReleaseSlug } from \"@interfere/types/releases/slug\";\nimport { normalizeEnv } from \"@interfere/types/sdk/runtime\";\n\nimport { propagation } from \"@opentelemetry/api\";\nimport {\n CompositePropagator,\n W3CBaggagePropagator,\n W3CTraceContextPropagator,\n} from \"@opentelemetry/core\";\nimport { browserDetector } from \"@opentelemetry/opentelemetry-browser-detector\";\nimport {\n detectResources,\n resourceFromAttributes,\n} from \"@opentelemetry/resources\";\nimport {\n BatchLogRecordProcessor,\n LoggerProvider,\n type LogRecordProcessor,\n} from \"@opentelemetry/sdk-logs\";\nimport {\n MeterProvider,\n type MetricReader,\n PeriodicExportingMetricReader,\n} from \"@opentelemetry/sdk-metrics\";\nimport {\n BatchSpanProcessor,\n type SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport { WebTracerProvider } from \"@opentelemetry/sdk-trace-web\";\nimport {\n createSessionLogRecordProcessor,\n createSessionSpanProcessor,\n} from \"@opentelemetry/web-common\";\n\nimport { SDK_NAME, SDK_VERSION } from \"../version.js\";\nimport {\n createBeaconLogExporter,\n createBeaconMetricExporter,\n createBeaconTraceExporter,\n} from \"./exporter.js\";\nimport { PageScopeContextManager } from \"./page-scope-context-manager.js\";\n\nconst SERVICE_NAMESPACE = \"interfere\";\n\n// Inlined wire keys — matches the pattern in\n// `@interfere/observability/browser/rum` (semconv keeps shifting these\n// between minor versions, but the wire keys themselves are stable).\nconst ATTR_SERVICE_NAME = \"service.name\" as const;\nconst ATTR_SERVICE_NAMESPACE = \"service.namespace\" as const;\nconst ATTR_DEPLOYMENT_ENVIRONMENT_NAME = \"deployment.environment.name\" as const;\nconst ATTR_TELEMETRY_SDK_LANGUAGE = \"telemetry.sdk.language\" as const;\nconst ATTR_RELEASE_SLUG = \"release.slug\" as const;\nconst ATTR_INTERFERE_SDK_STACK = \"interfere.sdk.stack\" as const;\nconst ATTR_INTERFERE_SDK_NAME = \"interfere.sdk.name\" as const;\nconst ATTR_INTERFERE_SDK_VERSION = \"interfere.sdk.version\" as const;\n// OTel semconv-canonical name for a stable per-device identifier.\n// Stamped as a *resource* attribute (not per-span) because it's stable\n// for the lifetime of the page; sessions rotate, devices don't. The\n// collector reads this off `context.resourceAttributes[\"device.id\"]`\n// and projects it into `spans.deviceId`.\nconst ATTR_DEVICE_ID = \"device.id\" as const;\n\nconst DEFAULT_SERVICE_NAME = \"interfere-sdk\";\n\n/**\n * Tuned for `navigator.sendBeacon`'s per-call payload ceiling.\n *\n * The browser primary exporters dispatch every export via beacon\n * (see `BeaconTraceExporter` / `BeaconLogExporter` /\n * `BeaconMetricExporter` in `exporter.ts`) — sendBeacon is the only\n * browser transport that reliably commits a request the page is also\n * tearing down. It's not free though: most browsers cap each beacon\n * payload at 64KiB (Chrome / Firefox) and reject anything larger\n * with a `false` return from `navigator.sendBeacon` — at which point\n * the BSP just drops the batch (no retry: the SW backstop intercepts\n * 5xx / network failures, not user-agent rejections).\n *\n * The numbers below keep payloads comfortably under that ceiling,\n * even on jank-heavy pages (production data on 2026-05-11 saw 831\n * `longtask` spans piled up on a single page-hide, observed via the\n * BetterStack dual-write) where a 100-span batch would already top\n * 100KiB:\n *\n * - `maxExportBatchSize: 10` — caps each beacon at ~10–20KiB,\n * leaving headroom for span attribute bloat without flirting\n * with the per-call ceiling.\n * - `maxQueueSize: 50` — caps the worst-case unload payload at\n * ~50–100KiB; the BSP drains across 5 beacons when forced-\n * flushed on `visibilitychange→hidden`, all of which queue\n * synchronously inside the user agent.\n * - `scheduledDelayMillis: 2000` — quieter pages keep less in\n * buffer at any moment, so a hidden→visible→hidden bounce\n * doesn't accumulate 5s of telemetry waiting to ship.\n * - `exportTimeoutMillis: 10_000` — unchanged. Beacon dispatches\n * are essentially synchronous (the user agent enqueues into its\n * own send loop), so the timeout is mostly defensive against\n * pathological `JsonTraceSerializer.serializeRequest` calls.\n */\nconst BROWSER_BATCH_OPTIONS = {\n maxQueueSize: 50,\n maxExportBatchSize: 10,\n scheduledDelayMillis: 2000,\n exportTimeoutMillis: 10_000,\n} as const;\n\nexport interface OtelProviderInput {\n /**\n * @internal\n *\n * Extra log-record processors fanned into the LoggerProvider's\n * processor list at construction. Used by `@interfere/observability`\n * for internal-only dual-write to BetterStack — keeps a parallel\n * destination's queue/exporter independent of the SDK's own.\n * Customers don't get a fan-out hook on the SDK surface; this is a\n * private bridge for our own dogfood apps.\n */\n additionalLogRecordProcessors?: LogRecordProcessor[];\n /**\n * @internal\n *\n * Extra metric readers fanned into the MeterProvider's reader list\n * at construction. Used by `@interfere/observability` for internal-\n * only dual-write of web vitals histograms (`web_vitals.{ttfb,fcp,\n * lcp,inp,cls}`) to the BetterStack-fronting OTel collector. Each\n * reader owns its own queue + exporter so a 5xx / network outage on\n * the parallel destination backs off only its own batch.\n */\n additionalMetricReaders?: MetricReader[];\n /**\n * @internal\n *\n * Extra span processors fanned into the WebTracerProvider's processor\n * list at construction. See `additionalLogRecordProcessors`.\n */\n additionalSpanProcessors?: SpanProcessor[];\n /**\n * Auth + content headers for OTLP exports. Sourced from the kernel's\n * already-resolved ingest target so the OTLP path inherits the same\n * `x-interfere-pub-token` (or proxy-mode auth) the rest of the SDK\n * uses — no separate auth knob.\n */\n authHeaders: Headers;\n /** OTLP base URL — the opaque sink path is appended by the exporters. */\n collectorUrl: string;\n /**\n * Stable per-device identifier from `DeviceManager`. Resolved from\n * localStorage / cookie at kernel boot, so it's synchronously\n * available before the providers are constructed. Stamped as the\n * OTel-semconv `device.id` resource attribute so every span / log /\n * metric carries it without per-record re-stamping. Optional —\n * tests and SSR boot paths that build the provider before the\n * device manager has run pass `null`.\n */\n deviceId?: string | null | undefined;\n /**\n * Returns the kernel's current session id. Wired into per-span /\n * per-log session processors so a mid-page session rotation (30-min\n * idle expiry, manual reset) lands on subsequent spans without the\n * kernel having to rebuild the providers. Returns `null` before the\n * `SessionTracker` has bootstrapped.\n */\n getSessionId: () => SessionId | null;\n /** Optional release slug — top-level `release.slug` attr (Phase 6 wire identity). */\n releaseSlug?: ReleaseSlug | null | undefined;\n /**\n * Producer chain for the `interfere.sdk.stack` resource attribute —\n * e.g. `[\"@interfere/next@10.0.0\", \"@interfere/react@10.0.0\"]`.\n * Wrapper-injected; the kernel just forwards.\n */\n sdkStack: string[];\n /**\n * Override the OTel `service.name` resource attribute. Defaults to\n * `\"interfere-sdk\"`.\n */\n serviceName?: string;\n}\n\nexport interface OtelProviderHandle {\n /**\n * Page-scope context manager. The kernel calls `setPageScope(ctx)` after\n * extracting the SSR `traceparent` meta tag so spans without a more\n * specific zone descend from the server-side parent.\n */\n contextManager: PageScopeContextManager;\n /** Force-flush all three providers — call from kernel.flush() / on visibility hidden. */\n flush(): Promise<void>;\n loggerProvider: LoggerProvider;\n meterProvider: MeterProvider;\n metricReader: MetricReader;\n /** Tear down all three providers — called from kernel.dispose(). */\n shutdown(): Promise<void>;\n tracerProvider: WebTracerProvider;\n}\n\n/**\n * Constructs the SDK's **private** OTel providers — never registers globally\n * via `provider.register()` or `trace.setGlobalTracerProvider()`. Customers\n * with their own OTel setup (DataDog, Vercel, etc.) cohabit cleanly.\n *\n * Splitting this out from `Kernel` keeps the OTel module lazy-loadable so\n * the error-only bundle path (no tracing) never imports the OTel SDK.\n */\nexport function buildOtelProvider(\n input: OtelProviderInput\n): OtelProviderHandle {\n // Static service identity merged with detected browser attrs\n // (`browser.brands`, `browser.platform`, `browser.mobile`,\n // `browser.language`, `browser.user_agent`). The browser detector\n // reads UA Client Hints synchronously via `navigator.userAgentData`\n // low-entropy properties, so this stays sync — async would race\n // `DocumentLoadInstrumentation` and consistently miss page-load.\n const environment =\n normalizeEnv(\n typeof process === \"undefined\" ? undefined : process.env[\"NODE_ENV\"]\n ) ?? \"unknown\";\n\n const baseResource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: input.serviceName ?? DEFAULT_SERVICE_NAME,\n [ATTR_SERVICE_NAMESPACE]: SERVICE_NAMESPACE,\n [ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: environment,\n [ATTR_TELEMETRY_SDK_LANGUAGE]: \"webjs\",\n [ATTR_INTERFERE_SDK_NAME]: SDK_NAME,\n [ATTR_INTERFERE_SDK_VERSION]: SDK_VERSION,\n [ATTR_INTERFERE_SDK_STACK]: input.sdkStack.join(\", \"),\n ...(input.releaseSlug ? { [ATTR_RELEASE_SLUG]: input.releaseSlug } : {}),\n ...(input.deviceId ? { [ATTR_DEVICE_ID]: input.deviceId } : {}),\n });\n const detected = detectResources({ detectors: [browserDetector] });\n const resource = baseResource.merge(detected);\n\n const traceExporter = createBeaconTraceExporter({\n collectorUrl: input.collectorUrl,\n authHeaders: input.authHeaders,\n });\n\n // The web-common session processor expects `() => string | null`,\n // matching our `KernelSession.getId` shape.\n const getSessionId = input.getSessionId;\n\n const spanProcessors: SpanProcessor[] = [\n // Stamps `session.id` per-span, re-reading on every export. A\n // mid-page session rotation (30-min idle, manual reset) lands on\n // subsequent spans without rebuilding the provider. Resource-level\n // `session.id` would freeze at boot.\n createSessionSpanProcessor({ getSessionId }),\n // Exception attribute defaults (`interfere.exception.{mechanism,\n // handled,kind}`) are no longer stamped here — third-party\n // auto-instrumentations bypass our kernel anyway, so the SDK\n // can't reliably tell what to default. Server-side enrichment\n // (`enrichment/lib/normalize-atom.ts`) is the single chokepoint\n // every exception event flows through, so the defaults live\n // there now and the SDK ships less code.\n new BatchSpanProcessor(traceExporter, BROWSER_BATCH_OPTIONS),\n // Internal-only fan-out (BetterStack dual-write for `interfere/homepage`\n // + `interfere/dashboard`). Each extra processor owns its own queue +\n // exporter, so a 5xx / network outage on the parallel destination\n // backs off only its own batch — the primary collector pipeline keeps\n // draining unaffected.\n ...(input.additionalSpanProcessors ?? []),\n ];\n\n const tracerProvider = new WebTracerProvider({ resource, spanProcessors });\n\n // `PageScopeContextManager` keeps the active context alive across\n // microtasks/promise chains/setTimeout, AND falls back to the page-scope\n // context (the SSR `traceparent`) for code paths that fire outside any\n // zone the manager itself created. The kernel populates the page scope\n // by calling `contextManager.setPageScope(ctx)` after extracting the\n // meta tag.\n //\n // `register({ contextManager })` does call `setGlobalContextManager`\n // internally — that's unavoidable; OTel exposes exactly one global\n // context manager. Customers with their own OTel provider boot last and\n // win. We accept the trade-off for correctness: the alternative\n // (separate per-provider context manager) is not supported by OTel.\n const contextManager = new PageScopeContextManager();\n tracerProvider.register({ contextManager });\n\n // Composed W3C trace context + baggage propagator. `FetchInstrumentation`\n // injects both `traceparent` and `baggage` on outgoing requests so\n // backend Elysia plugins can pick `interfere.*` baggage entries off\n // the request and stamp them on server spans. Only set the global if\n // no propagator is currently registered (`fields()` is `[]` for the\n // noop). Customers who installed their own propagator (B3, composite,\n // …) are preserved. Reading the SSR meta tag uses a local instance,\n // not the global, so our extract path is independent.\n if (propagation.fields().length === 0) {\n propagation.setGlobalPropagator(\n new CompositePropagator({\n propagators: [\n new W3CTraceContextPropagator(),\n new W3CBaggagePropagator(),\n ],\n })\n );\n }\n\n const metricExporter = createBeaconMetricExporter({\n collectorUrl: input.collectorUrl,\n authHeaders: input.authHeaders,\n });\n\n const metricReader = new PeriodicExportingMetricReader({\n exporter: metricExporter,\n exportIntervalMillis: 30_000,\n });\n\n const meterProvider = new MeterProvider({\n resource,\n readers: [metricReader, ...(input.additionalMetricReaders ?? [])],\n });\n\n // Logs path. The `plugins/logs.ts` plugin patches `console.*` and emits\n // a `LogRecord` per call (errorsPlugin still owns Error-bearing console\n // calls — the class boundary is checked there). LoggerProvider is\n // private; the kernel exposes `recordLog` so callers don't have to\n // touch the OTel logs API directly.\n const logExporter = createBeaconLogExporter({\n collectorUrl: input.collectorUrl,\n authHeaders: input.authHeaders,\n });\n const loggerProvider = new LoggerProvider({\n resource,\n processors: [\n // Mirrors the SessionSpanProcessor for traces — every log record\n // (including `BrowserNavigationInstrumentation`'s navigation\n // events) carries the same `session.id` as the spans emitted\n // around it, so trace ↔ log correlation by session is trivial.\n createSessionLogRecordProcessor({ getSessionId }),\n new BatchLogRecordProcessor(logExporter, BROWSER_BATCH_OPTIONS),\n ...(input.additionalLogRecordProcessors ?? []),\n ],\n });\n\n return {\n contextManager,\n tracerProvider,\n meterProvider,\n metricReader,\n loggerProvider,\n flush: async () => {\n await Promise.all([\n tracerProvider.forceFlush(),\n metricReader.forceFlush(),\n loggerProvider.forceFlush(),\n ]);\n },\n shutdown: async () => {\n await Promise.all([\n tracerProvider.shutdown(),\n meterProvider.shutdown(),\n loggerProvider.shutdown(),\n ]);\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AA2CA,MAAM,oBAAoB;AAK1B,MAAM,oBAAoB;AAC1B,MAAM,yBAAyB;AAC/B,MAAM,mCAAmC;AACzC,MAAM,8BAA8B;AACpC,MAAM,oBAAoB;AAC1B,MAAM,2BAA2B;AACjC,MAAM,0BAA0B;AAChC,MAAM,6BAA6B;AAMnC,MAAM,iBAAiB;AAEvB,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoC7B,MAAM,wBAAwB;CAC5B,cAAc;CACd,oBAAoB;CACpB,sBAAsB;CACtB,qBAAqB;CACtB;;;;;;;;;AAmGD,SAAgB,kBACd,OACoB;CAOpB,MAAM,cACJ,aACE,OAAO,YAAY,cAAc,KAAA,IAAY,QAAQ,IAAI,YAC1D,IAAI;CAEP,MAAM,eAAe,uBAAuB;GACzC,oBAAoB,MAAM,eAAe;GACzC,yBAAyB;GACzB,mCAAmC;GACnC,8BAA8B;GAC9B,0BAA0B;GAC1B,6BAA6B;GAC7B,2BAA2B,MAAM,SAAS,KAAK,KAAK;EACrD,GAAI,MAAM,cAAc,GAAG,oBAAoB,MAAM,aAAa,GAAG,EAAE;EACvE,GAAI,MAAM,WAAW,GAAG,iBAAiB,MAAM,UAAU,GAAG,EAAE;EAC/D,CAAC;CACF,MAAM,WAAW,gBAAgB,EAAE,WAAW,CAAC,gBAAgB,EAAE,CAAC;CAClE,MAAM,WAAW,aAAa,MAAM,SAAS;CAE7C,MAAM,gBAAgB,0BAA0B;EAC9C,cAAc,MAAM;EACpB,aAAa,MAAM;EACpB,CAAC;CAIF,MAAM,eAAe,MAAM;CAwB3B,MAAM,iBAAiB,IAAI,kBAAkB;EAAE;EAAU,gBAAA;GAjBvD,2BAA2B,EAAE,cAAc,CAAC;GAQ5C,IAAI,mBAAmB,eAAe,sBAAsB;GAM5D,GAAI,MAAM,4BAA4B,EAAE;GAG6B;EAAE,CAAC;CAc1E,MAAM,iBAAiB,IAAI,yBAAyB;CACpD,eAAe,SAAS,EAAE,gBAAgB,CAAC;CAU3C,IAAI,YAAY,QAAQ,CAAC,WAAW,GAClC,YAAY,oBACV,IAAI,oBAAoB,EACtB,aAAa,CACX,IAAI,2BAA2B,EAC/B,IAAI,sBAAsB,CAC3B,EACF,CAAC,CACH;CAQH,MAAM,eAAe,IAAI,8BAA8B;EACrD,UANqB,2BAA2B;GAChD,cAAc,MAAM;GACpB,aAAa,MAAM;GACpB,CAGyB;EACxB,sBAAsB;EACvB,CAAC;CAEF,MAAM,gBAAgB,IAAI,cAAc;EACtC;EACA,SAAS,CAAC,cAAc,GAAI,MAAM,2BAA2B,EAAE,CAAE;EAClE,CAAC;CAOF,MAAM,cAAc,wBAAwB;EAC1C,cAAc,MAAM;EACpB,aAAa,MAAM;EACpB,CAAC;CACF,MAAM,iBAAiB,IAAI,eAAe;EACxC;EACA,YAAY;GAKV,gCAAgC,EAAE,cAAc,CAAC;GACjD,IAAI,wBAAwB,aAAa,sBAAsB;GAC/D,GAAI,MAAM,iCAAiC,EAAE;GAC9C;EACF,CAAC;CAEF,OAAO;EACL;EACA;EACA;EACA;EACA;EACA,OAAO,YAAY;GACjB,MAAM,QAAQ,IAAI;IAChB,eAAe,YAAY;IAC3B,aAAa,YAAY;IACzB,eAAe,YAAY;IAC5B,CAAC;;EAEJ,UAAU,YAAY;GACpB,MAAM,QAAQ,IAAI;IAChB,eAAe,UAAU;IACzB,cAAc,UAAU;IACxB,eAAe,UAAU;IAC1B,CAAC;;EAEL"}
|
|
1
|
+
{"version":3,"file":"provider.mjs","names":[],"sources":["../../../src/internal/otel/provider.ts"],"sourcesContent":["import type { SessionId } from \"@interfere/types/data/session\";\nimport type { ReleaseSlug } from \"@interfere/types/releases/slug\";\nimport { normalizeEnv } from \"@interfere/types/sdk/runtime\";\n\nimport { propagation } from \"@opentelemetry/api\";\nimport {\n CompositePropagator,\n W3CBaggagePropagator,\n W3CTraceContextPropagator,\n} from \"@opentelemetry/core\";\nimport { browserDetector } from \"@opentelemetry/opentelemetry-browser-detector\";\nimport {\n detectResources,\n resourceFromAttributes,\n} from \"@opentelemetry/resources\";\nimport {\n BatchLogRecordProcessor,\n LoggerProvider,\n type LogRecordProcessor,\n} from \"@opentelemetry/sdk-logs\";\nimport {\n MeterProvider,\n type MetricReader,\n PeriodicExportingMetricReader,\n} from \"@opentelemetry/sdk-metrics\";\nimport {\n BatchSpanProcessor,\n type SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport { WebTracerProvider } from \"@opentelemetry/sdk-trace-web\";\nimport {\n createSessionLogRecordProcessor,\n createSessionSpanProcessor,\n} from \"@opentelemetry/web-common\";\n\nimport { SDK_NAME, SDK_VERSION } from \"../version.js\";\nimport {\n createBeaconLogExporter,\n createBeaconMetricExporter,\n createBeaconTraceExporter,\n} from \"./exporter.js\";\nimport { PageScopeContextManager } from \"./page-scope-context-manager.js\";\n\nconst SERVICE_NAMESPACE = \"interfere\";\n\n// Inlined wire keys — matches the pattern in\n// `@interfere/observability/browser/rum` (semconv keeps shifting these\n// between minor versions, but the wire keys themselves are stable).\nconst ATTR_SERVICE_NAME = \"service.name\" as const;\nconst ATTR_SERVICE_NAMESPACE = \"service.namespace\" as const;\nconst ATTR_DEPLOYMENT_ENVIRONMENT_NAME = \"deployment.environment.name\" as const;\nconst ATTR_TELEMETRY_SDK_LANGUAGE = \"telemetry.sdk.language\" as const;\nconst ATTR_RELEASE_SLUG = \"release.slug\" as const;\nconst ATTR_INTERFERE_SDK_STACK = \"interfere.sdk.stack\" as const;\nconst ATTR_INTERFERE_SDK_NAME = \"interfere.sdk.name\" as const;\nconst ATTR_INTERFERE_SDK_VERSION = \"interfere.sdk.version\" as const;\n// OTel semconv-canonical name for a stable per-device identifier.\n// Stamped as a *resource* attribute (not per-span) because it's stable\n// for the lifetime of the page; sessions rotate, devices don't. The\n// collector reads this off `context.resourceAttributes[\"device.id\"]`\n// and projects it into `spans.deviceId`.\nconst ATTR_DEVICE_ID = \"device.id\" as const;\n\nconst DEFAULT_SERVICE_NAME = \"interfere-sdk\";\n\n/**\n * Tuned for `navigator.sendBeacon`'s per-call payload ceiling.\n *\n * The browser primary exporters dispatch every export via beacon\n * (see `BeaconTraceExporter` / `BeaconLogExporter` /\n * `BeaconMetricExporter` in `exporter.ts`) — sendBeacon is the only\n * browser transport that reliably commits a request the page is also\n * tearing down. It's not free though: most browsers cap each beacon\n * payload at 64KiB (Chrome / Firefox) and reject anything larger\n * with a `false` return from `navigator.sendBeacon` — at which point\n * the BSP just drops the batch (no retry: the SW backstop intercepts\n * 5xx / network failures, not user-agent rejections).\n *\n * The numbers below keep payloads comfortably under that ceiling,\n * even on jank-heavy pages (production data on 2026-05-11 saw 831\n * `longtask` spans piled up on a single page-hide, observed via the\n * BetterStack dual-write) where a 100-span batch would already top\n * 100KiB:\n *\n * - `maxExportBatchSize: 10` — caps each beacon at ~10–20KiB,\n * leaving headroom for span attribute bloat without flirting\n * with the per-call ceiling.\n * - `maxQueueSize: 50` — caps the worst-case unload payload at\n * ~50–100KiB; the BSP drains across 5 beacons when forced-\n * flushed on `visibilitychange→hidden`, all of which queue\n * synchronously inside the user agent.\n * - `scheduledDelayMillis: 2000` — quieter pages keep less in\n * buffer at any moment, so a hidden→visible→hidden bounce\n * doesn't accumulate 5s of telemetry waiting to ship.\n * - `exportTimeoutMillis: 10_000` — unchanged. Beacon dispatches\n * are essentially synchronous (the user agent enqueues into its\n * own send loop), so the timeout is mostly defensive against\n * pathological `JsonTraceSerializer.serializeRequest` calls.\n */\nconst BROWSER_BATCH_OPTIONS = {\n maxQueueSize: 50,\n maxExportBatchSize: 10,\n scheduledDelayMillis: 2000,\n exportTimeoutMillis: 10_000,\n} as const;\n\nexport interface OtelProviderInput {\n /**\n * @internal\n *\n * Extra log-record processors fanned into the LoggerProvider's\n * processor list at construction. Used by `@interfere/observability`\n * for internal-only dual-write to BetterStack — keeps a parallel\n * destination's queue/exporter independent of the SDK's own.\n * Customers don't get a fan-out hook on the SDK surface; this is a\n * private bridge for our own dogfood apps.\n */\n additionalLogRecordProcessors?: LogRecordProcessor[];\n /**\n * @internal\n *\n * Extra metric readers fanned into the MeterProvider's reader list\n * at construction. Used by `@interfere/observability` for internal-\n * only dual-write of web vitals histograms (`web_vitals.{ttfb,fcp,\n * lcp,inp,cls}`) to the BetterStack-fronting OTel collector. Each\n * reader owns its own queue + exporter so a 5xx / network outage on\n * the parallel destination backs off only its own batch.\n */\n additionalMetricReaders?: MetricReader[];\n /**\n * @internal\n *\n * Extra span processors fanned into the WebTracerProvider's processor\n * list at construction. See `additionalLogRecordProcessors`.\n */\n additionalSpanProcessors?: SpanProcessor[];\n /**\n * Auth + content headers for OTLP exports. Sourced from the kernel's\n * already-resolved ingest target so the OTLP path inherits the same\n * transport metadata the rest of the SDK uses — no separate auth knob.\n */\n authHeaders: Headers;\n /** OTLP base URL — the opaque sink path is appended by the exporters. */\n collectorUrl: string;\n /**\n * Stable per-device identifier from `DeviceManager`. Resolved from\n * localStorage / cookie at kernel boot, so it's synchronously\n * available before the providers are constructed. Stamped as the\n * OTel-semconv `device.id` resource attribute so every span / log /\n * metric carries it without per-record re-stamping. Optional —\n * tests and SSR boot paths that build the provider before the\n * device manager has run pass `null`.\n */\n deviceId?: string | null | undefined;\n /**\n * Returns the kernel's current session id. Wired into per-span /\n * per-log session processors so a mid-page session rotation (30-min\n * idle expiry, manual reset) lands on subsequent spans without the\n * kernel having to rebuild the providers. Returns `null` before the\n * `SessionTracker` has bootstrapped.\n */\n getSessionId: () => SessionId | null;\n /** Optional release slug — top-level `release.slug` attr (Phase 6 wire identity). */\n releaseSlug?: ReleaseSlug | null | undefined;\n /**\n * Producer chain for the `interfere.sdk.stack` resource attribute —\n * e.g. `[\"@interfere/next@10.0.0\", \"@interfere/react@10.0.0\"]`.\n * Wrapper-injected; the kernel just forwards.\n */\n sdkStack: string[];\n /**\n * Override the OTel `service.name` resource attribute. Defaults to\n * `\"interfere-sdk\"`.\n */\n serviceName?: string;\n}\n\nexport interface OtelProviderHandle {\n /**\n * Page-scope context manager. The kernel calls `setPageScope(ctx)` after\n * extracting the SSR `traceparent` meta tag so spans without a more\n * specific zone descend from the server-side parent.\n */\n contextManager: PageScopeContextManager;\n /** Force-flush all three providers — call from kernel.flush() / on visibility hidden. */\n flush(): Promise<void>;\n loggerProvider: LoggerProvider;\n meterProvider: MeterProvider;\n metricReader: MetricReader;\n /** Tear down all three providers — called from kernel.dispose(). */\n shutdown(): Promise<void>;\n tracerProvider: WebTracerProvider;\n}\n\n/**\n * Constructs the SDK's **private** OTel providers — never registers globally\n * via `provider.register()` or `trace.setGlobalTracerProvider()`. Customers\n * with their own OTel setup (DataDog, Vercel, etc.) cohabit cleanly.\n *\n * Splitting this out from `Kernel` keeps the OTel module lazy-loadable so\n * the error-only bundle path (no tracing) never imports the OTel SDK.\n */\nexport function buildOtelProvider(\n input: OtelProviderInput\n): OtelProviderHandle {\n // Static service identity merged with detected browser attrs\n // (`browser.brands`, `browser.platform`, `browser.mobile`,\n // `browser.language`, `browser.user_agent`). The browser detector\n // reads UA Client Hints synchronously via `navigator.userAgentData`\n // low-entropy properties, so this stays sync — async would race\n // `DocumentLoadInstrumentation` and consistently miss page-load.\n const environment =\n normalizeEnv(\n typeof process === \"undefined\" ? undefined : process.env[\"NODE_ENV\"]\n ) ?? \"unknown\";\n\n const baseResource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: input.serviceName ?? DEFAULT_SERVICE_NAME,\n [ATTR_SERVICE_NAMESPACE]: SERVICE_NAMESPACE,\n [ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: environment,\n [ATTR_TELEMETRY_SDK_LANGUAGE]: \"webjs\",\n [ATTR_INTERFERE_SDK_NAME]: SDK_NAME,\n [ATTR_INTERFERE_SDK_VERSION]: SDK_VERSION,\n [ATTR_INTERFERE_SDK_STACK]: input.sdkStack.join(\", \"),\n ...(input.releaseSlug ? { [ATTR_RELEASE_SLUG]: input.releaseSlug } : {}),\n ...(input.deviceId ? { [ATTR_DEVICE_ID]: input.deviceId } : {}),\n });\n const detected = detectResources({ detectors: [browserDetector] });\n const resource = baseResource.merge(detected);\n\n const traceExporter = createBeaconTraceExporter({\n collectorUrl: input.collectorUrl,\n authHeaders: input.authHeaders,\n });\n\n // The web-common session processor expects `() => string | null`,\n // matching our `KernelSession.getId` shape.\n const getSessionId = input.getSessionId;\n\n const spanProcessors: SpanProcessor[] = [\n // Stamps `session.id` per-span, re-reading on every export. A\n // mid-page session rotation (30-min idle, manual reset) lands on\n // subsequent spans without rebuilding the provider. Resource-level\n // `session.id` would freeze at boot.\n createSessionSpanProcessor({ getSessionId }),\n // Exception attribute defaults (`interfere.exception.{mechanism,\n // handled,kind}`) are no longer stamped here — third-party\n // auto-instrumentations bypass our kernel anyway, so the SDK\n // can't reliably tell what to default. Server-side enrichment\n // (`enrichment/lib/normalize-atom.ts`) is the single chokepoint\n // every exception event flows through, so the defaults live\n // there now and the SDK ships less code.\n new BatchSpanProcessor(traceExporter, BROWSER_BATCH_OPTIONS),\n // Internal-only fan-out (BetterStack dual-write for `interfere/homepage`\n // + `interfere/dashboard`). Each extra processor owns its own queue +\n // exporter, so a 5xx / network outage on the parallel destination\n // backs off only its own batch — the primary collector pipeline keeps\n // draining unaffected.\n ...(input.additionalSpanProcessors ?? []),\n ];\n\n const tracerProvider = new WebTracerProvider({ resource, spanProcessors });\n\n // `PageScopeContextManager` keeps the active context alive across\n // microtasks/promise chains/setTimeout, AND falls back to the page-scope\n // context (the SSR `traceparent`) for code paths that fire outside any\n // zone the manager itself created. The kernel populates the page scope\n // by calling `contextManager.setPageScope(ctx)` after extracting the\n // meta tag.\n //\n // `register({ contextManager })` does call `setGlobalContextManager`\n // internally — that's unavoidable; OTel exposes exactly one global\n // context manager. Customers with their own OTel provider boot last and\n // win. We accept the trade-off for correctness: the alternative\n // (separate per-provider context manager) is not supported by OTel.\n const contextManager = new PageScopeContextManager();\n tracerProvider.register({ contextManager });\n\n // Composed W3C trace context + baggage propagator. `FetchInstrumentation`\n // injects both `traceparent` and `baggage` on outgoing requests so\n // backend Elysia plugins can pick `interfere.*` baggage entries off\n // the request and stamp them on server spans. Only set the global if\n // no propagator is currently registered (`fields()` is `[]` for the\n // noop). Customers who installed their own propagator (B3, composite,\n // …) are preserved. Reading the SSR meta tag uses a local instance,\n // not the global, so our extract path is independent.\n if (propagation.fields().length === 0) {\n propagation.setGlobalPropagator(\n new CompositePropagator({\n propagators: [\n new W3CTraceContextPropagator(),\n new W3CBaggagePropagator(),\n ],\n })\n );\n }\n\n const metricExporter = createBeaconMetricExporter({\n collectorUrl: input.collectorUrl,\n authHeaders: input.authHeaders,\n });\n\n const metricReader = new PeriodicExportingMetricReader({\n exporter: metricExporter,\n exportIntervalMillis: 30_000,\n });\n\n const meterProvider = new MeterProvider({\n resource,\n readers: [metricReader, ...(input.additionalMetricReaders ?? [])],\n });\n\n // Logs path. The `plugins/logs.ts` plugin patches `console.*` and emits\n // a `LogRecord` per call (errorsPlugin still owns Error-bearing console\n // calls — the class boundary is checked there). LoggerProvider is\n // private; the kernel exposes `recordLog` so callers don't have to\n // touch the OTel logs API directly.\n const logExporter = createBeaconLogExporter({\n collectorUrl: input.collectorUrl,\n authHeaders: input.authHeaders,\n });\n const loggerProvider = new LoggerProvider({\n resource,\n processors: [\n // Mirrors the SessionSpanProcessor for traces — every log record\n // (including `BrowserNavigationInstrumentation`'s navigation\n // events) carries the same `session.id` as the spans emitted\n // around it, so trace ↔ log correlation by session is trivial.\n createSessionLogRecordProcessor({ getSessionId }),\n new BatchLogRecordProcessor(logExporter, BROWSER_BATCH_OPTIONS),\n ...(input.additionalLogRecordProcessors ?? []),\n ],\n });\n\n return {\n contextManager,\n tracerProvider,\n meterProvider,\n metricReader,\n loggerProvider,\n flush: async () => {\n await Promise.all([\n tracerProvider.forceFlush(),\n metricReader.forceFlush(),\n loggerProvider.forceFlush(),\n ]);\n },\n shutdown: async () => {\n await Promise.all([\n tracerProvider.shutdown(),\n meterProvider.shutdown(),\n loggerProvider.shutdown(),\n ]);\n },\n };\n}\n"],"mappings":"27BA2CA,MAwDM,sBAAwB,CAC5B,aAAc,GACd,mBAAoB,GACpB,qBAAsB,IACtB,oBAAqB,GACvB,EAkGA,SAAgB,kBACd,MACoB,CAOpB,IAAM,YACJ,aACE,OAAO,QAAY,IAAc,IAAA,GAAY,QAAQ,IAAI,QAC3D,GAAK,UAED,aAAe,uBAAuB,CACzC,eAAoB,MAAM,aAAe,gBACzC,oBAAyB,YACzB,8BAAmC,YACnC,yBAA8B,QAC9B,qBAA0B,SAC1B,wBAA6B,YAC7B,sBAA2B,MAAM,SAAS,KAAK,IAAI,EACpD,GAAI,MAAM,YAAc,CAAG,eAAoB,MAAM,WAAY,EAAI,CAAC,EACtE,GAAI,MAAM,SAAW,CAAG,YAAiB,MAAM,QAAS,EAAI,CAAC,CAC/D,CAAC,EACK,SAAW,gBAAgB,CAAE,UAAW,CAAC,eAAe,CAAE,CAAC,EAC3D,SAAW,aAAa,MAAM,QAAQ,EAEtC,cAAgB,0BAA0B,CAC9C,aAAc,MAAM,aACpB,YAAa,MAAM,WACrB,CAAC,EAIK,aAAe,MAAM,aAwBrB,eAAiB,IAAI,kBAAkB,CAAE,SAAU,eAAA,CAjBvD,2BAA2B,CAAE,YAAa,CAAC,EAQ3C,IAAI,mBAAmB,cAAe,qBAAqB,EAM3D,GAAI,MAAM,0BAA4B,CAAC,CAG6B,CAAE,CAAC,EAcnE,eAAiB,IAAI,wBAC3B,eAAe,SAAS,CAAE,cAAe,CAAC,EAUtC,YAAY,OAAO,EAAE,SAAW,GAClC,YAAY,oBACV,IAAI,oBAAoB,CACtB,YAAa,CACX,IAAI,0BACJ,IAAI,oBACN,CACF,CAAC,CACH,EAQF,IAAM,aAAe,IAAI,8BAA8B,CACrD,SANqB,2BAA2B,CAChD,aAAc,MAAM,aACpB,YAAa,MAAM,WACrB,CAGyB,EACvB,qBAAsB,GACxB,CAAC,EAEK,cAAgB,IAAI,cAAc,CACtC,SACA,QAAS,CAAC,aAAc,GAAI,MAAM,yBAA2B,CAAC,CAAE,CAClE,CAAC,EAOK,YAAc,wBAAwB,CAC1C,aAAc,MAAM,aACpB,YAAa,MAAM,WACrB,CAAC,EACK,eAAiB,IAAI,eAAe,CACxC,SACA,WAAY,CAKV,gCAAgC,CAAE,YAAa,CAAC,EAChD,IAAI,wBAAwB,YAAa,qBAAqB,EAC9D,GAAI,MAAM,+BAAiC,CAAC,CAC9C,CACF,CAAC,EAED,MAAO,CACL,eACA,eACA,cACA,aACA,eACA,MAAO,SAAY,CACjB,MAAM,QAAQ,IAAI,CAChB,eAAe,WAAW,EAC1B,aAAa,WAAW,EACxB,eAAe,WAAW,CAC5B,CAAC,CACH,EACA,SAAU,SAAY,CACpB,MAAM,QAAQ,IAAI,CAChB,eAAe,SAAS,EACxB,cAAc,SAAS,EACvB,eAAe,SAAS,CAC1B,CAAC,CACH,CACF,CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-vitals.d.mts","names":[],"sources":["../../../src/internal/otel/web-vitals.ts"],"mappings":";;;UAmBiB,cAAA;;AAAjB;;;;EAME,KAAA,QAAa,OAAA;EAAA;EAEb,KAAA,EAAO,
|
|
1
|
+
{"version":3,"file":"web-vitals.d.mts","names":[],"sources":["../../../src/internal/otel/web-vitals.ts"],"mappings":";;;UAmBiB,cAAA;;AAAjB;;;;EAME,KAAA,QAAa,OAAA;EAAA;EAEb,KAAA,EAAO,KAAK;EAAL;;;;AAMyB;EAAhC,YAAA,IAAgB,QAAA;AAAA;;;AAgBoC;;;;;;;;;;;iBAAtC,gBAAA,CAAiB,KAAqB,EAAd,cAAc"}
|