@sentry/browser 10.26.0 → 10.28.0-alpha.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/build/npm/cjs/dev/client.js +0 -1
- package/build/npm/cjs/dev/client.js.map +1 -1
- package/build/npm/cjs/dev/index.js +5 -0
- package/build/npm/cjs/dev/index.js.map +1 -1
- package/build/npm/cjs/dev/integrations/httpcontext.js +2 -0
- package/build/npm/cjs/dev/integrations/httpcontext.js.map +1 -1
- package/build/npm/cjs/dev/integrations/spanstreaming.js +217 -0
- package/build/npm/cjs/dev/integrations/spanstreaming.js.map +1 -0
- package/build/npm/cjs/dev/profiling/UIProfiler.js +98 -41
- package/build/npm/cjs/dev/profiling/UIProfiler.js.map +1 -1
- package/build/npm/cjs/dev/profiling/index.js +59 -0
- package/build/npm/cjs/dev/profiling/index.js.map +1 -0
- package/build/npm/cjs/dev/profiling/integration.js +12 -10
- package/build/npm/cjs/dev/profiling/integration.js.map +1 -1
- package/build/npm/cjs/dev/profiling/utils.js +11 -5
- package/build/npm/cjs/dev/profiling/utils.js.map +1 -1
- package/build/npm/cjs/dev/tracing/request.js +1 -0
- package/build/npm/cjs/dev/tracing/request.js.map +1 -1
- package/build/npm/cjs/prod/client.js +0 -1
- package/build/npm/cjs/prod/client.js.map +1 -1
- package/build/npm/cjs/prod/index.js +5 -0
- package/build/npm/cjs/prod/index.js.map +1 -1
- package/build/npm/cjs/prod/integrations/httpcontext.js +2 -0
- package/build/npm/cjs/prod/integrations/httpcontext.js.map +1 -1
- package/build/npm/cjs/prod/integrations/spanstreaming.js +217 -0
- package/build/npm/cjs/prod/integrations/spanstreaming.js.map +1 -0
- package/build/npm/cjs/prod/profiling/UIProfiler.js +98 -41
- package/build/npm/cjs/prod/profiling/UIProfiler.js.map +1 -1
- package/build/npm/cjs/prod/profiling/index.js +59 -0
- package/build/npm/cjs/prod/profiling/index.js.map +1 -0
- package/build/npm/cjs/prod/profiling/integration.js +12 -10
- package/build/npm/cjs/prod/profiling/integration.js.map +1 -1
- package/build/npm/cjs/prod/profiling/utils.js +11 -5
- package/build/npm/cjs/prod/profiling/utils.js.map +1 -1
- package/build/npm/cjs/prod/tracing/request.js +1 -0
- package/build/npm/cjs/prod/tracing/request.js.map +1 -1
- package/build/npm/esm/dev/client.js +0 -1
- package/build/npm/esm/dev/client.js.map +1 -1
- package/build/npm/esm/dev/index.js +3 -1
- package/build/npm/esm/dev/index.js.map +1 -1
- package/build/npm/esm/dev/integrations/httpcontext.js +2 -0
- package/build/npm/esm/dev/integrations/httpcontext.js.map +1 -1
- package/build/npm/esm/dev/integrations/spanstreaming.js +215 -0
- package/build/npm/esm/dev/integrations/spanstreaming.js.map +1 -0
- package/build/npm/esm/dev/package.json +1 -1
- package/build/npm/esm/dev/profiling/UIProfiler.js +100 -43
- package/build/npm/esm/dev/profiling/UIProfiler.js.map +1 -1
- package/build/npm/esm/dev/profiling/index.js +57 -0
- package/build/npm/esm/dev/profiling/index.js.map +1 -0
- package/build/npm/esm/dev/profiling/integration.js +13 -11
- package/build/npm/esm/dev/profiling/integration.js.map +1 -1
- package/build/npm/esm/dev/profiling/utils.js +12 -6
- package/build/npm/esm/dev/profiling/utils.js.map +1 -1
- package/build/npm/esm/dev/tracing/request.js +1 -0
- package/build/npm/esm/dev/tracing/request.js.map +1 -1
- package/build/npm/esm/prod/client.js +0 -1
- package/build/npm/esm/prod/client.js.map +1 -1
- package/build/npm/esm/prod/index.js +3 -1
- package/build/npm/esm/prod/index.js.map +1 -1
- package/build/npm/esm/prod/integrations/httpcontext.js +2 -0
- package/build/npm/esm/prod/integrations/httpcontext.js.map +1 -1
- package/build/npm/esm/prod/integrations/spanstreaming.js +215 -0
- package/build/npm/esm/prod/integrations/spanstreaming.js.map +1 -0
- package/build/npm/esm/prod/package.json +1 -1
- package/build/npm/esm/prod/profiling/UIProfiler.js +100 -43
- package/build/npm/esm/prod/profiling/UIProfiler.js.map +1 -1
- package/build/npm/esm/prod/profiling/index.js +57 -0
- package/build/npm/esm/prod/profiling/index.js.map +1 -0
- package/build/npm/esm/prod/profiling/integration.js +13 -11
- package/build/npm/esm/prod/profiling/integration.js.map +1 -1
- package/build/npm/esm/prod/profiling/utils.js +12 -6
- package/build/npm/esm/prod/profiling/utils.js.map +1 -1
- package/build/npm/esm/prod/tracing/request.js +1 -0
- package/build/npm/esm/prod/tracing/request.js.map +1 -1
- package/build/npm/types/client.d.ts.map +1 -1
- package/build/npm/types/exports.d.ts +1 -0
- package/build/npm/types/exports.d.ts.map +1 -1
- package/build/npm/types/index.d.ts +2 -1
- package/build/npm/types/index.d.ts.map +1 -1
- package/build/npm/types/integrations/httpcontext.d.ts.map +1 -1
- package/build/npm/types/integrations/spanstreaming.d.ts +5 -0
- package/build/npm/types/integrations/spanstreaming.d.ts.map +1 -0
- package/build/npm/types/profiling/UIProfiler.d.ts +23 -22
- package/build/npm/types/profiling/UIProfiler.d.ts.map +1 -1
- package/build/npm/types/profiling/index.d.ts +8 -0
- package/build/npm/types/profiling/index.d.ts.map +1 -0
- package/build/npm/types/profiling/integration.d.ts.map +1 -1
- package/build/npm/types/profiling/utils.d.ts +1 -1
- package/build/npm/types/profiling/utils.d.ts.map +1 -1
- package/build/npm/types/tracing/request.d.ts.map +1 -1
- package/build/npm/types-ts3.8/exports.d.ts +1 -0
- package/build/npm/types-ts3.8/index.d.ts +2 -1
- package/build/npm/types-ts3.8/integrations/spanstreaming.d.ts +5 -0
- package/build/npm/types-ts3.8/profiling/UIProfiler.d.ts +23 -22
- package/build/npm/types-ts3.8/profiling/index.d.ts +8 -0
- package/build/npm/types-ts3.8/profiling/utils.d.ts +1 -1
- package/package.json +7 -7
|
@@ -50,7 +50,6 @@ class BrowserClient extends core.Client {
|
|
|
50
50
|
|
|
51
51
|
// Flush logs and metrics when page becomes hidden (e.g., tab switch, navigation)
|
|
52
52
|
// todo(v11): Remove the experimental flag
|
|
53
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
54
53
|
if (helpers.WINDOW.document && (sendClientReports || enableLogs || enableMetrics)) {
|
|
55
54
|
helpers.WINDOW.document.addEventListener('visibilitychange', () => {
|
|
56
55
|
if (helpers.WINDOW.document.visibilityState === 'hidden') {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sources":["../../../../src/client.ts"],"sourcesContent":["import type {\n BrowserClientProfilingOptions,\n BrowserClientReplayOptions,\n ClientOptions,\n Event,\n EventHint,\n Options as CoreOptions,\n ParameterizedString,\n Scope,\n SeverityLevel,\n} from '@sentry/core';\nimport {\n _INTERNAL_flushLogsBuffer,\n _INTERNAL_flushMetricsBuffer,\n addAutoIpAddressToSession,\n applySdkMetadata,\n Client,\n getSDKSource,\n} from '@sentry/core';\nimport { eventFromException, eventFromMessage } from './eventbuilder';\nimport { WINDOW } from './helpers';\nimport type { BrowserTransportOptions } from './transports/types';\n\n/**\n * A magic string that build tooling can leverage in order to inject a release value into the SDK.\n */\ndeclare const __SENTRY_RELEASE__: string | undefined;\n\ntype BrowserSpecificOptions = BrowserClientReplayOptions &\n BrowserClientProfilingOptions & {\n /** If configured, this URL will be used as base URL for lazy loading integration. */\n cdnBaseUrl?: string;\n\n /**\n * Important: Only set this option if you know what you are doing!\n *\n * By default, the SDK will check if `Sentry.init` is called in a browser extension.\n * In case it is, it will stop initialization and log a warning\n * because browser extensions require a different Sentry initialization process:\n * https://docs.sentry.io/platforms/javascript/best-practices/shared-environments/\n *\n * Setting up the SDK in a browser extension with global error monitoring is not recommended\n * and will likely flood you with errors from other web sites or extensions. This can heavily\n * impact your quota and cause interference with your and other Sentry SDKs in shared environments.\n *\n * If this check wrongfully flags your setup as a browser extension, you can set this\n * option to `true` to skip the check.\n *\n * @default false\n */\n skipBrowserExtensionCheck?: boolean;\n\n /**\n * If set to `true`, the SDK propagates the W3C `traceparent` header to any outgoing requests,\n * in addition to the `sentry-trace` and `baggage` headers. Use the {@link CoreOptions.tracePropagationTargets}\n * option to control to which outgoing requests the header will be attached.\n *\n * **Important:** If you set this option to `true`, make sure that you configured your servers'\n * CORS settings to allow the `traceparent` header. Otherwise, requests might get blocked.\n *\n * @see https://www.w3.org/TR/trace-context/\n *\n * @default false\n */\n propagateTraceparent?: boolean;\n\n /**\n * If you use Spotlight by Sentry during development, use\n * this option to forward captured Sentry events to Spotlight.\n *\n * Either set it to true, or provide a specific Spotlight Sidecar URL.\n *\n * More details: https://spotlightjs.com/\n *\n * IMPORTANT: Only set this option to `true` while developing, not in production!\n */\n spotlight?: boolean | string;\n };\n/**\n * Configuration options for the Sentry Browser SDK.\n * @see @sentry/core Options for more information.\n */\nexport type BrowserOptions = CoreOptions<BrowserTransportOptions> & BrowserSpecificOptions;\n\n/**\n * Configuration options for the Sentry Browser SDK Client class\n * @see BrowserClient for more information.\n */\nexport type BrowserClientOptions = ClientOptions<BrowserTransportOptions> & BrowserSpecificOptions;\n\n/**\n * The Sentry Browser SDK Client.\n *\n * @see BrowserOptions for documentation on configuration options.\n * @see SentryClient for usage documentation.\n */\nexport class BrowserClient extends Client<BrowserClientOptions> {\n /**\n * Creates a new Browser SDK instance.\n *\n * @param options Configuration options for this SDK.\n */\n public constructor(options: BrowserClientOptions) {\n const opts = applyDefaultOptions(options);\n const sdkSource = WINDOW.SENTRY_SDK_SOURCE || getSDKSource();\n applySdkMetadata(opts, 'browser', ['browser'], sdkSource);\n\n // Only allow IP inferral by Relay if sendDefaultPii is true\n if (opts._metadata?.sdk) {\n opts._metadata.sdk.settings = {\n infer_ip: opts.sendDefaultPii ? 'auto' : 'never',\n // purposefully allowing already passed settings to override the default\n ...opts._metadata.sdk.settings,\n };\n }\n\n super(opts);\n\n const {\n sendDefaultPii,\n sendClientReports,\n enableLogs,\n _experiments,\n enableMetrics: enableMetricsOption,\n } = this._options;\n\n // todo(v11): Remove the experimental flag\n // eslint-disable-next-line deprecation/deprecation\n const enableMetrics = enableMetricsOption ?? _experiments?.enableMetrics ?? true;\n\n // Flush logs and metrics when page becomes hidden (e.g., tab switch, navigation)\n // todo(v11): Remove the experimental flag\n
|
|
1
|
+
{"version":3,"file":"client.js","sources":["../../../../src/client.ts"],"sourcesContent":["import type {\n BrowserClientProfilingOptions,\n BrowserClientReplayOptions,\n ClientOptions,\n Event,\n EventHint,\n Options as CoreOptions,\n ParameterizedString,\n Scope,\n SeverityLevel,\n} from '@sentry/core';\nimport {\n _INTERNAL_flushLogsBuffer,\n _INTERNAL_flushMetricsBuffer,\n addAutoIpAddressToSession,\n applySdkMetadata,\n Client,\n getSDKSource,\n} from '@sentry/core';\nimport { eventFromException, eventFromMessage } from './eventbuilder';\nimport { WINDOW } from './helpers';\nimport type { BrowserTransportOptions } from './transports/types';\n\n/**\n * A magic string that build tooling can leverage in order to inject a release value into the SDK.\n */\ndeclare const __SENTRY_RELEASE__: string | undefined;\n\ntype BrowserSpecificOptions = BrowserClientReplayOptions &\n BrowserClientProfilingOptions & {\n /** If configured, this URL will be used as base URL for lazy loading integration. */\n cdnBaseUrl?: string;\n\n /**\n * Important: Only set this option if you know what you are doing!\n *\n * By default, the SDK will check if `Sentry.init` is called in a browser extension.\n * In case it is, it will stop initialization and log a warning\n * because browser extensions require a different Sentry initialization process:\n * https://docs.sentry.io/platforms/javascript/best-practices/shared-environments/\n *\n * Setting up the SDK in a browser extension with global error monitoring is not recommended\n * and will likely flood you with errors from other web sites or extensions. This can heavily\n * impact your quota and cause interference with your and other Sentry SDKs in shared environments.\n *\n * If this check wrongfully flags your setup as a browser extension, you can set this\n * option to `true` to skip the check.\n *\n * @default false\n */\n skipBrowserExtensionCheck?: boolean;\n\n /**\n * If set to `true`, the SDK propagates the W3C `traceparent` header to any outgoing requests,\n * in addition to the `sentry-trace` and `baggage` headers. Use the {@link CoreOptions.tracePropagationTargets}\n * option to control to which outgoing requests the header will be attached.\n *\n * **Important:** If you set this option to `true`, make sure that you configured your servers'\n * CORS settings to allow the `traceparent` header. Otherwise, requests might get blocked.\n *\n * @see https://www.w3.org/TR/trace-context/\n *\n * @default false\n */\n propagateTraceparent?: boolean;\n\n /**\n * If you use Spotlight by Sentry during development, use\n * this option to forward captured Sentry events to Spotlight.\n *\n * Either set it to true, or provide a specific Spotlight Sidecar URL.\n *\n * More details: https://spotlightjs.com/\n *\n * IMPORTANT: Only set this option to `true` while developing, not in production!\n */\n spotlight?: boolean | string;\n };\n/**\n * Configuration options for the Sentry Browser SDK.\n * @see @sentry/core Options for more information.\n */\nexport type BrowserOptions = CoreOptions<BrowserTransportOptions> & BrowserSpecificOptions;\n\n/**\n * Configuration options for the Sentry Browser SDK Client class\n * @see BrowserClient for more information.\n */\nexport type BrowserClientOptions = ClientOptions<BrowserTransportOptions> & BrowserSpecificOptions;\n\n/**\n * The Sentry Browser SDK Client.\n *\n * @see BrowserOptions for documentation on configuration options.\n * @see SentryClient for usage documentation.\n */\nexport class BrowserClient extends Client<BrowserClientOptions> {\n /**\n * Creates a new Browser SDK instance.\n *\n * @param options Configuration options for this SDK.\n */\n public constructor(options: BrowserClientOptions) {\n const opts = applyDefaultOptions(options);\n const sdkSource = WINDOW.SENTRY_SDK_SOURCE || getSDKSource();\n applySdkMetadata(opts, 'browser', ['browser'], sdkSource);\n\n // Only allow IP inferral by Relay if sendDefaultPii is true\n if (opts._metadata?.sdk) {\n opts._metadata.sdk.settings = {\n infer_ip: opts.sendDefaultPii ? 'auto' : 'never',\n // purposefully allowing already passed settings to override the default\n ...opts._metadata.sdk.settings,\n };\n }\n\n super(opts);\n\n const {\n sendDefaultPii,\n sendClientReports,\n enableLogs,\n _experiments,\n enableMetrics: enableMetricsOption,\n } = this._options;\n\n // todo(v11): Remove the experimental flag\n // eslint-disable-next-line deprecation/deprecation\n const enableMetrics = enableMetricsOption ?? _experiments?.enableMetrics ?? true;\n\n // Flush logs and metrics when page becomes hidden (e.g., tab switch, navigation)\n // todo(v11): Remove the experimental flag\n if (WINDOW.document && (sendClientReports || enableLogs || enableMetrics)) {\n WINDOW.document.addEventListener('visibilitychange', () => {\n if (WINDOW.document.visibilityState === 'hidden') {\n if (sendClientReports) {\n this._flushOutcomes();\n }\n if (enableLogs) {\n _INTERNAL_flushLogsBuffer(this);\n }\n\n if (enableMetrics) {\n _INTERNAL_flushMetricsBuffer(this);\n }\n }\n });\n }\n\n if (sendDefaultPii) {\n this.on('beforeSendSession', addAutoIpAddressToSession);\n }\n }\n\n /**\n * @inheritDoc\n */\n public eventFromException(exception: unknown, hint?: EventHint): PromiseLike<Event> {\n return eventFromException(this._options.stackParser, exception, hint, this._options.attachStacktrace);\n }\n\n /**\n * @inheritDoc\n */\n public eventFromMessage(\n message: ParameterizedString,\n level: SeverityLevel = 'info',\n hint?: EventHint,\n ): PromiseLike<Event> {\n return eventFromMessage(this._options.stackParser, message, level, hint, this._options.attachStacktrace);\n }\n\n /**\n * @inheritDoc\n */\n protected _prepareEvent(\n event: Event,\n hint: EventHint,\n currentScope: Scope,\n isolationScope: Scope,\n ): PromiseLike<Event | null> {\n event.platform = event.platform || 'javascript';\n\n return super._prepareEvent(event, hint, currentScope, isolationScope);\n }\n}\n\n/** Exported only for tests. */\nexport function applyDefaultOptions<T extends Partial<BrowserClientOptions>>(optionsArg: T): T {\n return {\n release:\n typeof __SENTRY_RELEASE__ === 'string' // This allows build tooling to find-and-replace __SENTRY_RELEASE__ to inject a release value\n ? __SENTRY_RELEASE__\n : WINDOW.SENTRY_RELEASE?.id, // This supports the variable that sentry-webpack-plugin injects\n sendClientReports: true,\n // We default this to true, as it is the safer scenario\n parentSpanIsAlwaysRootSpan: true,\n ...optionsArg,\n };\n}\n"],"names":["Client","WINDOW","getSDKSource","applySdkMetadata","_INTERNAL_flushLogsBuffer","_INTERNAL_flushMetricsBuffer","addAutoIpAddressToSession","eventFromException","eventFromMessage"],"mappings":"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"}
|
|
@@ -6,6 +6,7 @@ const core = require('@sentry/core');
|
|
|
6
6
|
const helpers = require('./helpers.js');
|
|
7
7
|
const client = require('./client.js');
|
|
8
8
|
const fetch = require('./transports/fetch.js');
|
|
9
|
+
const index = require('./profiling/index.js');
|
|
9
10
|
const stackParsers = require('./stack-parsers.js');
|
|
10
11
|
const eventbuilder = require('./eventbuilder.js');
|
|
11
12
|
const userfeedback = require('./userfeedback.js');
|
|
@@ -39,6 +40,7 @@ const integration$4 = require('./integrations/featureFlags/growthbook/integratio
|
|
|
39
40
|
const integration$5 = require('./integrations/featureFlags/statsig/integration.js');
|
|
40
41
|
const diagnoseSdk = require('./diagnose-sdk.js');
|
|
41
42
|
const webWorker = require('./integrations/webWorker.js');
|
|
43
|
+
const spanstreaming = require('./integrations/spanstreaming.js');
|
|
42
44
|
|
|
43
45
|
|
|
44
46
|
|
|
@@ -120,10 +122,12 @@ exports.updateSpanName = core.updateSpanName;
|
|
|
120
122
|
exports.withActiveSpan = core.withActiveSpan;
|
|
121
123
|
exports.withIsolationScope = core.withIsolationScope;
|
|
122
124
|
exports.withScope = core.withScope;
|
|
125
|
+
exports.withStreamSpan = core.withStreamSpan;
|
|
123
126
|
exports.zodErrorsIntegration = core.zodErrorsIntegration;
|
|
124
127
|
exports.WINDOW = helpers.WINDOW;
|
|
125
128
|
exports.BrowserClient = client.BrowserClient;
|
|
126
129
|
exports.makeFetchTransport = fetch.makeFetchTransport;
|
|
130
|
+
exports.uiProfiler = index.uiProfiler;
|
|
127
131
|
exports.chromeStackLineParser = stackParsers.chromeStackLineParser;
|
|
128
132
|
exports.defaultStackLineParsers = stackParsers.defaultStackLineParsers;
|
|
129
133
|
exports.defaultStackParser = stackParsers.defaultStackParser;
|
|
@@ -176,4 +180,5 @@ exports.statsigIntegration = integration$5.statsigIntegration;
|
|
|
176
180
|
exports.diagnoseSdkConnectivity = diagnoseSdk.diagnoseSdkConnectivity;
|
|
177
181
|
exports.registerWebWorker = webWorker.registerWebWorker;
|
|
178
182
|
exports.webWorkerIntegration = webWorker.webWorkerIntegration;
|
|
183
|
+
exports.spanStreamingIntegration = spanstreaming.spanStreamingIntegration;
|
|
179
184
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -10,6 +10,8 @@ const helpers = require('../helpers.js');
|
|
|
10
10
|
const httpContextIntegration = core.defineIntegration(() => {
|
|
11
11
|
return {
|
|
12
12
|
name: 'HttpContext',
|
|
13
|
+
// TODO (span-streaming): probably fine to omit this in favour of us globally
|
|
14
|
+
// already adding request context data but should double-check this
|
|
13
15
|
preprocessEvent(event) {
|
|
14
16
|
// if none of the information we want exists, don't bother
|
|
15
17
|
if (!helpers.WINDOW.navigator && !helpers.WINDOW.location && !helpers.WINDOW.document) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"httpcontext.js","sources":["../../../../../src/integrations/httpcontext.ts"],"sourcesContent":["import { defineIntegration } from '@sentry/core';\nimport { getHttpRequestData, WINDOW } from '../helpers';\n\n/**\n * Collects information about HTTP request headers and\n * attaches them to the event.\n */\nexport const httpContextIntegration = defineIntegration(() => {\n return {\n name: 'HttpContext',\n preprocessEvent(event) {\n // if none of the information we want exists, don't bother\n if (!WINDOW.navigator && !WINDOW.location && !WINDOW.document) {\n return;\n }\n\n const reqData = getHttpRequestData();\n const headers = {\n ...reqData.headers,\n ...event.request?.headers,\n };\n\n event.request = {\n ...reqData,\n ...event.request,\n headers,\n };\n },\n };\n});\n"],"names":["defineIntegration","WINDOW","getHttpRequestData"],"mappings":";;;;;AAGA;AACA;AACA;AACA;MACa,sBAAA,GAAyBA,sBAAiB,CAAC,MAAM;AAC9D,EAAE,OAAO;AACT,IAAI,IAAI,EAAE,aAAa;AACvB,IAAI,eAAe,CAAC,KAAK,EAAE;AAC3B;AACA,MAAM,IAAI,CAACC,cAAM,CAAC,aAAa,CAACA,cAAM,CAAC,YAAY,CAACA,cAAM,CAAC,QAAQ,EAAE;AACrE,QAAQ;AACR,MAAM;;AAEN,MAAM,MAAM,OAAA,GAAUC,0BAAkB,EAAE;AAC1C,MAAM,MAAM,UAAU;AACtB,QAAQ,GAAG,OAAO,CAAC,OAAO;AAC1B,QAAQ,GAAG,KAAK,CAAC,OAAO,EAAE,OAAO;AACjC,OAAO;;AAEP,MAAM,KAAK,CAAC,OAAA,GAAU;AACtB,QAAQ,GAAG,OAAO;AAClB,QAAQ,GAAG,KAAK,CAAC,OAAO;AACxB,QAAQ,OAAO;AACf,OAAO;AACP,IAAI,CAAC;AACL,GAAG;AACH,CAAC;;;;"}
|
|
1
|
+
{"version":3,"file":"httpcontext.js","sources":["../../../../../src/integrations/httpcontext.ts"],"sourcesContent":["import { defineIntegration } from '@sentry/core';\nimport { getHttpRequestData, WINDOW } from '../helpers';\n\n/**\n * Collects information about HTTP request headers and\n * attaches them to the event.\n */\nexport const httpContextIntegration = defineIntegration(() => {\n return {\n name: 'HttpContext',\n // TODO (span-streaming): probably fine to omit this in favour of us globally\n // already adding request context data but should double-check this\n preprocessEvent(event) {\n // if none of the information we want exists, don't bother\n if (!WINDOW.navigator && !WINDOW.location && !WINDOW.document) {\n return;\n }\n\n const reqData = getHttpRequestData();\n const headers = {\n ...reqData.headers,\n ...event.request?.headers,\n };\n\n event.request = {\n ...reqData,\n ...event.request,\n headers,\n };\n },\n };\n});\n"],"names":["defineIntegration","WINDOW","getHttpRequestData"],"mappings":";;;;;AAGA;AACA;AACA;AACA;MACa,sBAAA,GAAyBA,sBAAiB,CAAC,MAAM;AAC9D,EAAE,OAAO;AACT,IAAI,IAAI,EAAE,aAAa;AACvB;AACA;AACA,IAAI,eAAe,CAAC,KAAK,EAAE;AAC3B;AACA,MAAM,IAAI,CAACC,cAAM,CAAC,aAAa,CAACA,cAAM,CAAC,YAAY,CAACA,cAAM,CAAC,QAAQ,EAAE;AACrE,QAAQ;AACR,MAAM;;AAEN,MAAM,MAAM,OAAA,GAAUC,0BAAkB,EAAE;AAC1C,MAAM,MAAM,UAAU;AACtB,QAAQ,GAAG,OAAO,CAAC,OAAO;AAC1B,QAAQ,GAAG,KAAK,CAAC,OAAO,EAAE,OAAO;AACjC,OAAO;;AAEP,MAAM,KAAK,CAAC,OAAA,GAAU;AACtB,QAAQ,GAAG,OAAO;AAClB,QAAQ,GAAG,KAAK,CAAC,OAAO;AACxB,QAAQ,OAAO;AACf,OAAO;AACP,IAAI,CAAC;AACL,GAAG;AACH,CAAC;;;;"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
const core = require('@sentry/core');
|
|
4
|
+
const debugBuild = require('../debug-build.js');
|
|
5
|
+
|
|
6
|
+
const spanStreamingIntegration = core.defineIntegration(((userOptions) => {
|
|
7
|
+
const validatedUserProvidedBatchLimit =
|
|
8
|
+
userOptions?.batchLimit && userOptions.batchLimit <= 1000 && userOptions.batchLimit >= 1
|
|
9
|
+
? userOptions.batchLimit
|
|
10
|
+
: undefined;
|
|
11
|
+
|
|
12
|
+
if (debugBuild.DEBUG_BUILD && userOptions?.batchLimit && !validatedUserProvidedBatchLimit) {
|
|
13
|
+
core.debug.warn('SpanStreaming batchLimit must be between 1 and 1000, defaulting to 1000');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const options = {
|
|
17
|
+
...userOptions,
|
|
18
|
+
batchLimit:
|
|
19
|
+
userOptions?.batchLimit && userOptions.batchLimit <= 1000 && userOptions.batchLimit >= 1
|
|
20
|
+
? userOptions.batchLimit
|
|
21
|
+
: 1000,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// key: traceId-segmentSpanId
|
|
25
|
+
const spanTreeMap = new Map();
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
name: 'SpanStreaming',
|
|
29
|
+
setup(client) {
|
|
30
|
+
const clientOptions = client.getOptions();
|
|
31
|
+
const beforeSendSpan = clientOptions.beforeSendSpan;
|
|
32
|
+
|
|
33
|
+
const initialMessage = 'spanStreamingIntegration requires';
|
|
34
|
+
const fallbackMsg = 'Falling back to static trace lifecycle.';
|
|
35
|
+
|
|
36
|
+
if (clientOptions.traceLifecycle !== 'stream') {
|
|
37
|
+
debugBuild.DEBUG_BUILD && core.debug.warn(`${initialMessage} \`traceLifecycle\` to be set to "stream"! ${fallbackMsg}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (beforeSendSpan && !core.isV2BeforeSendSpanCallback(beforeSendSpan)) {
|
|
42
|
+
client.getOptions().traceLifecycle = 'static';
|
|
43
|
+
core.debug.warn(`${initialMessage} a beforeSendSpan callback using \`withStreamSpan\`! ${fallbackMsg}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
client.on('spanEnd', span => {
|
|
48
|
+
const spanTreeMapKey = getSpanTreeMapKey(span);
|
|
49
|
+
const spanBuffer = spanTreeMap.get(spanTreeMapKey);
|
|
50
|
+
if (spanBuffer) {
|
|
51
|
+
spanBuffer.add(span);
|
|
52
|
+
} else {
|
|
53
|
+
spanTreeMap.set(spanTreeMapKey, new Set([span]));
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// For now, we send all spans on local segment (root) span end.
|
|
58
|
+
// TODO: This will change once we have more concrete ideas about a universal SDK data buffer.
|
|
59
|
+
client.on('segmentSpanEnd', segmentSpan => {
|
|
60
|
+
processAndSendSpans(segmentSpan, {
|
|
61
|
+
spanTreeMap: spanTreeMap,
|
|
62
|
+
client,
|
|
63
|
+
batchLimit: options.batchLimit,
|
|
64
|
+
beforeSendSpan,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}) );
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Just the traceid alone isn't enough because there can be multiple span trees with the same traceid.
|
|
73
|
+
*/
|
|
74
|
+
function getSpanTreeMapKey(span) {
|
|
75
|
+
return `${span.spanContext().traceId}-${core.INTERNAL_getSegmentSpan(span).spanContext().spanId}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function processAndSendSpans(
|
|
79
|
+
segmentSpan,
|
|
80
|
+
{ client, spanTreeMap, batchLimit, beforeSendSpan },
|
|
81
|
+
) {
|
|
82
|
+
const traceId = segmentSpan.spanContext().traceId;
|
|
83
|
+
const spanTreeMapKey = getSpanTreeMapKey(segmentSpan);
|
|
84
|
+
const spansOfTrace = spanTreeMap.get(spanTreeMapKey);
|
|
85
|
+
|
|
86
|
+
if (!spansOfTrace?.size) {
|
|
87
|
+
spanTreeMap.delete(spanTreeMapKey);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const segmentSpanJson = core.spanToV2JSON(segmentSpan);
|
|
92
|
+
|
|
93
|
+
for (const span of spansOfTrace) {
|
|
94
|
+
applyCommonSpanAttributes(span, segmentSpanJson, client);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const { ignoreSpans } = client.getOptions();
|
|
98
|
+
|
|
99
|
+
// 1. Check if the entire span tree is ignored by ignoreSpans
|
|
100
|
+
if (ignoreSpans?.length && core.shouldIgnoreSpan(segmentSpanJson, ignoreSpans)) {
|
|
101
|
+
client.recordDroppedEvent('before_send', 'span', spansOfTrace.size);
|
|
102
|
+
spanTreeMap.delete(spanTreeMapKey);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const serializedSpans = Array.from(spansOfTrace ?? []).map(s => {
|
|
107
|
+
const serialized = core.spanToV2JSON(s);
|
|
108
|
+
// remove internal span attributes we don't need to send.
|
|
109
|
+
delete serialized.attributes?.[core.SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME];
|
|
110
|
+
return serialized;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const processedSpans = [];
|
|
114
|
+
let ignoredSpanCount = 0;
|
|
115
|
+
|
|
116
|
+
for (const span of serializedSpans) {
|
|
117
|
+
// 2. Check if child spans should be ignored
|
|
118
|
+
const isChildSpan = span.span_id !== segmentSpan.spanContext().spanId;
|
|
119
|
+
if (ignoreSpans?.length && isChildSpan && core.shouldIgnoreSpan(span, ignoreSpans)) {
|
|
120
|
+
core.reparentChildSpans(serializedSpans, span);
|
|
121
|
+
ignoredSpanCount++;
|
|
122
|
+
// drop this span by not adding it to the processedSpans array
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 3. Apply beforeSendSpan callback
|
|
127
|
+
// TODO: validate beforeSendSpan result/pass in a copy and merge afterwards
|
|
128
|
+
const processedSpan = beforeSendSpan ? applyBeforeSendSpanCallback(span, beforeSendSpan) : span;
|
|
129
|
+
processedSpans.push(processedSpan);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (ignoredSpanCount) {
|
|
133
|
+
client.recordDroppedEvent('before_send', 'span', ignoredSpanCount);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const batches = [];
|
|
137
|
+
for (let i = 0; i < processedSpans.length; i += batchLimit) {
|
|
138
|
+
batches.push(processedSpans.slice(i, i + batchLimit));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
debugBuild.DEBUG_BUILD && core.debug.log(`Sending trace ${traceId} in ${batches.length} batch${batches.length === 1 ? '' : 'es'}`);
|
|
142
|
+
|
|
143
|
+
const dsc = core.getDynamicSamplingContextFromSpan(segmentSpan);
|
|
144
|
+
|
|
145
|
+
for (const batch of batches) {
|
|
146
|
+
const envelope = core.createSpanV2Envelope(batch, dsc, client);
|
|
147
|
+
// no need to handle client reports for network errors,
|
|
148
|
+
// buffer overflows or rate limiting here. All of this is handled
|
|
149
|
+
// by client and transport.
|
|
150
|
+
client.sendEnvelope(envelope).then(null, reason => {
|
|
151
|
+
debugBuild.DEBUG_BUILD && core.debug.error('Error while sending span stream envelope:', reason);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
spanTreeMap.delete(spanTreeMapKey);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function applyCommonSpanAttributes(span, serializedSegmentSpan, client) {
|
|
159
|
+
const sdk = client.getSdkMetadata();
|
|
160
|
+
const { release, environment, sendDefaultPii } = client.getOptions();
|
|
161
|
+
|
|
162
|
+
const { isolationScope: spanIsolationScope, scope: spanScope } = core.getCapturedScopesOnSpan(span);
|
|
163
|
+
|
|
164
|
+
const originalAttributeKeys = Object.keys(core.spanToV2JSON(span).attributes ?? {});
|
|
165
|
+
|
|
166
|
+
const finalScopeData = getFinalScopeData(spanIsolationScope, spanScope);
|
|
167
|
+
|
|
168
|
+
// avoid overwriting any previously set attributes (from users or potentially our SDK instrumentation)
|
|
169
|
+
setAttributesIfNotPresent(span, originalAttributeKeys, {
|
|
170
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_RELEASE]: release,
|
|
171
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT]: environment,
|
|
172
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME]: serializedSegmentSpan.name,
|
|
173
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID]: serializedSegmentSpan.span_id,
|
|
174
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME]: sdk?.sdk?.name,
|
|
175
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION]: sdk?.sdk?.version,
|
|
176
|
+
...(sendDefaultPii
|
|
177
|
+
? {
|
|
178
|
+
[core.SEMANTIC_ATTRIBUTE_USER_ID]: finalScopeData.user?.id,
|
|
179
|
+
[core.SEMANTIC_ATTRIBUTE_USER_EMAIL]: finalScopeData.user?.email,
|
|
180
|
+
[core.SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS]: finalScopeData.user?.ip_address ?? undefined,
|
|
181
|
+
[core.SEMANTIC_ATTRIBUTE_USER_USERNAME]: finalScopeData.user?.username,
|
|
182
|
+
}
|
|
183
|
+
: {}),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function applyBeforeSendSpanCallback(span, beforeSendSpan) {
|
|
188
|
+
const modifedSpan = beforeSendSpan(span);
|
|
189
|
+
if (!modifedSpan) {
|
|
190
|
+
core.showSpanDropWarning();
|
|
191
|
+
return span;
|
|
192
|
+
}
|
|
193
|
+
return modifedSpan;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function setAttributesIfNotPresent(span, originalAttributeKeys, newAttributes) {
|
|
197
|
+
Object.keys(newAttributes).forEach(key => {
|
|
198
|
+
if (!originalAttributeKeys.includes(key)) {
|
|
199
|
+
span.setAttribute(key, newAttributes[key]);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// TODO: Extract this to a helper in core. It's used in multiple places.
|
|
205
|
+
function getFinalScopeData(isolationScope, scope) {
|
|
206
|
+
const finalScopeData = core.getGlobalScope().getScopeData();
|
|
207
|
+
if (isolationScope) {
|
|
208
|
+
core.mergeScopeData(finalScopeData, isolationScope.getScopeData());
|
|
209
|
+
}
|
|
210
|
+
if (scope) {
|
|
211
|
+
core.mergeScopeData(finalScopeData, scope.getScopeData());
|
|
212
|
+
}
|
|
213
|
+
return finalScopeData;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
exports.spanStreamingIntegration = spanStreamingIntegration;
|
|
217
|
+
//# sourceMappingURL=spanstreaming.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spanstreaming.js","sources":["../../../../../src/integrations/spanstreaming.ts"],"sourcesContent":["import type { Client, IntegrationFn, Scope, ScopeData, Span, SpanAttributes, SpanV2JSON } from '@sentry/core';\nimport {\n createSpanV2Envelope,\n debug,\n defineIntegration,\n getCapturedScopesOnSpan,\n getDynamicSamplingContextFromSpan,\n getGlobalScope,\n INTERNAL_getSegmentSpan,\n isV2BeforeSendSpanCallback,\n mergeScopeData,\n reparentChildSpans,\n SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME,\n SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT,\n SEMANTIC_ATTRIBUTE_SENTRY_RELEASE,\n SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME,\n SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION,\n SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID,\n SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME,\n SEMANTIC_ATTRIBUTE_USER_EMAIL,\n SEMANTIC_ATTRIBUTE_USER_ID,\n SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS,\n SEMANTIC_ATTRIBUTE_USER_USERNAME,\n shouldIgnoreSpan,\n showSpanDropWarning,\n spanToV2JSON,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../debug-build';\n\nexport interface SpanStreamingOptions {\n batchLimit: number;\n}\n\nexport const spanStreamingIntegration = defineIntegration(((userOptions?: Partial<SpanStreamingOptions>) => {\n const validatedUserProvidedBatchLimit =\n userOptions?.batchLimit && userOptions.batchLimit <= 1000 && userOptions.batchLimit >= 1\n ? userOptions.batchLimit\n : undefined;\n\n if (DEBUG_BUILD && userOptions?.batchLimit && !validatedUserProvidedBatchLimit) {\n debug.warn('SpanStreaming batchLimit must be between 1 and 1000, defaulting to 1000');\n }\n\n const options: SpanStreamingOptions = {\n ...userOptions,\n batchLimit:\n userOptions?.batchLimit && userOptions.batchLimit <= 1000 && userOptions.batchLimit >= 1\n ? userOptions.batchLimit\n : 1000,\n };\n\n // key: traceId-segmentSpanId\n const spanTreeMap = new Map<string, Set<Span>>();\n\n return {\n name: 'SpanStreaming',\n setup(client) {\n const clientOptions = client.getOptions();\n const beforeSendSpan = clientOptions.beforeSendSpan;\n\n const initialMessage = 'spanStreamingIntegration requires';\n const fallbackMsg = 'Falling back to static trace lifecycle.';\n\n if (clientOptions.traceLifecycle !== 'stream') {\n DEBUG_BUILD && debug.warn(`${initialMessage} \\`traceLifecycle\\` to be set to \"stream\"! ${fallbackMsg}`);\n return;\n }\n\n if (beforeSendSpan && !isV2BeforeSendSpanCallback(beforeSendSpan)) {\n client.getOptions().traceLifecycle = 'static';\n debug.warn(`${initialMessage} a beforeSendSpan callback using \\`withStreamSpan\\`! ${fallbackMsg}`);\n return;\n }\n\n client.on('spanEnd', span => {\n const spanTreeMapKey = getSpanTreeMapKey(span);\n const spanBuffer = spanTreeMap.get(spanTreeMapKey);\n if (spanBuffer) {\n spanBuffer.add(span);\n } else {\n spanTreeMap.set(spanTreeMapKey, new Set([span]));\n }\n });\n\n // For now, we send all spans on local segment (root) span end.\n // TODO: This will change once we have more concrete ideas about a universal SDK data buffer.\n client.on('segmentSpanEnd', segmentSpan => {\n processAndSendSpans(segmentSpan, {\n spanTreeMap: spanTreeMap,\n client,\n batchLimit: options.batchLimit,\n beforeSendSpan,\n });\n });\n },\n };\n}) satisfies IntegrationFn);\n\ninterface SpanProcessingOptions {\n client: Client;\n spanTreeMap: Map<string, Set<Span>>;\n batchLimit: number;\n beforeSendSpan: ((span: SpanV2JSON) => SpanV2JSON) | undefined;\n}\n\n/**\n * Just the traceid alone isn't enough because there can be multiple span trees with the same traceid.\n */\nfunction getSpanTreeMapKey(span: Span): string {\n return `${span.spanContext().traceId}-${INTERNAL_getSegmentSpan(span).spanContext().spanId}`;\n}\n\nfunction processAndSendSpans(\n segmentSpan: Span,\n { client, spanTreeMap, batchLimit, beforeSendSpan }: SpanProcessingOptions,\n): void {\n const traceId = segmentSpan.spanContext().traceId;\n const spanTreeMapKey = getSpanTreeMapKey(segmentSpan);\n const spansOfTrace = spanTreeMap.get(spanTreeMapKey);\n\n if (!spansOfTrace?.size) {\n spanTreeMap.delete(spanTreeMapKey);\n return;\n }\n\n const segmentSpanJson = spanToV2JSON(segmentSpan);\n\n for (const span of spansOfTrace) {\n applyCommonSpanAttributes(span, segmentSpanJson, client);\n }\n\n const { ignoreSpans } = client.getOptions();\n\n // 1. Check if the entire span tree is ignored by ignoreSpans\n if (ignoreSpans?.length && shouldIgnoreSpan(segmentSpanJson, ignoreSpans)) {\n client.recordDroppedEvent('before_send', 'span', spansOfTrace.size);\n spanTreeMap.delete(spanTreeMapKey);\n return;\n }\n\n const serializedSpans = Array.from(spansOfTrace ?? []).map(s => {\n const serialized = spanToV2JSON(s);\n // remove internal span attributes we don't need to send.\n delete serialized.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME];\n return serialized;\n });\n\n const processedSpans = [];\n let ignoredSpanCount = 0;\n\n for (const span of serializedSpans) {\n // 2. Check if child spans should be ignored\n const isChildSpan = span.span_id !== segmentSpan.spanContext().spanId;\n if (ignoreSpans?.length && isChildSpan && shouldIgnoreSpan(span, ignoreSpans)) {\n reparentChildSpans(serializedSpans, span);\n ignoredSpanCount++;\n // drop this span by not adding it to the processedSpans array\n continue;\n }\n\n // 3. Apply beforeSendSpan callback\n // TODO: validate beforeSendSpan result/pass in a copy and merge afterwards\n const processedSpan = beforeSendSpan ? applyBeforeSendSpanCallback(span, beforeSendSpan) : span;\n processedSpans.push(processedSpan);\n }\n\n if (ignoredSpanCount) {\n client.recordDroppedEvent('before_send', 'span', ignoredSpanCount);\n }\n\n const batches: SpanV2JSON[][] = [];\n for (let i = 0; i < processedSpans.length; i += batchLimit) {\n batches.push(processedSpans.slice(i, i + batchLimit));\n }\n\n DEBUG_BUILD && debug.log(`Sending trace ${traceId} in ${batches.length} batch${batches.length === 1 ? '' : 'es'}`);\n\n const dsc = getDynamicSamplingContextFromSpan(segmentSpan);\n\n for (const batch of batches) {\n const envelope = createSpanV2Envelope(batch, dsc, client);\n // no need to handle client reports for network errors,\n // buffer overflows or rate limiting here. All of this is handled\n // by client and transport.\n client.sendEnvelope(envelope).then(null, reason => {\n DEBUG_BUILD && debug.error('Error while sending span stream envelope:', reason);\n });\n }\n\n spanTreeMap.delete(spanTreeMapKey);\n}\n\nfunction applyCommonSpanAttributes(span: Span, serializedSegmentSpan: SpanV2JSON, client: Client): void {\n const sdk = client.getSdkMetadata();\n const { release, environment, sendDefaultPii } = client.getOptions();\n\n const { isolationScope: spanIsolationScope, scope: spanScope } = getCapturedScopesOnSpan(span);\n\n const originalAttributeKeys = Object.keys(spanToV2JSON(span).attributes ?? {});\n\n const finalScopeData = getFinalScopeData(spanIsolationScope, spanScope);\n\n // avoid overwriting any previously set attributes (from users or potentially our SDK instrumentation)\n setAttributesIfNotPresent(span, originalAttributeKeys, {\n [SEMANTIC_ATTRIBUTE_SENTRY_RELEASE]: release,\n [SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT]: environment,\n [SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME]: serializedSegmentSpan.name,\n [SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID]: serializedSegmentSpan.span_id,\n [SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME]: sdk?.sdk?.name,\n [SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION]: sdk?.sdk?.version,\n ...(sendDefaultPii\n ? {\n [SEMANTIC_ATTRIBUTE_USER_ID]: finalScopeData.user?.id,\n [SEMANTIC_ATTRIBUTE_USER_EMAIL]: finalScopeData.user?.email,\n [SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS]: finalScopeData.user?.ip_address ?? undefined,\n [SEMANTIC_ATTRIBUTE_USER_USERNAME]: finalScopeData.user?.username,\n }\n : {}),\n });\n}\n\nfunction applyBeforeSendSpanCallback(span: SpanV2JSON, beforeSendSpan: (span: SpanV2JSON) => SpanV2JSON): SpanV2JSON {\n const modifedSpan = beforeSendSpan(span);\n if (!modifedSpan) {\n showSpanDropWarning();\n return span;\n }\n return modifedSpan;\n}\n\nfunction setAttributesIfNotPresent(span: Span, originalAttributeKeys: string[], newAttributes: SpanAttributes): void {\n Object.keys(newAttributes).forEach(key => {\n if (!originalAttributeKeys.includes(key)) {\n span.setAttribute(key, newAttributes[key]);\n }\n });\n}\n\n// TODO: Extract this to a helper in core. It's used in multiple places.\nfunction getFinalScopeData(isolationScope: Scope | undefined, scope: Scope | undefined): ScopeData {\n const finalScopeData = getGlobalScope().getScopeData();\n if (isolationScope) {\n mergeScopeData(finalScopeData, isolationScope.getScopeData());\n }\n if (scope) {\n mergeScopeData(finalScopeData, scope.getScopeData());\n }\n return finalScopeData;\n}\n"],"names":["defineIntegration","DEBUG_BUILD","debug","isV2BeforeSendSpanCallback","INTERNAL_getSegmentSpan","spanToV2JSON","shouldIgnoreSpan","SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME","reparentChildSpans","getDynamicSamplingContextFromSpan","createSpanV2Envelope","getCapturedScopesOnSpan","SEMANTIC_ATTRIBUTE_SENTRY_RELEASE","SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT","SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME","SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID","SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME","SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION","SEMANTIC_ATTRIBUTE_USER_ID","SEMANTIC_ATTRIBUTE_USER_EMAIL","SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS","SEMANTIC_ATTRIBUTE_USER_USERNAME","showSpanDropWarning","getGlobalScope","mergeScopeData"],"mappings":"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"}
|
|
@@ -6,55 +6,109 @@ const utils = require('./utils.js');
|
|
|
6
6
|
|
|
7
7
|
const CHUNK_INTERVAL_MS = 60000; // 1 minute
|
|
8
8
|
// Maximum length for trace lifecycle profiling per root span (e.g. if spanEnd never fires)
|
|
9
|
-
const MAX_ROOT_SPAN_PROFILE_MS = 300000; // 5 minutes
|
|
9
|
+
const MAX_ROOT_SPAN_PROFILE_MS = 300000; // 5 minutes max per root span in trace mode
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* UIProfiler (Profiling V2):
|
|
13
|
+
* Supports two lifecycle modes:
|
|
14
|
+
* - 'manual': controlled explicitly via start()/stop()
|
|
15
|
+
* - 'trace': automatically runs while there are active sampled root spans
|
|
16
16
|
*
|
|
17
17
|
* Profiles are emitted as standalone `profile_chunk` envelopes either when:
|
|
18
18
|
* - there are no more sampled root spans, or
|
|
19
19
|
* - the 60s chunk timer elapses while profiling is running.
|
|
20
20
|
*/
|
|
21
|
-
class UIProfiler
|
|
21
|
+
class UIProfiler {
|
|
22
22
|
|
|
23
|
-
//
|
|
23
|
+
// Manual + Trace
|
|
24
|
+
// one per Profiler session
|
|
25
|
+
// current profiler instance active flag
|
|
26
|
+
// sampling decision for entire session
|
|
24
27
|
|
|
25
|
-
//
|
|
28
|
+
// Trace-only
|
|
26
29
|
|
|
27
30
|
constructor() {
|
|
28
31
|
this._client = undefined;
|
|
29
32
|
this._profiler = undefined;
|
|
30
33
|
this._chunkTimer = undefined;
|
|
31
|
-
|
|
32
|
-
this._rootSpanTimeouts = new Map();
|
|
34
|
+
|
|
33
35
|
this._profilerId = undefined;
|
|
34
36
|
this._isRunning = false;
|
|
35
37
|
this._sessionSampled = false;
|
|
38
|
+
this._lifecycleMode = undefined;
|
|
39
|
+
|
|
40
|
+
this._activeRootSpanIds = new Set();
|
|
41
|
+
this._rootSpanTimeouts = new Map();
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
/**
|
|
39
|
-
* Initialize the profiler with client
|
|
45
|
+
* Initialize the profiler with client, session sampling and lifecycle mode.
|
|
40
46
|
*/
|
|
41
|
-
initialize(client
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
initialize(client) {
|
|
48
|
+
const lifecycleMode = (client.getOptions() ).profileLifecycle;
|
|
49
|
+
const sessionSampled = utils.shouldProfileSession(client.getOptions());
|
|
50
|
+
|
|
51
|
+
debugBuild.DEBUG_BUILD && core.debug.log(`[Profiling] Initializing profiler (lifecycle='${lifecycleMode}').`);
|
|
44
52
|
|
|
45
|
-
|
|
53
|
+
if (!sessionSampled) {
|
|
54
|
+
debugBuild.DEBUG_BUILD && core.debug.log('[Profiling] Session not sampled. Skipping lifecycle profiler initialization.');
|
|
55
|
+
}
|
|
46
56
|
|
|
57
|
+
// One Profiler ID per profiling session (user session)
|
|
58
|
+
this._profilerId = core.uuid4();
|
|
47
59
|
this._client = client;
|
|
48
60
|
this._sessionSampled = sessionSampled;
|
|
61
|
+
this._lifecycleMode = lifecycleMode;
|
|
49
62
|
|
|
50
|
-
|
|
63
|
+
if (lifecycleMode === 'trace') {
|
|
64
|
+
this._setupTraceLifecycleListeners(client);
|
|
65
|
+
}
|
|
51
66
|
}
|
|
52
67
|
|
|
53
|
-
/**
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
68
|
+
/** Starts UI profiling (only effective in 'manual' mode and when sampled). */
|
|
69
|
+
start() {
|
|
70
|
+
if (this._lifecycleMode === 'trace') {
|
|
71
|
+
debugBuild.DEBUG_BUILD &&
|
|
72
|
+
core.debug.warn(
|
|
73
|
+
'[Profiling] `profileLifecycle` is set to "trace". Calls to `uiProfiler.start()` are ignored in trace mode.',
|
|
74
|
+
);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (this._isRunning) {
|
|
79
|
+
debugBuild.DEBUG_BUILD && core.debug.warn('[Profiling] Profile session is already running, `uiProfiler.start()` is a no-op.');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
57
83
|
if (!this._sessionSampled) {
|
|
84
|
+
debugBuild.DEBUG_BUILD && core.debug.warn('[Profiling] Session is not sampled, `uiProfiler.start()` is a no-op.');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this._beginProfiling();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Stops UI profiling (only effective in 'manual' mode). */
|
|
92
|
+
stop() {
|
|
93
|
+
if (this._lifecycleMode === 'trace') {
|
|
94
|
+
debugBuild.DEBUG_BUILD &&
|
|
95
|
+
core.debug.warn(
|
|
96
|
+
'[Profiling] `profileLifecycle` is set to "trace". Calls to `uiProfiler.stop()` are ignored in trace mode.',
|
|
97
|
+
);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!this._isRunning) {
|
|
102
|
+
debugBuild.DEBUG_BUILD && core.debug.warn('[Profiling] Profiler is not running, `uiProfiler.stop()` is a no-op.');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this._endProfiling();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Handle an already-active root span at integration setup time (used only in trace mode). */
|
|
110
|
+
notifyRootSpanActive(rootSpan) {
|
|
111
|
+
if (this._lifecycleMode !== 'trace' || !this._sessionSampled) {
|
|
58
112
|
return;
|
|
59
113
|
}
|
|
60
114
|
|
|
@@ -63,7 +117,7 @@ class UIProfiler {
|
|
|
63
117
|
return;
|
|
64
118
|
}
|
|
65
119
|
|
|
66
|
-
this.
|
|
120
|
+
this._registerTraceRootSpan(spanId);
|
|
67
121
|
|
|
68
122
|
const rootSpanCount = this._activeRootSpanIds.size;
|
|
69
123
|
|
|
@@ -71,20 +125,20 @@ class UIProfiler {
|
|
|
71
125
|
debugBuild.DEBUG_BUILD &&
|
|
72
126
|
core.debug.log('[Profiling] Detected already active root span during setup. Active root spans now:', rootSpanCount);
|
|
73
127
|
|
|
74
|
-
this.
|
|
128
|
+
this._beginProfiling();
|
|
75
129
|
}
|
|
76
130
|
}
|
|
77
131
|
|
|
78
132
|
/**
|
|
79
|
-
*
|
|
133
|
+
* Begin profiling if not already running.
|
|
80
134
|
*/
|
|
81
|
-
|
|
135
|
+
_beginProfiling() {
|
|
82
136
|
if (this._isRunning) {
|
|
83
137
|
return;
|
|
84
138
|
}
|
|
85
139
|
this._isRunning = true;
|
|
86
140
|
|
|
87
|
-
debugBuild.DEBUG_BUILD && core.debug.log('[Profiling] Started profiling with
|
|
141
|
+
debugBuild.DEBUG_BUILD && core.debug.log('[Profiling] Started profiling with profiler ID:', this._profilerId);
|
|
88
142
|
|
|
89
143
|
// Expose profiler_id to match root spans with profiles
|
|
90
144
|
core.getGlobalScope().setContext('profile', { profiler_id: this._profilerId });
|
|
@@ -92,7 +146,7 @@ class UIProfiler {
|
|
|
92
146
|
this._startProfilerInstance();
|
|
93
147
|
|
|
94
148
|
if (!this._profiler) {
|
|
95
|
-
debugBuild.DEBUG_BUILD && core.debug.log('[Profiling]
|
|
149
|
+
debugBuild.DEBUG_BUILD && core.debug.log('[Profiling] Failed to start JS Profiler; stopping.');
|
|
96
150
|
this._resetProfilerInfo();
|
|
97
151
|
return;
|
|
98
152
|
}
|
|
@@ -100,15 +154,13 @@ class UIProfiler {
|
|
|
100
154
|
this._startPeriodicChunking();
|
|
101
155
|
}
|
|
102
156
|
|
|
103
|
-
/**
|
|
104
|
-
|
|
105
|
-
*/
|
|
106
|
-
stop() {
|
|
157
|
+
/** End profiling session; final chunk will be collected and sent. */
|
|
158
|
+
_endProfiling() {
|
|
107
159
|
if (!this._isRunning) {
|
|
108
160
|
return;
|
|
109
161
|
}
|
|
110
|
-
|
|
111
162
|
this._isRunning = false;
|
|
163
|
+
|
|
112
164
|
if (this._chunkTimer) {
|
|
113
165
|
clearTimeout(this._chunkTimer);
|
|
114
166
|
this._chunkTimer = undefined;
|
|
@@ -120,6 +172,12 @@ class UIProfiler {
|
|
|
120
172
|
this._collectCurrentChunk().catch(e => {
|
|
121
173
|
debugBuild.DEBUG_BUILD && core.debug.error('[Profiling] Failed to collect current profile chunk on `stop()`:', e);
|
|
122
174
|
});
|
|
175
|
+
|
|
176
|
+
// Manual: Clear profiling context so spans outside start()/stop() aren't marked as profiled
|
|
177
|
+
// Trace: Profile context is kept for the whole session duration
|
|
178
|
+
if (this._lifecycleMode === 'manual') {
|
|
179
|
+
core.getGlobalScope().setContext('profile', {});
|
|
180
|
+
}
|
|
123
181
|
}
|
|
124
182
|
|
|
125
183
|
/** Trace-mode: attach spanStart/spanEnd listeners. */
|
|
@@ -151,7 +209,7 @@ class UIProfiler {
|
|
|
151
209
|
core.debug.log(
|
|
152
210
|
`[Profiling] Root span ${spanId} started. Profiling active while there are active root spans (count=${rootSpanCount}).`,
|
|
153
211
|
);
|
|
154
|
-
this.
|
|
212
|
+
this._beginProfiling();
|
|
155
213
|
}
|
|
156
214
|
});
|
|
157
215
|
|
|
@@ -174,13 +232,13 @@ class UIProfiler {
|
|
|
174
232
|
this._collectCurrentChunk().catch(e => {
|
|
175
233
|
debugBuild.DEBUG_BUILD && core.debug.error('[Profiling] Failed to collect current profile chunk on last `spanEnd`:', e);
|
|
176
234
|
});
|
|
177
|
-
this.
|
|
235
|
+
this._endProfiling();
|
|
178
236
|
}
|
|
179
237
|
});
|
|
180
238
|
}
|
|
181
239
|
|
|
182
240
|
/**
|
|
183
|
-
* Resets profiling information from scope and resets running state
|
|
241
|
+
* Resets profiling information from scope and resets running state (used on failure)
|
|
184
242
|
*/
|
|
185
243
|
_resetProfilerInfo() {
|
|
186
244
|
this._isRunning = false;
|
|
@@ -195,7 +253,7 @@ class UIProfiler {
|
|
|
195
253
|
this._rootSpanTimeouts.clear();
|
|
196
254
|
}
|
|
197
255
|
|
|
198
|
-
/**
|
|
256
|
+
/** Keep track of root spans and schedule safeguard timeout (trace mode). */
|
|
199
257
|
_registerTraceRootSpan(spanId) {
|
|
200
258
|
this._activeRootSpanIds.add(spanId);
|
|
201
259
|
const timeout = setTimeout(() => this._onRootSpanTimeout(spanId), MAX_ROOT_SPAN_PROFILE_MS);
|
|
@@ -207,11 +265,11 @@ class UIProfiler {
|
|
|
207
265
|
*/
|
|
208
266
|
_startProfilerInstance() {
|
|
209
267
|
if (this._profiler?.stopped === false) {
|
|
210
|
-
return;
|
|
268
|
+
return; // already running
|
|
211
269
|
}
|
|
212
270
|
const profiler = utils.startJSSelfProfile();
|
|
213
271
|
if (!profiler) {
|
|
214
|
-
debugBuild.DEBUG_BUILD && core.debug.log('[Profiling] Failed to start JS Profiler
|
|
272
|
+
debugBuild.DEBUG_BUILD && core.debug.log('[Profiling] Failed to start JS Profiler.');
|
|
215
273
|
return;
|
|
216
274
|
}
|
|
217
275
|
this._profiler = profiler;
|
|
@@ -268,14 +326,13 @@ class UIProfiler {
|
|
|
268
326
|
|
|
269
327
|
this._activeRootSpanIds.delete(rootSpanId);
|
|
270
328
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
this.stop();
|
|
329
|
+
if (this._activeRootSpanIds.size === 0) {
|
|
330
|
+
this._endProfiling();
|
|
274
331
|
}
|
|
275
332
|
}
|
|
276
333
|
|
|
277
334
|
/**
|
|
278
|
-
* Stop
|
|
335
|
+
* Stop current profiler instance, convert profile to chunk & send.
|
|
279
336
|
*/
|
|
280
337
|
async _collectCurrentChunk() {
|
|
281
338
|
const prevProfiler = this._profiler;
|