From 0e4761af646a13cdff2bbe99e0ca661e3d367da2 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 18 Sep 2025 12:56:44 +0200 Subject: [PATCH 1/4] ref(core): Streamling `stripMetadataFromStackFrames` calls --- packages/core/src/client.ts | 11 ++++++++--- packages/core/src/index.ts | 2 +- .../{metadata.ts => moduleMetadata.ts} | 18 +----------------- .../integrations/third-party-errors-filter.ts | 19 +------------------ packages/core/src/metadata.ts | 17 ++++------------- 5 files changed, 15 insertions(+), 52 deletions(-) rename packages/core/src/integrations/{metadata.ts => moduleMetadata.ts} (56%) diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 924bd1810ea3..eb064b9ac5a2 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -6,6 +6,7 @@ import { DEBUG_BUILD } from './debug-build'; import { createEventEnvelope, createSessionEnvelope } from './envelope'; import type { IntegrationIndex } from './integration'; import { afterSetupIntegrations, setupIntegration, setupIntegrations } from './integration'; +import { stripMetadataFromStackFrames } from './metadata'; import type { Scope } from './scope'; import { updateSession } from './session'; import { @@ -1124,9 +1125,13 @@ export abstract class Client { throw _makeDoNotSendEventError(`${beforeSendLabel} returned \`null\`, will not send event.`); } - const session = currentScope.getSession() || isolationScope.getSession(); - if (isError && session) { - this._updateSessionFromEvent(session, processedEvent); + if (isError) { + const session = currentScope.getSession() || isolationScope.getSession(); + if (session) { + this._updateSessionFromEvent(session, processedEvent); + } + + stripMetadataFromStackFrames(processedEvent); } if (isTransaction) { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index b971aa8b43a3..e4071c827daf 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -101,7 +101,7 @@ export { functionToStringIntegration } from './integrations/functiontostring'; export { inboundFiltersIntegration } from './integrations/eventFilters'; export { eventFiltersIntegration } from './integrations/eventFilters'; export { linkedErrorsIntegration } from './integrations/linkederrors'; -export { moduleMetadataIntegration } from './integrations/metadata'; +export { moduleMetadataIntegration } from './integrations/moduleMetadata'; export { requestDataIntegration } from './integrations/requestdata'; export { captureConsoleIntegration } from './integrations/captureconsole'; export { dedupeIntegration } from './integrations/dedupe'; diff --git a/packages/core/src/integrations/metadata.ts b/packages/core/src/integrations/moduleMetadata.ts similarity index 56% rename from packages/core/src/integrations/metadata.ts rename to packages/core/src/integrations/moduleMetadata.ts index 1bbbbdfb9624..177ca7a9d8f1 100644 --- a/packages/core/src/integrations/metadata.ts +++ b/packages/core/src/integrations/moduleMetadata.ts @@ -1,7 +1,5 @@ import { defineIntegration } from '../integration'; -import { addMetadataToStackFrames, stripMetadataFromStackFrames } from '../metadata'; -import type { EventItem } from '../types-hoist/envelope'; -import { forEachEnvelopeItem } from '../utils/envelope'; +import { addMetadataToStackFrames } from '../metadata'; /** * Adds module metadata to stack frames. @@ -16,20 +14,6 @@ export const moduleMetadataIntegration = defineIntegration(() => { return { name: 'ModuleMetadata', setup(client) { - // We need to strip metadata from stack frames before sending them to Sentry since these are client side only. - client.on('beforeEnvelope', envelope => { - forEachEnvelopeItem(envelope, (item, type) => { - if (type === 'event') { - const event = Array.isArray(item) ? (item as EventItem)[1] : undefined; - - if (event) { - stripMetadataFromStackFrames(event); - item[1] = event; - } - } - }); - }); - client.on('applyFrameMetadata', event => { // Only apply stack frame metadata to error events if (event.type) { diff --git a/packages/core/src/integrations/third-party-errors-filter.ts b/packages/core/src/integrations/third-party-errors-filter.ts index 1a0628359f5b..f4af1908ec57 100644 --- a/packages/core/src/integrations/third-party-errors-filter.ts +++ b/packages/core/src/integrations/third-party-errors-filter.ts @@ -1,8 +1,6 @@ import { defineIntegration } from '../integration'; -import { addMetadataToStackFrames, stripMetadataFromStackFrames } from '../metadata'; -import type { EventItem } from '../types-hoist/envelope'; +import { addMetadataToStackFrames } from '../metadata'; import type { Event } from '../types-hoist/event'; -import { forEachEnvelopeItem } from '../utils/envelope'; import { getFramesFromEvent } from '../utils/stacktrace'; interface Options { @@ -41,21 +39,6 @@ export const thirdPartyErrorFilterIntegration = defineIntegration((options: Opti return { name: 'ThirdPartyErrorsFilter', setup(client) { - // We need to strip metadata from stack frames before sending them to Sentry since these are client side only. - // TODO(lforst): Move this cleanup logic into a more central place in the SDK. - client.on('beforeEnvelope', envelope => { - forEachEnvelopeItem(envelope, (item, type) => { - if (type === 'event') { - const event = Array.isArray(item) ? (item as EventItem)[1] : undefined; - - if (event) { - stripMetadataFromStackFrames(event); - item[1] = event; - } - } - }); - }); - client.on('applyFrameMetadata', event => { // Only apply stack frame metadata to error events if (event.type) { diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index 190db6dd55fa..657aecad3cb5 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -81,18 +81,9 @@ export function addMetadataToStackFrames(parser: StackParser, event: Event): voi * Strips metadata from stack frames. */ export function stripMetadataFromStackFrames(event: Event): void { - try { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - event.exception!.values!.forEach(exception => { - if (!exception.stacktrace) { - return; - } - - for (const frame of exception.stacktrace.frames || []) { - delete frame.module_metadata; - } + event.exception?.values?.forEach(exception => { + exception.stacktrace?.frames?.forEach(frame => { + delete frame.module_metadata; }); - } catch { - // To save bundle size we're just try catching here instead of checking for the existence of all the different objects. - } + }); } From fec78dd224fde96da36bbab19b1d397294d963c9 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 18 Sep 2025 13:08:27 +0200 Subject: [PATCH 2/4] revert centralizing to client --- packages/core/src/client.ts | 85 +++++++++---------- .../core/src/integrations/moduleMetadata.ts | 18 +++- .../integrations/third-party-errors-filter.ts | 18 +++- packages/core/src/metadata.ts | 23 ++--- 4 files changed, 80 insertions(+), 64 deletions(-) diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index eb064b9ac5a2..75a0d5ed49d2 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -6,7 +6,6 @@ import { DEBUG_BUILD } from './debug-build'; import { createEventEnvelope, createSessionEnvelope } from './envelope'; import type { IntegrationIndex } from './integration'; import { afterSetupIntegrations, setupIntegration, setupIntegrations } from './integration'; -import { stripMetadataFromStackFrames } from './metadata'; import type { Scope } from './scope'; import { updateSession } from './session'; import { @@ -45,7 +44,7 @@ import { parseSampleRate } from './utils/parseSampleRate'; import { prepareEvent } from './utils/prepareEvent'; import { reparentChildSpans, shouldIgnoreSpan } from './utils/should-ignore-span'; import { getActiveSpan, showSpanDropWarning, spanToTraceContext } from './utils/spanUtils'; -import { rejectedSyncPromise } from './utils/syncpromise'; +import { rejectedSyncPromise, resolvedSyncPromise, SyncPromise } from './utils/syncpromise'; import { convertSpanJsonToTransactionEvent, convertTransactionEventToSpanJson } from './utils/transactionEvent'; const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been captured."; @@ -317,19 +316,16 @@ export abstract class Client { * @returns A promise that will resolve with `true` if all events are sent before the timeout, or `false` if there are * still events in the queue when the timeout is reached. */ - // @ts-expect-error - PromiseLike is a subset of Promise - public async flush(timeout?: number): PromiseLike { + public flush(timeout?: number): PromiseLike { const transport = this._transport; - if (!transport) { - return true; + if (transport) { + this.emit('flush'); + return this._isClientDoneProcessing(timeout).then(clientFinished => { + return transport.flush(timeout).then(transportFlushed => clientFinished && transportFlushed); + }); + } else { + return resolvedSyncPromise(true); } - - this.emit('flush'); - - const clientFinished = await this._isClientDoneProcessing(timeout); - const transportFlushed = await transport.flush(timeout); - - return clientFinished && transportFlushed; } /** @@ -340,12 +336,12 @@ export abstract class Client { * @returns {Promise} A promise which resolves to `true` if the flush completes successfully before the timeout, or `false` if * it doesn't. */ - // @ts-expect-error - PromiseLike is a subset of Promise - public async close(timeout?: number): PromiseLike { - const result = await this.flush(timeout); - this.getOptions().enabled = false; - this.emit('close'); - return result; + public close(timeout?: number): PromiseLike { + return this.flush(timeout).then(result => { + this.getOptions().enabled = false; + this.emit('close'); + return result; + }); } /** @@ -876,21 +872,18 @@ export abstract class Client { /** * Send an envelope to Sentry. */ - // @ts-expect-error - PromiseLike is a subset of Promise - public async sendEnvelope(envelope: Envelope): PromiseLike { + public sendEnvelope(envelope: Envelope): PromiseLike { this.emit('beforeEnvelope', envelope); if (this._isEnabled() && this._transport) { - try { - return await this._transport.send(envelope); - } catch (reason) { + return this._transport.send(envelope).then(null, reason => { DEBUG_BUILD && debug.error('Error while sending envelope:', reason); return {}; - } + }); } DEBUG_BUILD && debug.error('Transport disabled'); - return {}; + return resolvedSyncPromise({}); } /* eslint-enable @typescript-eslint/unified-signatures */ @@ -945,20 +938,24 @@ export abstract class Client { * @returns A promise which will resolve to `true` if processing is already done or finishes before the timeout, and * `false` otherwise */ - protected async _isClientDoneProcessing(timeout?: number): Promise { - let ticked = 0; - - // if no timeout is provided, we wait "forever" until everything is processed - while (!timeout || ticked < timeout) { - await new Promise(resolve => setTimeout(resolve, 1)); + protected _isClientDoneProcessing(timeout?: number): PromiseLike { + return new SyncPromise(resolve => { + let ticked: number = 0; + const tick: number = 1; - if (!this._numProcessing) { - return true; - } - ticked++; - } - - return false; + const interval = setInterval(() => { + if (this._numProcessing == 0) { + clearInterval(interval); + resolve(true); + } else { + ticked += tick; + if (timeout && ticked >= timeout) { + clearInterval(interval); + resolve(false); + } + } + }, tick); + }); } /** Determines whether this SDK is enabled and a transport is present. */ @@ -1125,13 +1122,9 @@ export abstract class Client { throw _makeDoNotSendEventError(`${beforeSendLabel} returned \`null\`, will not send event.`); } - if (isError) { - const session = currentScope.getSession() || isolationScope.getSession(); - if (session) { - this._updateSessionFromEvent(session, processedEvent); - } - - stripMetadataFromStackFrames(processedEvent); + const session = currentScope.getSession() || isolationScope.getSession(); + if (isError && session) { + this._updateSessionFromEvent(session, processedEvent); } if (isTransaction) { diff --git a/packages/core/src/integrations/moduleMetadata.ts b/packages/core/src/integrations/moduleMetadata.ts index 177ca7a9d8f1..1bbbbdfb9624 100644 --- a/packages/core/src/integrations/moduleMetadata.ts +++ b/packages/core/src/integrations/moduleMetadata.ts @@ -1,5 +1,7 @@ import { defineIntegration } from '../integration'; -import { addMetadataToStackFrames } from '../metadata'; +import { addMetadataToStackFrames, stripMetadataFromStackFrames } from '../metadata'; +import type { EventItem } from '../types-hoist/envelope'; +import { forEachEnvelopeItem } from '../utils/envelope'; /** * Adds module metadata to stack frames. @@ -14,6 +16,20 @@ export const moduleMetadataIntegration = defineIntegration(() => { return { name: 'ModuleMetadata', setup(client) { + // We need to strip metadata from stack frames before sending them to Sentry since these are client side only. + client.on('beforeEnvelope', envelope => { + forEachEnvelopeItem(envelope, (item, type) => { + if (type === 'event') { + const event = Array.isArray(item) ? (item as EventItem)[1] : undefined; + + if (event) { + stripMetadataFromStackFrames(event); + item[1] = event; + } + } + }); + }); + client.on('applyFrameMetadata', event => { // Only apply stack frame metadata to error events if (event.type) { diff --git a/packages/core/src/integrations/third-party-errors-filter.ts b/packages/core/src/integrations/third-party-errors-filter.ts index f4af1908ec57..cf03db31643d 100644 --- a/packages/core/src/integrations/third-party-errors-filter.ts +++ b/packages/core/src/integrations/third-party-errors-filter.ts @@ -1,6 +1,8 @@ import { defineIntegration } from '../integration'; -import { addMetadataToStackFrames } from '../metadata'; +import { addMetadataToStackFrames, stripMetadataFromStackFrames } from '../metadata'; +import type { EventItem } from '../types-hoist/envelope'; import type { Event } from '../types-hoist/event'; +import { forEachEnvelopeItem } from '../utils/envelope'; import { getFramesFromEvent } from '../utils/stacktrace'; interface Options { @@ -39,6 +41,20 @@ export const thirdPartyErrorFilterIntegration = defineIntegration((options: Opti return { name: 'ThirdPartyErrorsFilter', setup(client) { + // We need to strip metadata from stack frames before sending them to Sentry since these are client side only. + client.on('beforeEnvelope', envelope => { + forEachEnvelopeItem(envelope, (item, type) => { + if (type === 'event') { + const event = Array.isArray(item) ? (item as EventItem)[1] : undefined; + + if (event) { + stripMetadataFromStackFrames(event); + item[1] = event; + } + } + }); + }); + client.on('applyFrameMetadata', event => { // Only apply stack frame metadata to error events if (event.type) { diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index 657aecad3cb5..1ee93e8dcd5a 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -53,28 +53,19 @@ export function getMetadataForUrl(parser: StackParser, filename: string): any | * Metadata is injected by the Sentry bundler plugins using the `_experiments.moduleMetadata` config option. */ export function addMetadataToStackFrames(parser: StackParser, event: Event): void { - try { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - event.exception!.values!.forEach(exception => { - if (!exception.stacktrace) { + event.exception?.values?.forEach(exception => { + exception.stacktrace?.frames?.forEach(frame => { + if (!frame.filename || frame.module_metadata) { return; } - for (const frame of exception.stacktrace.frames || []) { - if (!frame.filename || frame.module_metadata) { - continue; - } - - const metadata = getMetadataForUrl(parser, frame.filename); + const metadata = getMetadataForUrl(parser, frame.filename); - if (metadata) { - frame.module_metadata = metadata; - } + if (metadata) { + frame.module_metadata = metadata; } }); - } catch { - // To save bundle size we're just try catching here instead of checking for the existence of all the different objects. - } + }); } /** From 4d1f2c1b9c5ef2dd08b2ccbc7e558530b1abf261 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 18 Sep 2025 13:12:41 +0200 Subject: [PATCH 3/4] fix client changes --- packages/core/src/client.ts | 74 +++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 75a0d5ed49d2..924bd1810ea3 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -44,7 +44,7 @@ import { parseSampleRate } from './utils/parseSampleRate'; import { prepareEvent } from './utils/prepareEvent'; import { reparentChildSpans, shouldIgnoreSpan } from './utils/should-ignore-span'; import { getActiveSpan, showSpanDropWarning, spanToTraceContext } from './utils/spanUtils'; -import { rejectedSyncPromise, resolvedSyncPromise, SyncPromise } from './utils/syncpromise'; +import { rejectedSyncPromise } from './utils/syncpromise'; import { convertSpanJsonToTransactionEvent, convertTransactionEventToSpanJson } from './utils/transactionEvent'; const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been captured."; @@ -316,16 +316,19 @@ export abstract class Client { * @returns A promise that will resolve with `true` if all events are sent before the timeout, or `false` if there are * still events in the queue when the timeout is reached. */ - public flush(timeout?: number): PromiseLike { + // @ts-expect-error - PromiseLike is a subset of Promise + public async flush(timeout?: number): PromiseLike { const transport = this._transport; - if (transport) { - this.emit('flush'); - return this._isClientDoneProcessing(timeout).then(clientFinished => { - return transport.flush(timeout).then(transportFlushed => clientFinished && transportFlushed); - }); - } else { - return resolvedSyncPromise(true); + if (!transport) { + return true; } + + this.emit('flush'); + + const clientFinished = await this._isClientDoneProcessing(timeout); + const transportFlushed = await transport.flush(timeout); + + return clientFinished && transportFlushed; } /** @@ -336,12 +339,12 @@ export abstract class Client { * @returns {Promise} A promise which resolves to `true` if the flush completes successfully before the timeout, or `false` if * it doesn't. */ - public close(timeout?: number): PromiseLike { - return this.flush(timeout).then(result => { - this.getOptions().enabled = false; - this.emit('close'); - return result; - }); + // @ts-expect-error - PromiseLike is a subset of Promise + public async close(timeout?: number): PromiseLike { + const result = await this.flush(timeout); + this.getOptions().enabled = false; + this.emit('close'); + return result; } /** @@ -872,18 +875,21 @@ export abstract class Client { /** * Send an envelope to Sentry. */ - public sendEnvelope(envelope: Envelope): PromiseLike { + // @ts-expect-error - PromiseLike is a subset of Promise + public async sendEnvelope(envelope: Envelope): PromiseLike { this.emit('beforeEnvelope', envelope); if (this._isEnabled() && this._transport) { - return this._transport.send(envelope).then(null, reason => { + try { + return await this._transport.send(envelope); + } catch (reason) { DEBUG_BUILD && debug.error('Error while sending envelope:', reason); return {}; - }); + } } DEBUG_BUILD && debug.error('Transport disabled'); - return resolvedSyncPromise({}); + return {}; } /* eslint-enable @typescript-eslint/unified-signatures */ @@ -938,24 +944,20 @@ export abstract class Client { * @returns A promise which will resolve to `true` if processing is already done or finishes before the timeout, and * `false` otherwise */ - protected _isClientDoneProcessing(timeout?: number): PromiseLike { - return new SyncPromise(resolve => { - let ticked: number = 0; - const tick: number = 1; + protected async _isClientDoneProcessing(timeout?: number): Promise { + let ticked = 0; - const interval = setInterval(() => { - if (this._numProcessing == 0) { - clearInterval(interval); - resolve(true); - } else { - ticked += tick; - if (timeout && ticked >= timeout) { - clearInterval(interval); - resolve(false); - } - } - }, tick); - }); + // if no timeout is provided, we wait "forever" until everything is processed + while (!timeout || ticked < timeout) { + await new Promise(resolve => setTimeout(resolve, 1)); + + if (!this._numProcessing) { + return true; + } + ticked++; + } + + return false; } /** Determines whether this SDK is enabled and a transport is present. */ From ded166e035cd721b9ad3a63f247b5e1a43033594 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 18 Sep 2025 13:19:20 +0200 Subject: [PATCH 4/4] start build?