From 7be5f79bc5711761b5e0a5924fc3d9b56b7d8298 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Thu, 10 Jul 2025 16:15:26 +0200 Subject: [PATCH 01/11] ref(core): Avoid side-effect of & streamline `timestampInSeconds` method (#16890) Related to https://github.com/getsentry/sentry-javascript/issues/16846 This rewrites `timestampInSeconds` to avoid side effects. As a part of this, I also streamlined this a bit to remove special handling for missing `timeOrigin`. If that is missing, we just fall back to using `dateTimestampInSeconds`, instead of doing some custom handling of `timeOrigin` - IMHO that should not be any less accurate, as otherwise we are currently mixing `performance.now` with `Date.now()` which is likely not necessarily accurate either. --- .../consistent-sampling/meta-precedence/test.ts | 3 --- packages/core/src/utils/time.ts | 17 +++++++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-precedence/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-precedence/test.ts index f48aebbca9e3..1b55f5235088 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-precedence/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-precedence/test.ts @@ -25,9 +25,6 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains sentryTest.skip(); } - - - const url = await getLocalTestUrl({ testDir: __dirname }); const clientReportPromise = waitForClientReportRequest(page); diff --git a/packages/core/src/utils/time.ts b/packages/core/src/utils/time.ts index c23915482590..ff858a15b0ac 100644 --- a/packages/core/src/utils/time.ts +++ b/packages/core/src/utils/time.ts @@ -32,14 +32,13 @@ export function dateTimestampInSeconds(): number { */ function createUnixTimestampInSecondsFunc(): () => number { const { performance } = GLOBAL_OBJ as typeof GLOBAL_OBJ & { performance?: Performance }; - if (!performance?.now) { + // Some browser and environments don't have a performance or timeOrigin, so we fallback to + // using Date.now() to compute the starting time. + if (!performance?.now || !performance.timeOrigin) { return dateTimestampInSeconds; } - // Some browser and environments don't have a timeOrigin, so we fallback to - // using Date.now() to compute the starting time. - const approxStartingTimeOrigin = Date.now() - performance.now(); - const timeOrigin = performance.timeOrigin == undefined ? approxStartingTimeOrigin : performance.timeOrigin; + const timeOrigin = performance.timeOrigin; // performance.now() is a monotonic clock, which means it starts at 0 when the process begins. To get the current // wall clock time (actual UNIX timestamp), we need to add the starting time origin and the current time elapsed. @@ -55,6 +54,8 @@ function createUnixTimestampInSecondsFunc(): () => number { }; } +let _cachedTimestampInSeconds: (() => number) | undefined; + /** * Returns a timestamp in seconds since the UNIX epoch using either the Performance or Date APIs, depending on the * availability of the Performance API. @@ -64,7 +65,11 @@ function createUnixTimestampInSecondsFunc(): () => number { * skew can grow to arbitrary amounts like days, weeks or months. * See https://github.com/getsentry/sentry-javascript/issues/2590. */ -export const timestampInSeconds = createUnixTimestampInSecondsFunc(); +export function timestampInSeconds(): number { + // We store this in a closure so that we don't have to create a new function every time this is called. + const func = _cachedTimestampInSeconds ?? (_cachedTimestampInSeconds = createUnixTimestampInSecondsFunc()); + return func(); +} /** * Cached result of getBrowserTimeOrigin. From fbf7b1c99cd02ea32f90511fb6eb39ed4845bd4b Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Thu, 10 Jul 2025 17:42:15 +0200 Subject: [PATCH 02/11] ref(core): Rewrite `logger` to avoid side effects (#16897) Today the logger export from core has a side effect and reads from the global. This PR rewrites this to instead only keep the enabled flag on the carrier. The logger itself is a "normal" method that just looks at the carrier to see if logging is enabled or not. --- packages/core/src/carrier.ts | 2 + packages/core/src/utils/logger.ts | 152 ++++++++++++++------ packages/core/test/clear-global-scope.ts | 0 packages/core/test/lib/utils/logger.test.ts | 57 ++++++++ 4 files changed, 165 insertions(+), 46 deletions(-) delete mode 100644 packages/core/test/clear-global-scope.ts create mode 100644 packages/core/test/lib/utils/logger.test.ts diff --git a/packages/core/src/carrier.ts b/packages/core/src/carrier.ts index d11ef2940d0c..31827ad7b87e 100644 --- a/packages/core/src/carrier.ts +++ b/packages/core/src/carrier.ts @@ -24,7 +24,9 @@ export interface SentryCarrier { globalScope?: Scope; defaultIsolationScope?: Scope; defaultCurrentScope?: Scope; + /** @deprecated Logger is no longer set. Instead, we keep enabled state in loggerSettings. */ logger?: Logger; + loggerSettings?: { enabled: boolean }; /** Overwrites TextEncoder used in `@sentry/core`, need for `react-native@0.73` and older */ encodePolyfill?: (input: string) => Uint8Array; diff --git a/packages/core/src/utils/logger.ts b/packages/core/src/utils/logger.ts index 669917dfbadf..d17f67f6f6e6 100644 --- a/packages/core/src/utils/logger.ts +++ b/packages/core/src/utils/logger.ts @@ -3,8 +3,19 @@ import { DEBUG_BUILD } from '../debug-build'; import type { ConsoleLevel } from '../types-hoist/instrument'; import { GLOBAL_OBJ } from './worldwide'; -/** Prefix for logging strings */ -const PREFIX = 'Sentry Logger '; +/** A Sentry Logger instance. */ +export interface Logger { + disable(): void; + enable(): void; + isEnabled(): boolean; + log(...args: Parameters): void; + info(...args: Parameters): void; + warn(...args: Parameters): void; + error(...args: Parameters): void; + debug(...args: Parameters): void; + assert(...args: Parameters): void; + trace(...args: Parameters): void; +} export const CONSOLE_LEVELS: readonly ConsoleLevel[] = [ 'debug', @@ -16,20 +27,19 @@ export const CONSOLE_LEVELS: readonly ConsoleLevel[] = [ 'trace', ] as const; -type LoggerMethod = (...args: unknown[]) => void; -type LoggerConsoleMethods = Record; +/** Prefix for logging strings */ +const PREFIX = 'Sentry Logger '; /** This may be mutated by the console instrumentation. */ -export const originalConsoleMethods: { - [key in ConsoleLevel]?: (...args: unknown[]) => void; -} = {}; - -/** A Sentry Logger instance. */ -export interface Logger extends LoggerConsoleMethods { - disable(): void; - enable(): void; - isEnabled(): boolean; -} +export const originalConsoleMethods: Partial<{ + log(...args: Parameters): void; + info(...args: Parameters): void; + warn(...args: Parameters): void; + error(...args: Parameters): void; + debug(...args: Parameters): void; + assert(...args: Parameters): void; + trace(...args: Parameters): void; +}> = {}; /** * Temporarily disable sentry console instrumentations. @@ -43,15 +53,15 @@ export function consoleSandbox(callback: () => T): T { } const console = GLOBAL_OBJ.console as Console; - const wrappedFuncs: Partial = {}; + const wrappedFuncs: Partial void>> = {}; const wrappedLevels = Object.keys(originalConsoleMethods) as ConsoleLevel[]; // Restore all wrapped console methods wrappedLevels.forEach(level => { - const originalConsoleMethod = originalConsoleMethods[level] as LoggerMethod; - wrappedFuncs[level] = console[level] as LoggerMethod | undefined; - console[level] = originalConsoleMethod; + const originalConsoleMethod = originalConsoleMethods[level]; + wrappedFuncs[level] = console[level] as (...args: unknown[]) => void; + console[level] = originalConsoleMethod as (...args: unknown[]) => void; }); try { @@ -59,44 +69,94 @@ export function consoleSandbox(callback: () => T): T { } finally { // Revert restoration to wrapped state wrappedLevels.forEach(level => { - console[level] = wrappedFuncs[level] as LoggerMethod; + console[level] = wrappedFuncs[level] as (...args: unknown[]) => void; }); } } -function makeLogger(): Logger { - let enabled = false; - const logger: Partial = { - enable: () => { - enabled = true; - }, - disable: () => { - enabled = false; - }, - isEnabled: () => enabled, - }; - - if (DEBUG_BUILD) { - CONSOLE_LEVELS.forEach(name => { - logger[name] = (...args: Parameters<(typeof GLOBAL_OBJ.console)[typeof name]>) => { - if (enabled) { - consoleSandbox(() => { - GLOBAL_OBJ.console[name](`${PREFIX}[${name}]:`, ...args); - }); - } - }; - }); - } else { - CONSOLE_LEVELS.forEach(name => { - logger[name] = () => undefined; +function enable(): void { + _getLoggerSettings().enabled = true; +} + +function disable(): void { + _getLoggerSettings().enabled = false; +} + +function isEnabled(): boolean { + return _getLoggerSettings().enabled; +} + +function log(...args: Parameters): void { + _maybeLog('log', ...args); +} + +function info(...args: Parameters): void { + _maybeLog('info', ...args); +} + +function warn(...args: Parameters): void { + _maybeLog('warn', ...args); +} + +function error(...args: Parameters): void { + _maybeLog('error', ...args); +} + +function debug(...args: Parameters): void { + _maybeLog('debug', ...args); +} + +function assert(...args: Parameters): void { + _maybeLog('assert', ...args); +} + +function trace(...args: Parameters): void { + _maybeLog('trace', ...args); +} + +function _maybeLog(level: ConsoleLevel, ...args: Parameters<(typeof console)[typeof level]>): void { + if (!DEBUG_BUILD) { + return; + } + + if (isEnabled()) { + consoleSandbox(() => { + GLOBAL_OBJ.console[level](`${PREFIX}[${level}]:`, ...args); }); } +} + +function _getLoggerSettings(): { enabled: boolean } { + if (!DEBUG_BUILD) { + return { enabled: false }; + } - return logger as Logger; + return getGlobalSingleton('loggerSettings', () => ({ enabled: false })); } /** * This is a logger singleton which either logs things or no-ops if logging is not enabled. * The logger is a singleton on the carrier, to ensure that a consistent logger is used throughout the SDK. */ -export const logger = getGlobalSingleton('logger', makeLogger); +export const logger = { + /** Enable logging. */ + enable, + /** Disable logging. */ + disable, + /** Check if logging is enabled. */ + isEnabled, + /** Log a message. */ + log, + /** Log level info */ + info, + /** Log a warning. */ + warn, + /** Log an error. */ + error, + /** Log a debug message. */ + debug, + /** Log an assertion. */ + assert, + /** Log a trace. */ + trace, +} satisfies Logger; diff --git a/packages/core/test/clear-global-scope.ts b/packages/core/test/clear-global-scope.ts deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/packages/core/test/lib/utils/logger.test.ts b/packages/core/test/lib/utils/logger.test.ts new file mode 100644 index 000000000000..21d26b273dea --- /dev/null +++ b/packages/core/test/lib/utils/logger.test.ts @@ -0,0 +1,57 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { logger } from '../../../src'; +import { getMainCarrier, getSentryCarrier } from '../../../src/carrier'; + +describe('logger', () => { + beforeEach(() => { + vi.clearAllMocks(); + getSentryCarrier(getMainCarrier()).loggerSettings = undefined; + }); + + it('works with defaults', () => { + const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + logger.log('test'); + expect(consoleLogSpy).not.toHaveBeenCalled(); + expect(logger.isEnabled()).toBe(false); + }); + + it('allows to enable and disable logging', () => { + const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + + logger.log('test'); + expect(logger.isEnabled()).toBe(false); + expect(consoleLogSpy).not.toHaveBeenCalled(); + + logger.enable(); + logger.log('test'); + expect(logger.isEnabled()).toBe(true); + expect(consoleLogSpy).toHaveBeenCalledTimes(1); + + logger.log('test2'); + expect(consoleLogSpy).toHaveBeenCalledTimes(2); + + logger.disable(); + + logger.log('test3'); + expect(logger.isEnabled()).toBe(false); + expect(consoleLogSpy).toHaveBeenCalledTimes(2); + }); + + it('picks up enabled logger settings from carrier', () => { + getSentryCarrier(getMainCarrier()).loggerSettings = { enabled: true }; + + const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + logger.log('test'); + expect(consoleLogSpy).toHaveBeenCalledTimes(1); + expect(logger.isEnabled()).toBe(true); + }); + + it('picks up disabled logger settings from carrier', () => { + getSentryCarrier(getMainCarrier()).loggerSettings = { enabled: false }; + + const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + logger.log('test'); + expect(consoleLogSpy).toHaveBeenCalledTimes(0); + expect(logger.isEnabled()).toBe(false); + }); +}); From 6179229df5e4fbbba56ea5c490caf77e0affab10 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 10 Jul 2025 11:58:51 -0400 Subject: [PATCH 03/11] fix(browser): guard `nextHopProtocol` when adding resource spans (#16900) ref https://github.com/getsentry/sentry-javascript/issues/16804 This was addressed with https://github.com/getsentry/sentry-javascript/pull/16806, but we didn't guard every location that uses `entry.nextHopProtocol`. Will backport to v9 after merge. --------- Co-authored-by: Lukas Stracke --- packages/browser-utils/src/metrics/browserMetrics.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index e573ca441b03..18b274b855e0 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -715,9 +715,13 @@ export function _addResourceSpans( attributes['url.same_origin'] = resourceUrl.includes(WINDOW.location.origin); - const { name, version } = extractNetworkProtocol(entry.nextHopProtocol); - attributes['network.protocol.name'] = name; - attributes['network.protocol.version'] = version; + // Checking for only `undefined` and `null` is intentional because it's + // valid for `nextHopProtocol` to be an empty string. + if (entry.nextHopProtocol != null) { + const { name, version } = extractNetworkProtocol(entry.nextHopProtocol); + attributes['network.protocol.name'] = name; + attributes['network.protocol.version'] = version; + } const startTimestamp = timeOrigin + startTime; const endTimestamp = startTimestamp + duration; From 6a245a0bea215e6b29fda00358b21dbe58fc72e1 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 10 Jul 2025 13:52:00 -0400 Subject: [PATCH 04/11] chore: Add craft entry for @sentry/node-native (#16907) We need to publish this package! --- .craft.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.craft.yml b/.craft.yml index efb18669ac9f..6d2076434180 100644 --- a/.craft.yml +++ b/.craft.yml @@ -46,6 +46,9 @@ targets: - name: npm id: '@sentry/profiling-node' includeNames: /^sentry-profiling-node-\d.*\.tgz$/ + - name: npm + id: '@sentry/node-native' + includeNames: /^sentry-node-native-\d.*\.tgz$/ ## 3 Browser-based Packages - name: npm From ccbf6274f5e72ddcc2eb9750072068bc8184892e Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 10 Jul 2025 13:58:01 -0400 Subject: [PATCH 05/11] feat(core): Introduce `debug` to replace `logger` (#16906) resolves https://github.com/getsentry/sentry-javascript/issues/16903 ref https://github.com/getsentry/sentry-javascript/issues/16901 This PR introduces a `debug` function that aims to replace `logger`. This gives us a couple of benefits: 1. communicates intent of the export much better 1. avoids collision with `logger` exported from other sdks (which is used for sentry structured logging) 1. we can move structured logging `logger` code into `@sentry/core`, reducing duplication across our sdks We don't deprecate `logger` just yet, we need to convert everything else in the other SDKs first. --- packages/core/src/client.ts | 34 +++++------ packages/core/src/eventProcessors.ts | 4 +- packages/core/src/exports.ts | 10 ++-- packages/core/src/index.ts | 2 +- packages/core/src/instrument/handlers.ts | 6 +- packages/core/src/integration.ts | 8 +-- packages/core/src/integrations/dedupe.ts | 4 +- .../core/src/integrations/eventFilters.ts | 14 ++--- .../core/src/integrations/extraerrordata.ts | 4 +- packages/core/src/integrations/supabase.ts | 6 +- packages/core/src/logs/console-integration.ts | 4 +- packages/core/src/logs/exports.ts | 8 +-- packages/core/src/mcp-server.ts | 4 +- packages/core/src/profiling.ts | 14 ++--- packages/core/src/scope.ts | 9 +-- packages/core/src/sdk.ts | 6 +- packages/core/src/server-runtime-client.ts | 6 +- packages/core/src/tracing/errors.ts | 4 +- packages/core/src/tracing/idleSpan.ts | 12 ++-- packages/core/src/tracing/logSpans.ts | 6 +- packages/core/src/tracing/measurement.ts | 4 +- packages/core/src/tracing/sampling.ts | 8 +-- packages/core/src/tracing/sentrySpan.ts | 10 ++-- packages/core/src/tracing/trace.ts | 6 +- packages/core/src/transports/base.ts | 8 +-- packages/core/src/transports/offline.ts | 4 +- packages/core/src/utils/baggage.ts | 4 +- packages/core/src/utils/dsn.ts | 10 ++-- packages/core/src/utils/env.ts | 2 +- packages/core/src/utils/featureFlags.ts | 4 +- packages/core/src/utils/logger.ts | 31 +++++++++- packages/core/src/utils/node.ts | 2 +- packages/core/src/utils/object.ts | 6 +- packages/core/src/utils/supports.ts | 5 +- packages/core/src/utils/traceData.ts | 4 +- packages/core/src/utils/worldwide.ts | 2 +- packages/core/test/lib/client.test.ts | 26 ++++----- packages/core/test/lib/integration.test.ts | 8 +-- .../lib/integrations/captureconsole.test.ts | 2 +- packages/core/test/lib/logs/exports.test.ts | 4 +- packages/core/test/lib/utils/dsn.test.ts | 4 +- .../core/test/lib/utils/featureFlags.test.ts | 4 +- packages/core/test/lib/utils/logger.test.ts | 56 ++++++++++++++++++- packages/nextjs/test/clientSdk.test.ts | 6 +- 44 files changed, 233 insertions(+), 152 deletions(-) diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index e31ab482e0b3..869395beea21 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -37,7 +37,7 @@ import { dsnToString, makeDsn } from './utils/dsn'; import { addItemToEnvelope, createAttachmentEnvelopeItem } from './utils/envelope'; import { getPossibleEventMessages } from './utils/eventUtils'; import { isParameterizedString, isPlainObject, isPrimitive, isThenable } from './utils/is'; -import { logger } from './utils/logger'; +import { debug } from './utils/logger'; import { merge } from './utils/merge'; import { checkOrSetAlreadyCaught, uuid4 } from './utils/misc'; import { parseSampleRate } from './utils/parseSampleRate'; @@ -154,7 +154,7 @@ export abstract class Client { if (options.dsn) { this._dsn = makeDsn(options.dsn); } else { - DEBUG_BUILD && logger.warn('No DSN provided, client will not send events.'); + DEBUG_BUILD && debug.warn('No DSN provided, client will not send events.'); } if (this._dsn) { @@ -182,7 +182,7 @@ export abstract class Client { // ensure we haven't captured this very object before if (checkOrSetAlreadyCaught(exception)) { - DEBUG_BUILD && logger.log(ALREADY_SEEN_ERROR); + DEBUG_BUILD && debug.log(ALREADY_SEEN_ERROR); return eventId; } @@ -237,7 +237,7 @@ export abstract class Client { // ensure we haven't captured this very object before if (hint?.originalException && checkOrSetAlreadyCaught(hint.originalException)) { - DEBUG_BUILD && logger.log(ALREADY_SEEN_ERROR); + DEBUG_BUILD && debug.log(ALREADY_SEEN_ERROR); return eventId; } @@ -429,7 +429,7 @@ export abstract class Client { if ('aggregates' in session) { const sessionAttrs = session.attrs || {}; if (!sessionAttrs.release && !clientReleaseOption) { - DEBUG_BUILD && logger.warn(MISSING_RELEASE_FOR_SESSION_ERROR); + DEBUG_BUILD && debug.warn(MISSING_RELEASE_FOR_SESSION_ERROR); return; } sessionAttrs.release = sessionAttrs.release || clientReleaseOption; @@ -437,7 +437,7 @@ export abstract class Client { session.attrs = sessionAttrs; } else { if (!session.release && !clientReleaseOption) { - DEBUG_BUILD && logger.warn(MISSING_RELEASE_FOR_SESSION_ERROR); + DEBUG_BUILD && debug.warn(MISSING_RELEASE_FOR_SESSION_ERROR); return; } session.release = session.release || clientReleaseOption; @@ -465,7 +465,7 @@ export abstract class Client { // would be `Partial>>>` // With typescript 4.1 we could even use template literal types const key = `${reason}:${category}`; - DEBUG_BUILD && logger.log(`Recording outcome: "${key}"${count > 1 ? ` (${count} times)` : ''}`); + DEBUG_BUILD && debug.log(`Recording outcome: "${key}"${count > 1 ? ` (${count} times)` : ''}`); this._outcomes[key] = (this._outcomes[key] || 0) + count; } } @@ -866,12 +866,12 @@ export abstract class Client { if (this._isEnabled() && this._transport) { return this._transport.send(envelope).then(null, reason => { - DEBUG_BUILD && logger.error('Error while sending envelope:', reason); + DEBUG_BUILD && debug.error('Error while sending envelope:', reason); return reason; }); } - DEBUG_BUILD && logger.error('Transport disabled'); + DEBUG_BUILD && debug.error('Transport disabled'); return resolvedSyncPromise({}); } @@ -1021,7 +1021,7 @@ export abstract class Client { isolationScope = getIsolationScope(), ): PromiseLike { if (DEBUG_BUILD && isErrorEvent(event)) { - logger.log(`Captured error event \`${getPossibleEventMessages(event)[0] || ''}\``); + debug.log(`Captured error event \`${getPossibleEventMessages(event)[0] || ''}\``); } return this._processEvent(event, hint, currentScope, isolationScope).then( @@ -1031,11 +1031,11 @@ export abstract class Client { reason => { if (DEBUG_BUILD) { if (_isDoNotSendEventError(reason)) { - logger.log(reason.message); + debug.log(reason.message); } else if (_isInternalError(reason)) { - logger.warn(reason.message); + debug.warn(reason.message); } else { - logger.warn(reason); + debug.warn(reason); } } return undefined; @@ -1196,22 +1196,22 @@ export abstract class Client { * Sends client reports as an envelope. */ protected _flushOutcomes(): void { - DEBUG_BUILD && logger.log('Flushing outcomes...'); + DEBUG_BUILD && debug.log('Flushing outcomes...'); const outcomes = this._clearOutcomes(); if (outcomes.length === 0) { - DEBUG_BUILD && logger.log('No outcomes to send'); + DEBUG_BUILD && debug.log('No outcomes to send'); return; } // This is really the only place where we want to check for a DSN and only send outcomes then if (!this._dsn) { - DEBUG_BUILD && logger.log('No dsn provided, will not send outcomes'); + DEBUG_BUILD && debug.log('No dsn provided, will not send outcomes'); return; } - DEBUG_BUILD && logger.log('Sending outcomes:', outcomes); + DEBUG_BUILD && debug.log('Sending outcomes:', outcomes); const envelope = createClientReportEnvelope(outcomes, this._options.tunnel && dsnToString(this._dsn)); diff --git a/packages/core/src/eventProcessors.ts b/packages/core/src/eventProcessors.ts index 436f3e3c79ed..0dea0b88286d 100644 --- a/packages/core/src/eventProcessors.ts +++ b/packages/core/src/eventProcessors.ts @@ -2,7 +2,7 @@ import { DEBUG_BUILD } from './debug-build'; import type { Event, EventHint } from './types-hoist/event'; import type { EventProcessor } from './types-hoist/eventprocessor'; import { isThenable } from './utils/is'; -import { logger } from './utils/logger'; +import { debug } from './utils/logger'; import { SyncPromise } from './utils/syncpromise'; /** @@ -21,7 +21,7 @@ export function notifyEventProcessors( } else { const result = processor({ ...event }, hint) as Event | null; - DEBUG_BUILD && processor.id && result === null && logger.log(`Event processor "${processor.id}" dropped event`); + DEBUG_BUILD && processor.id && result === null && debug.log(`Event processor "${processor.id}" dropped event`); if (isThenable(result)) { void result diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index 5d36ffedf43f..d70a542ca284 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -11,7 +11,7 @@ import type { Session, SessionContext } from './types-hoist/session'; import type { SeverityLevel } from './types-hoist/severity'; import type { User } from './types-hoist/user'; import { isThenable } from './utils/is'; -import { logger } from './utils/logger'; +import { debug } from './utils/logger'; import { uuid4 } from './utils/misc'; import type { ExclusiveEventHintOrCaptureContext } from './utils/prepareEvent'; import { parseEventHintOrCaptureContext } from './utils/prepareEvent'; @@ -136,9 +136,9 @@ export function captureCheckIn(checkIn: CheckIn, upsertMonitorConfig?: MonitorCo const scope = getCurrentScope(); const client = getClient(); if (!client) { - DEBUG_BUILD && logger.warn('Cannot capture check-in. No client defined.'); + DEBUG_BUILD && debug.warn('Cannot capture check-in. No client defined.'); } else if (!client.captureCheckIn) { - DEBUG_BUILD && logger.warn('Cannot capture check-in. Client does not support sending check-ins.'); + DEBUG_BUILD && debug.warn('Cannot capture check-in. Client does not support sending check-ins.'); } else { return client.captureCheckIn(checkIn, upsertMonitorConfig, scope); } @@ -206,7 +206,7 @@ export async function flush(timeout?: number): Promise { if (client) { return client.flush(timeout); } - DEBUG_BUILD && logger.warn('Cannot flush events. No client defined.'); + DEBUG_BUILD && debug.warn('Cannot flush events. No client defined.'); return Promise.resolve(false); } @@ -223,7 +223,7 @@ export async function close(timeout?: number): Promise { if (client) { return client.close(timeout); } - DEBUG_BUILD && logger.warn('Cannot flush events and disable SDK. No client defined.'); + DEBUG_BUILD && debug.warn('Cannot flush events and disable SDK. No client defined.'); return Promise.resolve(false); } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7551478c9c88..8852e2c9293f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -165,7 +165,7 @@ export { isVueViewModel, } from './utils/is'; export { isBrowser } from './utils/isBrowser'; -export { CONSOLE_LEVELS, consoleSandbox, logger, originalConsoleMethods } from './utils/logger'; +export { CONSOLE_LEVELS, consoleSandbox, debug, logger, originalConsoleMethods } from './utils/logger'; export type { Logger } from './utils/logger'; export { addContextToFrame, diff --git a/packages/core/src/instrument/handlers.ts b/packages/core/src/instrument/handlers.ts index 2d88b3d8abe9..fbde79985120 100644 --- a/packages/core/src/instrument/handlers.ts +++ b/packages/core/src/instrument/handlers.ts @@ -1,5 +1,5 @@ import { DEBUG_BUILD } from '../debug-build'; -import { logger } from '../utils/logger'; +import { debug } from '../utils/logger'; import { getFunctionName } from '../utils/stacktrace'; export type InstrumentHandlerType = @@ -41,7 +41,7 @@ export function maybeInstrument(type: InstrumentHandlerType, instrumentFn: () => try { instrumentFn(); } catch (e) { - DEBUG_BUILD && logger.error(`Error while instrumenting ${type}`, e); + DEBUG_BUILD && debug.error(`Error while instrumenting ${type}`, e); } } } @@ -58,7 +58,7 @@ export function triggerHandlers(type: InstrumentHandlerType, data: unknown): voi handler(data); } catch (e) { DEBUG_BUILD && - logger.error( + debug.error( `Error while triggering instrumentation handler.\nType: ${type}\nName: ${getFunctionName(handler)}\nError:`, e, ); diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts index d8d741183287..5354649532d0 100644 --- a/packages/core/src/integration.ts +++ b/packages/core/src/integration.ts @@ -4,7 +4,7 @@ import { DEBUG_BUILD } from './debug-build'; import type { Event, EventHint } from './types-hoist/event'; import type { Integration, IntegrationFn } from './types-hoist/integration'; import type { Options } from './types-hoist/options'; -import { logger } from './utils/logger'; +import { debug } from './utils/logger'; export const installedIntegrations: string[] = []; @@ -99,7 +99,7 @@ export function afterSetupIntegrations(client: Client, integrations: Integration /** Setup a single integration. */ export function setupIntegration(client: Client, integration: Integration, integrationIndex: IntegrationIndex): void { if (integrationIndex[integration.name]) { - DEBUG_BUILD && logger.log(`Integration skipped because it was already installed: ${integration.name}`); + DEBUG_BUILD && debug.log(`Integration skipped because it was already installed: ${integration.name}`); return; } integrationIndex[integration.name] = integration; @@ -130,7 +130,7 @@ export function setupIntegration(client: Client, integration: Integration, integ client.addEventProcessor(processor); } - DEBUG_BUILD && logger.log(`Integration installed: ${integration.name}`); + DEBUG_BUILD && debug.log(`Integration installed: ${integration.name}`); } /** Add an integration to the current scope's client. */ @@ -138,7 +138,7 @@ export function addIntegration(integration: Integration): void { const client = getClient(); if (!client) { - DEBUG_BUILD && logger.warn(`Cannot add integration "${integration.name}" because no SDK Client is available.`); + DEBUG_BUILD && debug.warn(`Cannot add integration "${integration.name}" because no SDK Client is available.`); return; } diff --git a/packages/core/src/integrations/dedupe.ts b/packages/core/src/integrations/dedupe.ts index ab0e7eb41b25..dfca4698f788 100644 --- a/packages/core/src/integrations/dedupe.ts +++ b/packages/core/src/integrations/dedupe.ts @@ -4,7 +4,7 @@ import type { Event } from '../types-hoist/event'; import type { Exception } from '../types-hoist/exception'; import type { IntegrationFn } from '../types-hoist/integration'; import type { StackFrame } from '../types-hoist/stackframe'; -import { logger } from '../utils/logger'; +import { debug } from '../utils/logger'; import { getFramesFromEvent } from '../utils/stacktrace'; const INTEGRATION_NAME = 'Dedupe'; @@ -24,7 +24,7 @@ const _dedupeIntegration = (() => { // Juuust in case something goes wrong try { if (_shouldDropEvent(currentEvent, previousEvent)) { - DEBUG_BUILD && logger.warn('Event dropped due to being a duplicate of previously captured event.'); + DEBUG_BUILD && debug.warn('Event dropped due to being a duplicate of previously captured event.'); return null; } } catch (_oO) {} // eslint-disable-line no-empty diff --git a/packages/core/src/integrations/eventFilters.ts b/packages/core/src/integrations/eventFilters.ts index c1818c8a46f8..729758a7438e 100644 --- a/packages/core/src/integrations/eventFilters.ts +++ b/packages/core/src/integrations/eventFilters.ts @@ -4,7 +4,7 @@ import type { Event } from '../types-hoist/event'; import type { IntegrationFn } from '../types-hoist/integration'; import type { StackFrame } from '../types-hoist/stackframe'; import { getPossibleEventMessages } from '../utils/eventUtils'; -import { logger } from '../utils/logger'; +import { debug } from '../utils/logger'; import { getEventDescription } from '../utils/misc'; import { stringMatchesSomePattern } from '../utils/string'; @@ -111,14 +111,14 @@ function _shouldDropEvent(event: Event, options: Partial): // Filter errors if (_isIgnoredError(event, options.ignoreErrors)) { DEBUG_BUILD && - logger.warn( + debug.warn( `Event dropped due to being matched by \`ignoreErrors\` option.\nEvent: ${getEventDescription(event)}`, ); return true; } if (_isUselessError(event)) { DEBUG_BUILD && - logger.warn( + debug.warn( `Event dropped due to not having an error message, error type or stacktrace.\nEvent: ${getEventDescription( event, )}`, @@ -127,7 +127,7 @@ function _shouldDropEvent(event: Event, options: Partial): } if (_isDeniedUrl(event, options.denyUrls)) { DEBUG_BUILD && - logger.warn( + debug.warn( `Event dropped due to being matched by \`denyUrls\` option.\nEvent: ${getEventDescription( event, )}.\nUrl: ${_getEventFilterUrl(event)}`, @@ -136,7 +136,7 @@ function _shouldDropEvent(event: Event, options: Partial): } if (!_isAllowedUrl(event, options.allowUrls)) { DEBUG_BUILD && - logger.warn( + debug.warn( `Event dropped due to not being matched by \`allowUrls\` option.\nEvent: ${getEventDescription( event, )}.\nUrl: ${_getEventFilterUrl(event)}`, @@ -148,7 +148,7 @@ function _shouldDropEvent(event: Event, options: Partial): if (_isIgnoredTransaction(event, options.ignoreTransactions)) { DEBUG_BUILD && - logger.warn( + debug.warn( `Event dropped due to being matched by \`ignoreTransactions\` option.\nEvent: ${getEventDescription(event)}`, ); return true; @@ -212,7 +212,7 @@ function _getEventFilterUrl(event: Event): string | null { const frames = rootException?.stacktrace?.frames; return frames ? _getLastValidUrl(frames) : null; } catch (oO) { - DEBUG_BUILD && logger.error(`Cannot extract url for event ${getEventDescription(event)}`); + DEBUG_BUILD && debug.error(`Cannot extract url for event ${getEventDescription(event)}`); return null; } } diff --git a/packages/core/src/integrations/extraerrordata.ts b/packages/core/src/integrations/extraerrordata.ts index 3856699c9f68..21b47e9fdb9c 100644 --- a/packages/core/src/integrations/extraerrordata.ts +++ b/packages/core/src/integrations/extraerrordata.ts @@ -5,7 +5,7 @@ import type { ExtendedError } from '../types-hoist/error'; import type { Event, EventHint } from '../types-hoist/event'; import type { IntegrationFn } from '../types-hoist/integration'; import { isError, isPlainObject } from '../utils/is'; -import { logger } from '../utils/logger'; +import { debug } from '../utils/logger'; import { normalize } from '../utils/normalize'; import { addNonEnumerableProperty } from '../utils/object'; import { truncate } from '../utils/string'; @@ -130,7 +130,7 @@ function _extractErrorData( return extraErrorInfo; } catch (oO) { - DEBUG_BUILD && logger.error('Unable to extract extra data from the Error object:', oO); + DEBUG_BUILD && debug.error('Unable to extract extra data from the Error object:', oO); } return null; diff --git a/packages/core/src/integrations/supabase.ts b/packages/core/src/integrations/supabase.ts index ac781e95ece6..66ddf955419d 100644 --- a/packages/core/src/integrations/supabase.ts +++ b/packages/core/src/integrations/supabase.ts @@ -10,7 +10,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from ' import { setHttpStatus, SPAN_STATUS_ERROR, SPAN_STATUS_OK, startSpan } from '../tracing'; import type { IntegrationFn } from '../types-hoist/integration'; import { isPlainObject } from '../utils/is'; -import { logger } from '../utils/logger'; +import { debug } from '../utils/logger'; const AUTH_OPERATIONS_TO_INSTRUMENT = [ 'reauthenticate', @@ -481,7 +481,7 @@ function instrumentPostgRESTQueryBuilder(PostgRESTQueryBuilder: new () => PostgR const rv = Reflect.apply(target, thisArg, argumentsList); const PostgRESTFilterBuilder = (rv as PostgRESTFilterBuilder).constructor; - DEBUG_BUILD && logger.log(`Instrumenting ${operation} operation's PostgRESTFilterBuilder`); + DEBUG_BUILD && debug.log(`Instrumenting ${operation} operation's PostgRESTFilterBuilder`); instrumentPostgRESTFilterBuilder(PostgRESTFilterBuilder); @@ -496,7 +496,7 @@ function instrumentPostgRESTQueryBuilder(PostgRESTQueryBuilder: new () => PostgR export const instrumentSupabaseClient = (supabaseClient: unknown): void => { if (!supabaseClient) { - DEBUG_BUILD && logger.warn('Supabase integration was not installed because no Supabase client was provided.'); + DEBUG_BUILD && debug.warn('Supabase integration was not installed because no Supabase client was provided.'); return; } const SupabaseClientConstructor = diff --git a/packages/core/src/logs/console-integration.ts b/packages/core/src/logs/console-integration.ts index 677532c36346..3affabd412e9 100644 --- a/packages/core/src/logs/console-integration.ts +++ b/packages/core/src/logs/console-integration.ts @@ -6,7 +6,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes'; import type { ConsoleLevel } from '../types-hoist/instrument'; import type { IntegrationFn } from '../types-hoist/integration'; import { isPrimitive } from '../utils/is'; -import { CONSOLE_LEVELS, logger } from '../utils/logger'; +import { CONSOLE_LEVELS, debug } from '../utils/logger'; import { normalize } from '../utils/normalize'; import { GLOBAL_OBJ } from '../utils/worldwide'; import { _INTERNAL_captureLog } from './exports'; @@ -35,7 +35,7 @@ const _consoleLoggingIntegration = ((options: Partial = { setup(client) { const { _experiments, normalizeDepth = 3, normalizeMaxBreadth = 1_000 } = client.getOptions(); if (!_experiments?.enableLogs) { - DEBUG_BUILD && logger.warn('`_experiments.enableLogs` is not enabled, ConsoleLogs integration disabled'); + DEBUG_BUILD && debug.warn('`_experiments.enableLogs` is not enabled, ConsoleLogs integration disabled'); return; } diff --git a/packages/core/src/logs/exports.ts b/packages/core/src/logs/exports.ts index 0ec4d960c66d..8d8a2a292df8 100644 --- a/packages/core/src/logs/exports.ts +++ b/packages/core/src/logs/exports.ts @@ -6,7 +6,7 @@ import type { Scope, ScopeData } from '../scope'; import type { Log, SerializedLog, SerializedLogAttributeValue } from '../types-hoist/log'; import { mergeScopeData } from '../utils/applyScopeDataToEvent'; import { isParameterizedString } from '../utils/is'; -import { logger } from '../utils/logger'; +import { debug } from '../utils/logger'; import { _getSpanForScope } from '../utils/spanOnScope'; import { timestampInSeconds } from '../utils/time'; import { GLOBAL_OBJ } from '../utils/worldwide'; @@ -121,14 +121,14 @@ export function _INTERNAL_captureLog( captureSerializedLog: (client: Client, log: SerializedLog) => void = _INTERNAL_captureSerializedLog, ): void { if (!client) { - DEBUG_BUILD && logger.warn('No client available to capture log.'); + DEBUG_BUILD && debug.warn('No client available to capture log.'); return; } const { _experiments, release, environment } = client.getOptions(); const { enableLogs = false, beforeSendLog } = _experiments ?? {}; if (!enableLogs) { - DEBUG_BUILD && logger.warn('logging option not enabled, log will not be captured.'); + DEBUG_BUILD && debug.warn('logging option not enabled, log will not be captured.'); return; } @@ -172,7 +172,7 @@ export function _INTERNAL_captureLog( const log = beforeSendLog ? beforeSendLog(processedLog) : processedLog; if (!log) { client.recordDroppedEvent('before_send', 'log_item', 1); - DEBUG_BUILD && logger.warn('beforeSendLog returned null, log will not be captured.'); + DEBUG_BUILD && debug.warn('beforeSendLog returned null, log will not be captured.'); return; } diff --git a/packages/core/src/mcp-server.ts b/packages/core/src/mcp-server.ts index 1a6f626a83f2..fe53d228bd60 100644 --- a/packages/core/src/mcp-server.ts +++ b/packages/core/src/mcp-server.ts @@ -6,7 +6,7 @@ import { } from './semanticAttributes'; import { startSpan, withActiveSpan } from './tracing'; import type { Span } from './types-hoist/span'; -import { logger } from './utils/logger'; +import { debug } from './utils/logger'; import { getActiveSpan } from './utils/spanUtils'; interface MCPTransport { @@ -41,7 +41,7 @@ export function wrapMcpServerWithSentry(mcpServerInstance: S): } if (!isMcpServerInstance(mcpServerInstance)) { - DEBUG_BUILD && logger.warn('Did not patch MCP server. Interface is incompatible.'); + DEBUG_BUILD && debug.warn('Did not patch MCP server. Interface is incompatible.'); return mcpServerInstance; } diff --git a/packages/core/src/profiling.ts b/packages/core/src/profiling.ts index 956f37992f81..ccc8d587bab6 100644 --- a/packages/core/src/profiling.ts +++ b/packages/core/src/profiling.ts @@ -1,7 +1,7 @@ import { getClient } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; import type { Profiler, ProfilingIntegration } from './types-hoist/profiling'; -import { logger } from './utils/logger'; +import { debug } from './utils/logger'; function isProfilingIntegrationWithProfiler( integration: ProfilingIntegration | undefined, @@ -21,19 +21,19 @@ function isProfilingIntegrationWithProfiler( function startProfiler(): void { const client = getClient(); if (!client) { - DEBUG_BUILD && logger.warn('No Sentry client available, profiling is not started'); + DEBUG_BUILD && debug.warn('No Sentry client available, profiling is not started'); return; } const integration = client.getIntegrationByName>('ProfilingIntegration'); if (!integration) { - DEBUG_BUILD && logger.warn('ProfilingIntegration is not available'); + DEBUG_BUILD && debug.warn('ProfilingIntegration is not available'); return; } if (!isProfilingIntegrationWithProfiler(integration)) { - DEBUG_BUILD && logger.warn('Profiler is not available on profiling integration.'); + DEBUG_BUILD && debug.warn('Profiler is not available on profiling integration.'); return; } @@ -47,18 +47,18 @@ function startProfiler(): void { function stopProfiler(): void { const client = getClient(); if (!client) { - DEBUG_BUILD && logger.warn('No Sentry client available, profiling is not started'); + DEBUG_BUILD && debug.warn('No Sentry client available, profiling is not started'); return; } const integration = client.getIntegrationByName>('ProfilingIntegration'); if (!integration) { - DEBUG_BUILD && logger.warn('ProfilingIntegration is not available'); + DEBUG_BUILD && debug.warn('ProfilingIntegration is not available'); return; } if (!isProfilingIntegrationWithProfiler(integration)) { - DEBUG_BUILD && logger.warn('Profiler is not available on profiling integration.'); + DEBUG_BUILD && debug.warn('Profiler is not available on profiling integration.'); return; } diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index 6f9269faec78..dfcc8019ace2 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -1,5 +1,6 @@ /* eslint-disable max-lines */ import type { Client } from './client'; +import { DEBUG_BUILD } from './debug-build'; import { updateSession } from './session'; import type { Attachment } from './types-hoist/attachment'; import type { Breadcrumb } from './types-hoist/breadcrumb'; @@ -16,7 +17,7 @@ import type { Span } from './types-hoist/span'; import type { PropagationContext } from './types-hoist/tracing'; import type { User } from './types-hoist/user'; import { isPlainObject } from './utils/is'; -import { logger } from './utils/logger'; +import { debug } from './utils/logger'; import { merge } from './utils/merge'; import { uuid4 } from './utils/misc'; import { generateTraceId } from './utils/propagationContext'; @@ -573,7 +574,7 @@ export class Scope { const eventId = hint?.event_id || uuid4(); if (!this._client) { - logger.warn('No client configured on scope - will not capture exception!'); + DEBUG_BUILD && debug.warn('No client configured on scope - will not capture exception!'); return eventId; } @@ -602,7 +603,7 @@ export class Scope { const eventId = hint?.event_id || uuid4(); if (!this._client) { - logger.warn('No client configured on scope - will not capture message!'); + DEBUG_BUILD && debug.warn('No client configured on scope - will not capture message!'); return eventId; } @@ -632,7 +633,7 @@ export class Scope { const eventId = hint?.event_id || uuid4(); if (!this._client) { - logger.warn('No client configured on scope - will not capture event!'); + DEBUG_BUILD && debug.warn('No client configured on scope - will not capture event!'); return eventId; } diff --git a/packages/core/src/sdk.ts b/packages/core/src/sdk.ts index 3ebbcbd4b673..db191df436c5 100644 --- a/packages/core/src/sdk.ts +++ b/packages/core/src/sdk.ts @@ -2,7 +2,7 @@ import type { Client } from './client'; import { getCurrentScope } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; import type { ClientOptions } from './types-hoist/options'; -import { consoleSandbox, logger } from './utils/logger'; +import { consoleSandbox, debug } from './utils/logger'; /** A class object that can instantiate Client objects. */ export type ClientClass = new (options: O) => F; @@ -20,9 +20,9 @@ export function initAndBind( ): Client { if (options.debug === true) { if (DEBUG_BUILD) { - logger.enable(); + debug.enable(); } else { - // use `console.warn` rather than `logger.warn` since by non-debug bundles have all `logger.x` statements stripped + // use `console.warn` rather than `debug.warn` since by non-debug bundles have all `debug.x` statements stripped consoleSandbox(() => { // eslint-disable-next-line no-console console.warn('[Sentry] Cannot initialize SDK with `debug` option using a non-debug bundle.'); diff --git a/packages/core/src/server-runtime-client.ts b/packages/core/src/server-runtime-client.ts index 9f41e6142c24..6dd8a4f045a3 100644 --- a/packages/core/src/server-runtime-client.ts +++ b/packages/core/src/server-runtime-client.ts @@ -15,7 +15,7 @@ import type { SeverityLevel } from './types-hoist/severity'; import type { BaseTransportOptions } from './types-hoist/transport'; import { eventFromMessage, eventFromUnknownInput } from './utils/eventbuilder'; import { isPrimitive } from './utils/is'; -import { logger } from './utils/logger'; +import { debug } from './utils/logger'; import { uuid4 } from './utils/misc'; import { resolvedSyncPromise } from './utils/syncpromise'; @@ -134,7 +134,7 @@ export class ServerRuntimeClient< public captureCheckIn(checkIn: CheckIn, monitorConfig?: MonitorConfig, scope?: Scope): string { const id = 'checkInId' in checkIn && checkIn.checkInId ? checkIn.checkInId : uuid4(); if (!this._isEnabled()) { - DEBUG_BUILD && logger.warn('SDK not enabled, will not capture check-in.'); + DEBUG_BUILD && debug.warn('SDK not enabled, will not capture check-in.'); return id; } @@ -179,7 +179,7 @@ export class ServerRuntimeClient< this.getDsn(), ); - DEBUG_BUILD && logger.info('Sending checkin:', checkIn.monitorSlug, checkIn.status); + DEBUG_BUILD && debug.log('Sending checkin:', checkIn.monitorSlug, checkIn.status); // sendEnvelope should not throw // eslint-disable-next-line @typescript-eslint/no-floating-promises diff --git a/packages/core/src/tracing/errors.ts b/packages/core/src/tracing/errors.ts index 28f582f32309..f72cbbfe5349 100644 --- a/packages/core/src/tracing/errors.ts +++ b/packages/core/src/tracing/errors.ts @@ -1,7 +1,7 @@ import { DEBUG_BUILD } from '../debug-build'; import { addGlobalErrorInstrumentationHandler } from '../instrument/globalError'; import { addGlobalUnhandledRejectionInstrumentationHandler } from '../instrument/globalUnhandledRejection'; -import { logger } from '../utils/logger'; +import { debug } from '../utils/logger'; import { getActiveSpan, getRootSpan } from '../utils/spanUtils'; import { SPAN_STATUS_ERROR } from './spanstatus'; @@ -33,7 +33,7 @@ function errorCallback(): void { const rootSpan = activeSpan && getRootSpan(activeSpan); if (rootSpan) { const message = 'internal_error'; - DEBUG_BUILD && logger.log(`[Tracing] Root span: ${message} -> Global error occurred`); + DEBUG_BUILD && debug.log(`[Tracing] Root span: ${message} -> Global error occurred`); rootSpan.setStatus({ code: SPAN_STATUS_ERROR, message }); } } diff --git a/packages/core/src/tracing/idleSpan.ts b/packages/core/src/tracing/idleSpan.ts index be0f1b80fb47..e1e3585579a7 100644 --- a/packages/core/src/tracing/idleSpan.ts +++ b/packages/core/src/tracing/idleSpan.ts @@ -5,7 +5,7 @@ import type { DynamicSamplingContext } from '../types-hoist/envelope'; import type { Span } from '../types-hoist/span'; import type { StartSpanOptions } from '../types-hoist/startSpanOptions'; import { hasSpansEnabled } from '../utils/hasSpansEnabled'; -import { logger } from '../utils/logger'; +import { debug } from '../utils/logger'; import { _setSpanForScope } from '../utils/spanOnScope'; import { getActiveSpan, @@ -277,7 +277,7 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON, _finishReason); } - logger.log(`[Tracing] Idle span "${spanJSON.op}" finished`); + debug.log(`[Tracing] Idle span "${spanJSON.op}" finished`); const childSpans = getSpanDescendants(span).filter(child => child !== span); @@ -288,7 +288,7 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti childSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'cancelled' }); childSpan.end(endTimestamp); DEBUG_BUILD && - logger.log('[Tracing] Cancelling span since span ended early', JSON.stringify(childSpan, undefined, 2)); + debug.log('[Tracing] Cancelling span since span ended early', JSON.stringify(childSpan, undefined, 2)); } const childSpanJSON = spanToJSON(childSpan); @@ -303,9 +303,9 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti if (DEBUG_BUILD) { const stringifiedSpan = JSON.stringify(childSpan, undefined, 2); if (!spanStartedBeforeIdleSpanEnd) { - logger.log('[Tracing] Discarding span since it happened after idle span was finished', stringifiedSpan); + debug.log('[Tracing] Discarding span since it happened after idle span was finished', stringifiedSpan); } else if (!spanEndedBeforeFinalTimeout) { - logger.log('[Tracing] Discarding span since it finished after idle span final timeout', stringifiedSpan); + debug.log('[Tracing] Discarding span since it finished after idle span final timeout', stringifiedSpan); } } @@ -383,7 +383,7 @@ function _startIdleSpan(options: StartSpanOptions): Span { _setSpanForScope(getCurrentScope(), span); - DEBUG_BUILD && logger.log('[Tracing] Started span is an idle span'); + DEBUG_BUILD && debug.log('[Tracing] Started span is an idle span'); return span; } diff --git a/packages/core/src/tracing/logSpans.ts b/packages/core/src/tracing/logSpans.ts index 9728ac6f3399..4cc08fd2388d 100644 --- a/packages/core/src/tracing/logSpans.ts +++ b/packages/core/src/tracing/logSpans.ts @@ -1,6 +1,6 @@ import { DEBUG_BUILD } from '../debug-build'; import type { Span } from '../types-hoist/span'; -import { logger } from '../utils/logger'; +import { debug } from '../utils/logger'; import { getRootSpan, spanIsSampled, spanToJSON } from '../utils/spanUtils'; /** @@ -35,7 +35,7 @@ export function logSpanStart(span: Span): void { } } - logger.log(`${header} + debug.log(`${header} ${infoParts.join('\n ')}`); } @@ -51,5 +51,5 @@ export function logSpanEnd(span: Span): void { const isRootSpan = rootSpan === span; const msg = `[Tracing] Finishing "${op}" ${isRootSpan ? 'root ' : ''}span "${description}" with ID ${spanId}`; - logger.log(msg); + debug.log(msg); } diff --git a/packages/core/src/tracing/measurement.ts b/packages/core/src/tracing/measurement.ts index 5ee0397f8946..115325e41594 100644 --- a/packages/core/src/tracing/measurement.ts +++ b/packages/core/src/tracing/measurement.ts @@ -5,7 +5,7 @@ import { } from '../semanticAttributes'; import type { Measurements, MeasurementUnit } from '../types-hoist/measurement'; import type { TimedEvent } from '../types-hoist/timedEvent'; -import { logger } from '../utils/logger'; +import { debug } from '../utils/logger'; import { getActiveSpan, getRootSpan } from '../utils/spanUtils'; /** @@ -16,7 +16,7 @@ export function setMeasurement(name: string, value: number, unit: MeasurementUni const rootSpan = activeSpan && getRootSpan(activeSpan); if (rootSpan) { - DEBUG_BUILD && logger.log(`[Measurement] Setting measurement on root span: ${name} = ${value} ${unit}`); + DEBUG_BUILD && debug.log(`[Measurement] Setting measurement on root span: ${name} = ${value} ${unit}`); rootSpan.addEvent(name, { [SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE]: value, [SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_UNIT]: unit as string, diff --git a/packages/core/src/tracing/sampling.ts b/packages/core/src/tracing/sampling.ts index 8fec768c9d52..87c056d613a3 100644 --- a/packages/core/src/tracing/sampling.ts +++ b/packages/core/src/tracing/sampling.ts @@ -2,7 +2,7 @@ import { DEBUG_BUILD } from '../debug-build'; import type { Options } from '../types-hoist/options'; import type { SamplingContext } from '../types-hoist/samplingcontext'; import { hasSpansEnabled } from '../utils/hasSpansEnabled'; -import { logger } from '../utils/logger'; +import { debug } from '../utils/logger'; import { parseSampleRate } from '../utils/parseSampleRate'; /** @@ -59,7 +59,7 @@ export function sampleSpan( if (parsedSampleRate === undefined) { DEBUG_BUILD && - logger.warn( + debug.warn( `[Tracing] Discarding root span because of invalid sample rate. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify( sampleRate, )} of type ${JSON.stringify(typeof sampleRate)}.`, @@ -70,7 +70,7 @@ export function sampleSpan( // if the function returned 0 (or false), or if `tracesSampleRate` is 0, it's a sign the transaction should be dropped if (!parsedSampleRate) { DEBUG_BUILD && - logger.log( + debug.log( `[Tracing] Discarding transaction because ${ typeof options.tracesSampler === 'function' ? 'tracesSampler returned 0 or false' @@ -87,7 +87,7 @@ export function sampleSpan( // if we're not going to keep it, we're done if (!shouldSample) { DEBUG_BUILD && - logger.log( + debug.log( `[Tracing] Discarding transaction because it's not included in the random sample (sampling rate = ${Number( sampleRate, )})`, diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index 74b59c655e83..01c0417c48b4 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -25,7 +25,7 @@ import type { import type { SpanStatus } from '../types-hoist/spanStatus'; import type { TimedEvent } from '../types-hoist/timedEvent'; import type { TransactionSource } from '../types-hoist/transaction'; -import { logger } from '../utils/logger'; +import { debug } from '../utils/logger'; import { generateSpanId, generateTraceId } from '../utils/propagationContext'; import { convertSpanLinksForEnvelope, @@ -255,7 +255,7 @@ export class SentrySpan implements Span { attributesOrStartTime?: SpanAttributes | SpanTimeInput, startTime?: SpanTimeInput, ): this { - DEBUG_BUILD && logger.log('[Tracing] Adding an event to span:', name); + DEBUG_BUILD && debug.log('[Tracing] Adding an event to span:', name); const time = isSpanTimeInput(attributesOrStartTime) ? attributesOrStartTime : startTime || timestampInSeconds(); const attributes = isSpanTimeInput(attributesOrStartTime) ? {} : attributesOrStartTime || {}; @@ -305,7 +305,7 @@ export class SentrySpan implements Span { sendSpanEnvelope(createSpanEnvelope([this], client)); } else { DEBUG_BUILD && - logger.log('[Tracing] Discarding standalone span because its trace was not chosen to be sampled.'); + debug.log('[Tracing] Discarding standalone span because its trace was not chosen to be sampled.'); if (client) { client.recordDroppedEvent('sample_rate', 'span'); } @@ -330,7 +330,7 @@ export class SentrySpan implements Span { } if (!this._name) { - DEBUG_BUILD && logger.warn('Transaction has no name, falling back to ``.'); + DEBUG_BUILD && debug.warn('Transaction has no name, falling back to ``.'); this._name = ''; } @@ -389,7 +389,7 @@ export class SentrySpan implements Span { if (hasMeasurements) { DEBUG_BUILD && - logger.log( + debug.log( '[Measurements] Adding measurements to transaction event', JSON.stringify(measurements, undefined, 2), ); diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index bbb956acf042..7062cf3bbb47 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -13,7 +13,7 @@ import type { SentrySpanArguments, Span, SpanTimeInput } from '../types-hoist/sp import type { StartSpanOptions } from '../types-hoist/startSpanOptions'; import { handleCallbackErrors } from '../utils/handleCallbackErrors'; import { hasSpansEnabled } from '../utils/hasSpansEnabled'; -import { logger } from '../utils/logger'; +import { debug } from '../utils/logger'; import { parseSampleRate } from '../utils/parseSampleRate'; import { generateTraceId } from '../utils/propagationContext'; import { _getSpanForScope, _setSpanForScope } from '../utils/spanOnScope'; @@ -287,7 +287,7 @@ export function startNewTrace(callback: () => T): T { traceId: generateTraceId(), sampleRand: Math.random(), }); - DEBUG_BUILD && logger.info(`Starting a new trace with id ${scope.getPropagationContext().traceId}`); + DEBUG_BUILD && debug.log(`Starting a new trace with id ${scope.getPropagationContext().traceId}`); return withActiveSpan(null, callback); }); } @@ -447,7 +447,7 @@ function _startRootSpan(spanArguments: SentrySpanArguments, scope: Scope, parent }); if (!sampled && client) { - DEBUG_BUILD && logger.log('[Tracing] Discarding root span because its trace was not chosen to be sampled.'); + DEBUG_BUILD && debug.log('[Tracing] Discarding root span because its trace was not chosen to be sampled.'); client.recordDroppedEvent('sample_rate', 'transaction'); } diff --git a/packages/core/src/transports/base.ts b/packages/core/src/transports/base.ts index 9c74bb4261db..d37b7cbb1b05 100644 --- a/packages/core/src/transports/base.ts +++ b/packages/core/src/transports/base.ts @@ -13,7 +13,7 @@ import { forEachEnvelopeItem, serializeEnvelope, } from '../utils/envelope'; -import { logger } from '../utils/logger'; +import { debug } from '../utils/logger'; import { type PromiseBuffer, makePromiseBuffer, SENTRY_BUFFER_FULL_ERROR } from '../utils/promisebuffer'; import { type RateLimits, isRateLimited, updateRateLimits } from '../utils/ratelimit'; import { resolvedSyncPromise } from '../utils/syncpromise'; @@ -68,7 +68,7 @@ export function createTransport( response => { // We don't want to throw on NOK responses, but we want to at least log them if (response.statusCode !== undefined && (response.statusCode < 200 || response.statusCode >= 300)) { - DEBUG_BUILD && logger.warn(`Sentry responded with status code ${response.statusCode} to sent event.`); + DEBUG_BUILD && debug.warn(`Sentry responded with status code ${response.statusCode} to sent event.`); } rateLimits = updateRateLimits(rateLimits, response); @@ -76,7 +76,7 @@ export function createTransport( }, error => { recordEnvelopeLoss('network_error'); - DEBUG_BUILD && logger.error('Encountered error running transport request:', error); + DEBUG_BUILD && debug.error('Encountered error running transport request:', error); throw error; }, ); @@ -85,7 +85,7 @@ export function createTransport( result => result, error => { if (error === SENTRY_BUFFER_FULL_ERROR) { - DEBUG_BUILD && logger.error('Skipped sending event because buffer is full.'); + DEBUG_BUILD && debug.error('Skipped sending event because buffer is full.'); recordEnvelopeLoss('queue_overflow'); return resolvedSyncPromise({}); } else { diff --git a/packages/core/src/transports/offline.ts b/packages/core/src/transports/offline.ts index b7713b12bb2b..e8b7437b9f3a 100644 --- a/packages/core/src/transports/offline.ts +++ b/packages/core/src/transports/offline.ts @@ -2,7 +2,7 @@ import { DEBUG_BUILD } from '../debug-build'; import type { Envelope } from '../types-hoist/envelope'; import type { InternalBaseTransportOptions, Transport, TransportMakeRequestResponse } from '../types-hoist/transport'; import { envelopeContainsItemType } from '../utils/envelope'; -import { logger } from '../utils/logger'; +import { debug } from '../utils/logger'; import { parseRetryAfterHeader } from '../utils/ratelimit'; export const MIN_DELAY = 100; // 100 ms @@ -64,7 +64,7 @@ export function makeOfflineTransport( createTransport: (options: TO) => Transport, ): (options: TO & OfflineTransportOptions) => Transport { function log(...args: unknown[]): void { - DEBUG_BUILD && logger.info('[Offline]:', ...args); + DEBUG_BUILD && debug.log('[Offline]:', ...args); } return options => { diff --git a/packages/core/src/utils/baggage.ts b/packages/core/src/utils/baggage.ts index 0e90457db97b..6696169aad79 100644 --- a/packages/core/src/utils/baggage.ts +++ b/packages/core/src/utils/baggage.ts @@ -1,7 +1,7 @@ import { DEBUG_BUILD } from '../debug-build'; import type { DynamicSamplingContext } from '../types-hoist/envelope'; import { isString } from './is'; -import { logger } from './logger'; +import { debug } from './logger'; export const SENTRY_BAGGAGE_KEY_PREFIX = 'sentry-'; @@ -150,7 +150,7 @@ export function objectToBaggageHeader(object: Record): string | const newBaggageHeader = currentIndex === 0 ? baggageEntry : `${baggageHeader},${baggageEntry}`; if (newBaggageHeader.length > MAX_BAGGAGE_STRING_LENGTH) { DEBUG_BUILD && - logger.warn( + debug.warn( `Not adding key: ${objectKey} with val: ${objectValue} to baggage header due to exceeding baggage size limits.`, ); return baggageHeader; diff --git a/packages/core/src/utils/dsn.ts b/packages/core/src/utils/dsn.ts index b22e2baed1f8..57bb07a53014 100644 --- a/packages/core/src/utils/dsn.ts +++ b/packages/core/src/utils/dsn.ts @@ -1,6 +1,6 @@ import { DEBUG_BUILD } from '../debug-build'; import type { DsnComponents, DsnLike, DsnProtocol } from '../types-hoist/dsn'; -import { consoleSandbox, logger } from './logger'; +import { consoleSandbox, debug } from './logger'; /** Regular expression used to extract org ID from a DSN host. */ const ORG_ID_REGEX = /^o(\d+)\./; @@ -89,7 +89,7 @@ function validateDsn(dsn: DsnComponents): boolean { const requiredComponents: ReadonlyArray = ['protocol', 'publicKey', 'host', 'projectId']; const hasMissingRequiredComponent = requiredComponents.find(component => { if (!dsn[component]) { - logger.error(`Invalid Sentry Dsn: ${component} missing`); + debug.error(`Invalid Sentry Dsn: ${component} missing`); return true; } return false; @@ -100,17 +100,17 @@ function validateDsn(dsn: DsnComponents): boolean { } if (!projectId.match(/^\d+$/)) { - logger.error(`Invalid Sentry Dsn: Invalid projectId ${projectId}`); + debug.error(`Invalid Sentry Dsn: Invalid projectId ${projectId}`); return false; } if (!isValidProtocol(protocol)) { - logger.error(`Invalid Sentry Dsn: Invalid protocol ${protocol}`); + debug.error(`Invalid Sentry Dsn: Invalid protocol ${protocol}`); return false; } if (port && isNaN(parseInt(port, 10))) { - logger.error(`Invalid Sentry Dsn: Invalid port ${port}`); + debug.error(`Invalid Sentry Dsn: Invalid port ${port}`); return false; } diff --git a/packages/core/src/utils/env.ts b/packages/core/src/utils/env.ts index b85c91c55a8d..6f8c7ca8e946 100644 --- a/packages/core/src/utils/env.ts +++ b/packages/core/src/utils/env.ts @@ -3,7 +3,7 @@ * constants, which can be overridden during build. By guarding certain pieces of code with functions that return these * constants, we can control whether or not they appear in the final bundle. (Any code guarded by a false condition will * never run, and will hence be dropped during treeshaking.) The two primary uses for this are stripping out calls to - * `logger` and preventing node-related code from appearing in browser bundles. + * `debug` and preventing node-related code from appearing in browser bundles. * * Attention: * This file should not be used to define constants/flags that are intended to be used for tree-shaking conducted by diff --git a/packages/core/src/utils/featureFlags.ts b/packages/core/src/utils/featureFlags.ts index a055e8875c06..c79cfa9c9b3b 100644 --- a/packages/core/src/utils/featureFlags.ts +++ b/packages/core/src/utils/featureFlags.ts @@ -2,7 +2,7 @@ import { getCurrentScope } from '../currentScopes'; import { DEBUG_BUILD } from '../debug-build'; import { type Event } from '../types-hoist/event'; import { type Span } from '../types-hoist/span'; -import { logger } from '../utils/logger'; +import { debug } from '../utils/logger'; import { GLOBAL_OBJ } from '../utils/worldwide'; import { getActiveSpan } from './spanUtils'; @@ -95,7 +95,7 @@ export function _INTERNAL_insertToFlagBuffer( } if (flags.length > maxSize) { - DEBUG_BUILD && logger.error(`[Feature Flags] insertToFlagBuffer called on a buffer larger than maxSize=${maxSize}`); + DEBUG_BUILD && debug.error(`[Feature Flags] insertToFlagBuffer called on a buffer larger than maxSize=${maxSize}`); return; } diff --git a/packages/core/src/utils/logger.ts b/packages/core/src/utils/logger.ts index d17f67f6f6e6..590d02afb34d 100644 --- a/packages/core/src/utils/logger.ts +++ b/packages/core/src/utils/logger.ts @@ -17,6 +17,15 @@ export interface Logger { trace(...args: Parameters): void; } +export interface SentryDebugLogger { + disable(): void; + enable(): void; + isEnabled(): boolean; + log(...args: Parameters): void; + warn(...args: Parameters): void; + error(...args: Parameters): void; +} + export const CONSOLE_LEVELS: readonly ConsoleLevel[] = [ 'debug', 'info', @@ -102,7 +111,7 @@ function error(...args: Parameters): void { _maybeLog('error', ...args); } -function debug(...args: Parameters): void { +function _debug(...args: Parameters): void { _maybeLog('debug', ...args); } @@ -154,9 +163,27 @@ export const logger = { /** Log an error. */ error, /** Log a debug message. */ - debug, + debug: _debug, /** Log an assertion. */ assert, /** Log a trace. */ trace, } satisfies Logger; + +/** + * This is a logger singleton which either logs things or no-ops if logging is not enabled. + */ +export const debug = { + /** Enable logging. */ + enable, + /** Disable logging. */ + disable, + /** Check if logging is enabled. */ + isEnabled, + /** Log a message. */ + log, + /** Log a warning. */ + warn, + /** Log an error. */ + error, +} satisfies SentryDebugLogger; diff --git a/packages/core/src/utils/node.ts b/packages/core/src/utils/node.ts index 94e8001863aa..d7747d6ae6c0 100644 --- a/packages/core/src/utils/node.ts +++ b/packages/core/src/utils/node.ts @@ -1,6 +1,6 @@ /** * NOTE: In order to avoid circular dependencies, if you add a function to this module and it needs to print something, - * you must either a) use `console.log` rather than the logger, or b) put your function elsewhere. + * you must either a) use `console.log` rather than the `debug` singleton, or b) put your function elsewhere. */ import { isBrowserBundle } from './env'; diff --git a/packages/core/src/utils/object.ts b/packages/core/src/utils/object.ts index c973ad056dee..208bd4d0ec8f 100644 --- a/packages/core/src/utils/object.ts +++ b/packages/core/src/utils/object.ts @@ -3,7 +3,7 @@ import { DEBUG_BUILD } from '../debug-build'; import type { WrappedFunction } from '../types-hoist/wrappedfunction'; import { htmlTreeAsString } from './browser'; import { isElement, isError, isEvent, isInstanceOf, isPrimitive } from './is'; -import { logger } from './logger'; +import { debug } from './logger'; import { truncate } from './string'; /** @@ -42,7 +42,7 @@ export function fill(source: { [key: string]: any }, name: string, replacementFa try { source[name] = wrapped; } catch { - DEBUG_BUILD && logger.log(`Failed to replace method "${name}" in object`, source); + DEBUG_BUILD && debug.log(`Failed to replace method "${name}" in object`, source); } } @@ -62,7 +62,7 @@ export function addNonEnumerableProperty(obj: object, name: string, value: unkno configurable: true, }); } catch (o_O) { - DEBUG_BUILD && logger.log(`Failed to add non-enumerable property "${name}" to object`, obj); + DEBUG_BUILD && debug.log(`Failed to add non-enumerable property "${name}" to object`, obj); } } diff --git a/packages/core/src/utils/supports.ts b/packages/core/src/utils/supports.ts index b5c96a7e50d8..51f839ad8918 100644 --- a/packages/core/src/utils/supports.ts +++ b/packages/core/src/utils/supports.ts @@ -1,5 +1,5 @@ import { DEBUG_BUILD } from '../debug-build'; -import { logger } from './logger'; +import { debug } from './logger'; import { GLOBAL_OBJ } from './worldwide'; const WINDOW = GLOBAL_OBJ as unknown as Window; @@ -133,8 +133,7 @@ export function supportsNativeFetch(): boolean { } doc.head.removeChild(sandbox); } catch (err) { - DEBUG_BUILD && - logger.warn('Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ', err); + DEBUG_BUILD && debug.warn('Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ', err); } } diff --git a/packages/core/src/utils/traceData.ts b/packages/core/src/utils/traceData.ts index e65271cf5a23..2a11ed56f24a 100644 --- a/packages/core/src/utils/traceData.ts +++ b/packages/core/src/utils/traceData.ts @@ -8,7 +8,7 @@ import { getDynamicSamplingContextFromScope, getDynamicSamplingContextFromSpan } import type { Span } from '../types-hoist/span'; import type { SerializedTraceData } from '../types-hoist/tracing'; import { dynamicSamplingContextToSentryBaggageHeader } from './baggage'; -import { logger } from './logger'; +import { debug } from './logger'; import { getActiveSpan, spanToTraceHeader } from './spanUtils'; import { generateSentryTraceHeader, TRACEPARENT_REGEXP } from './tracing'; @@ -43,7 +43,7 @@ export function getTraceData(options: { span?: Span; scope?: Scope; client?: Cli const isValidSentryTraceHeader = TRACEPARENT_REGEXP.test(sentryTrace); if (!isValidSentryTraceHeader) { - logger.warn('Invalid sentry-trace data. Cannot generate trace data'); + debug.warn('Invalid sentry-trace data. Cannot generate trace data'); return {}; } diff --git a/packages/core/src/utils/worldwide.ts b/packages/core/src/utils/worldwide.ts index 70196e4b0c8b..0b7d763f3007 100644 --- a/packages/core/src/utils/worldwide.ts +++ b/packages/core/src/utils/worldwide.ts @@ -1,6 +1,6 @@ /** * NOTE: In order to avoid circular dependencies, if you add a function to this module and it needs to print something, - * you must either a) use `console.log` rather than the logger, or b) put your function elsewhere. + * you must either a) use `console.log` rather than the `debug` singleton, or b) put your function elsewhere. * * Note: This file was originally called `global.ts`, but was changed to unblock users which might be doing * string replaces with bundlers like Vite for `global` (would break imports that rely on importing from utils/src/global). diff --git a/packages/core/test/lib/client.test.ts b/packages/core/test/lib/client.test.ts index 844b49e4b55f..7962b43cf757 100644 --- a/packages/core/test/lib/client.test.ts +++ b/packages/core/test/lib/client.test.ts @@ -348,8 +348,8 @@ describe('Client', () => { expect(clientEventFromException).toHaveBeenCalledTimes(1); }); - test('captures logger message', () => { - const logSpy = vi.spyOn(loggerModule.logger, 'log').mockImplementation(() => undefined); + test('captures debug message', () => { + const logSpy = vi.spyOn(loggerModule.debug, 'log').mockImplementation(() => undefined); const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); @@ -440,8 +440,8 @@ describe('Client', () => { ); }); - test('captures logger message', () => { - const logSpy = vi.spyOn(loggerModule.logger, 'log').mockImplementation(() => undefined); + test('captures debug message', () => { + const logSpy = vi.spyOn(loggerModule.debug, 'log').mockImplementation(() => undefined); const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); @@ -1207,7 +1207,7 @@ describe('Client', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, beforeSend }); const client = new TestClient(options); const captureExceptionSpy = vi.spyOn(client, 'captureException'); - const loggerLogSpy = vi.spyOn(loggerModule.logger, 'log'); + const loggerLogSpy = vi.spyOn(loggerModule.debug, 'log'); client.captureEvent({ message: 'hello' }); @@ -1226,7 +1226,7 @@ describe('Client', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, beforeSendTransaction }); const client = new TestClient(options); const captureExceptionSpy = vi.spyOn(client, 'captureException'); - const loggerLogSpy = vi.spyOn(loggerModule.logger, 'log'); + const loggerLogSpy = vi.spyOn(loggerModule.debug, 'log'); client.captureEvent({ transaction: '/dogs/are/great', type: 'transaction' }); @@ -1288,7 +1288,7 @@ describe('Client', () => { // @ts-expect-error we need to test regular-js behavior const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, beforeSend }); const client = new TestClient(options); - const loggerWarnSpy = vi.spyOn(loggerModule.logger, 'warn'); + const loggerWarnSpy = vi.spyOn(loggerModule.debug, 'warn'); client.captureEvent({ message: 'hello' }); @@ -1307,7 +1307,7 @@ describe('Client', () => { // @ts-expect-error we need to test regular-js behavior const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, beforeSendTransaction }); const client = new TestClient(options); - const loggerWarnSpy = vi.spyOn(loggerModule.logger, 'warn'); + const loggerWarnSpy = vi.spyOn(loggerModule.debug, 'warn'); client.captureEvent({ transaction: '/dogs/are/great', type: 'transaction' }); @@ -1551,7 +1551,7 @@ describe('Client', () => { const client = new TestClient(getDefaultTestClientOptions({ dsn: PUBLIC_DSN })); const captureExceptionSpy = vi.spyOn(client, 'captureException'); - const loggerLogSpy = vi.spyOn(loggerModule.logger, 'log'); + const loggerLogSpy = vi.spyOn(loggerModule.debug, 'log'); const scope = new Scope(); scope.addEventProcessor(() => null); @@ -1569,7 +1569,7 @@ describe('Client', () => { const client = new TestClient(getDefaultTestClientOptions({ dsn: PUBLIC_DSN })); const captureExceptionSpy = vi.spyOn(client, 'captureException'); - const loggerLogSpy = vi.spyOn(loggerModule.logger, 'log'); + const loggerLogSpy = vi.spyOn(loggerModule.debug, 'log'); const scope = new Scope(); scope.addEventProcessor(() => null); @@ -1668,7 +1668,7 @@ describe('Client', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const captureExceptionSpy = vi.spyOn(client, 'captureException'); - const loggerWarnSpy = vi.spyOn(loggerModule.logger, 'warn'); + const loggerWarnSpy = vi.spyOn(loggerModule.debug, 'warn'); const scope = new Scope(); const exception = new Error('sorry'); scope.addEventProcessor(() => { @@ -1701,8 +1701,8 @@ describe('Client', () => { expect(recordLostEventSpy).toHaveBeenCalledWith('sample_rate', 'error'); }); - test('captures logger message', () => { - const logSpy = vi.spyOn(loggerModule.logger, 'log').mockImplementation(() => undefined); + test('captures debug message', () => { + const logSpy = vi.spyOn(loggerModule.debug, 'log').mockImplementation(() => undefined); const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); diff --git a/packages/core/test/lib/integration.test.ts b/packages/core/test/lib/integration.test.ts index 58fabde15ef7..e0e7ff3e5740 100644 --- a/packages/core/test/lib/integration.test.ts +++ b/packages/core/test/lib/integration.test.ts @@ -4,7 +4,7 @@ import { addIntegration, getIntegrationsToSetup, installedIntegrations, setupInt import { setCurrentClient } from '../../src/sdk'; import type { Integration } from '../../src/types-hoist/integration'; import type { Options } from '../../src/types-hoist/options'; -import { logger } from '../../src/utils/logger'; +import { debug } from '../../src/utils/logger'; import { getDefaultTestClientOptions, TestClient } from '../mocks/client'; function getTestClient(): TestClient { @@ -594,7 +594,7 @@ describe('addIntegration', () => { }); it('works with a client setup', () => { - const warnings = vi.spyOn(logger, 'warn'); + const warnings = vi.spyOn(debug, 'warn'); class CustomIntegration implements Integration { name = 'test'; @@ -612,7 +612,7 @@ describe('addIntegration', () => { }); it('works without a client setup', () => { - const warnings = vi.spyOn(logger, 'warn'); + const warnings = vi.spyOn(debug, 'warn'); class CustomIntegration implements Integration { name = 'test'; setupOnce = vi.fn(); @@ -653,7 +653,7 @@ describe('addIntegration', () => { }); it('does not trigger hooks if already installed', () => { - const logs = vi.spyOn(logger, 'log'); + const logs = vi.spyOn(debug, 'log'); class CustomIntegration implements Integration { name = 'test'; diff --git a/packages/core/test/lib/integrations/captureconsole.test.ts b/packages/core/test/lib/integrations/captureconsole.test.ts index faabc2590aac..bbc3af2722e9 100644 --- a/packages/core/test/lib/integrations/captureconsole.test.ts +++ b/packages/core/test/lib/integrations/captureconsole.test.ts @@ -125,7 +125,7 @@ describe('CaptureConsole setup', () => { expect(captureMessage).toHaveBeenCalledWith('', { extra: { arguments: [] }, level: 'log' }); }); - it('should add an event processor that sets the `logger` field of events', () => { + it('should add an event processor that sets the `debug` field of events', () => { const captureConsole = captureConsoleIntegration({ levels: ['log'] }); captureConsole.setup?.(mockClient); diff --git a/packages/core/test/lib/logs/exports.test.ts b/packages/core/test/lib/logs/exports.test.ts index 0dd73f064619..1f93e3609f2f 100644 --- a/packages/core/test/lib/logs/exports.test.ts +++ b/packages/core/test/lib/logs/exports.test.ts @@ -100,7 +100,7 @@ describe('_INTERNAL_captureLog', () => { }); it('does not capture logs when enableLogs experiment is not enabled', () => { - const logWarnSpy = vi.spyOn(loggerModule.logger, 'warn').mockImplementation(() => undefined); + const logWarnSpy = vi.spyOn(loggerModule.debug, 'warn').mockImplementation(() => undefined); const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); @@ -332,7 +332,7 @@ describe('_INTERNAL_captureLog', () => { it('drops logs when beforeSendLog returns null', () => { const beforeSendLog = vi.fn().mockReturnValue(null); const recordDroppedEventSpy = vi.spyOn(TestClient.prototype, 'recordDroppedEvent'); - const loggerWarnSpy = vi.spyOn(loggerModule.logger, 'warn').mockImplementation(() => undefined); + const loggerWarnSpy = vi.spyOn(loggerModule.debug, 'warn').mockImplementation(() => undefined); const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, diff --git a/packages/core/test/lib/utils/dsn.test.ts b/packages/core/test/lib/utils/dsn.test.ts index 3dc081866703..81c4f9f9ead2 100644 --- a/packages/core/test/lib/utils/dsn.test.ts +++ b/packages/core/test/lib/utils/dsn.test.ts @@ -1,13 +1,13 @@ import { beforeEach, describe, expect, it, test, vi } from 'vitest'; import { DEBUG_BUILD } from '../../../src/debug-build'; import { dsnToString, extractOrgIdFromDsnHost, makeDsn } from '../../../src/utils/dsn'; -import { logger } from '../../../src/utils/logger'; +import { debug } from '../../../src/utils/logger'; function testIf(condition: boolean) { return condition ? test : test.skip; } -const loggerErrorSpy = vi.spyOn(logger, 'error').mockImplementation(() => {}); +const loggerErrorSpy = vi.spyOn(debug, 'error').mockImplementation(() => {}); const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); describe('Dsn', () => { diff --git a/packages/core/test/lib/utils/featureFlags.test.ts b/packages/core/test/lib/utils/featureFlags.test.ts index 8f6e512bca7b..fe76eb8da1ff 100644 --- a/packages/core/test/lib/utils/featureFlags.test.ts +++ b/packages/core/test/lib/utils/featureFlags.test.ts @@ -5,7 +5,7 @@ import { _INTERNAL_insertFlagToScope, _INTERNAL_insertToFlagBuffer, } from '../../../src/utils/featureFlags'; -import { logger } from '../../../src/utils/logger'; +import { debug } from '../../../src/utils/logger'; describe('flags', () => { describe('insertFlagToScope()', () => { @@ -26,7 +26,7 @@ describe('flags', () => { }); describe('insertToFlagBuffer()', () => { - const loggerSpy = vi.spyOn(logger, 'error'); + const loggerSpy = vi.spyOn(debug, 'error'); afterEach(() => { loggerSpy.mockClear(); diff --git a/packages/core/test/lib/utils/logger.test.ts b/packages/core/test/lib/utils/logger.test.ts index 21d26b273dea..4bac1b2b64e4 100644 --- a/packages/core/test/lib/utils/logger.test.ts +++ b/packages/core/test/lib/utils/logger.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { logger } from '../../../src'; +import { debug, logger } from '../../../src'; import { getMainCarrier, getSentryCarrier } from '../../../src/carrier'; describe('logger', () => { @@ -55,3 +55,57 @@ describe('logger', () => { expect(logger.isEnabled()).toBe(false); }); }); + +describe('debug', () => { + beforeEach(() => { + vi.clearAllMocks(); + getSentryCarrier(getMainCarrier()).loggerSettings = undefined; + }); + + it('works with defaults', () => { + const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + debug.log('test'); + expect(consoleLogSpy).not.toHaveBeenCalled(); + expect(debug.isEnabled()).toBe(false); + }); + + it('allows to enable and disable logging', () => { + const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + + debug.log('test'); + expect(debug.isEnabled()).toBe(false); + expect(consoleLogSpy).not.toHaveBeenCalled(); + + debug.enable(); + debug.log('test'); + expect(debug.isEnabled()).toBe(true); + expect(consoleLogSpy).toHaveBeenCalledTimes(1); + + debug.log('test2'); + expect(consoleLogSpy).toHaveBeenCalledTimes(2); + + debug.disable(); + + debug.log('test3'); + expect(debug.isEnabled()).toBe(false); + expect(consoleLogSpy).toHaveBeenCalledTimes(2); + }); + + it('picks up enabled logger settings from carrier', () => { + getSentryCarrier(getMainCarrier()).loggerSettings = { enabled: true }; + + const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + debug.log('test'); + expect(consoleLogSpy).toHaveBeenCalledTimes(1); + expect(debug.isEnabled()).toBe(true); + }); + + it('picks up disabled logger settings from carrier', () => { + getSentryCarrier(getMainCarrier()).loggerSettings = { enabled: false }; + + const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + debug.log('test'); + expect(consoleLogSpy).toHaveBeenCalledTimes(0); + expect(debug.isEnabled()).toBe(false); + }); +}); diff --git a/packages/nextjs/test/clientSdk.test.ts b/packages/nextjs/test/clientSdk.test.ts index b1e7884d5ad2..4ecd436b709a 100644 --- a/packages/nextjs/test/clientSdk.test.ts +++ b/packages/nextjs/test/clientSdk.test.ts @@ -1,5 +1,5 @@ import type { Integration } from '@sentry/core'; -import { getGlobalScope, getIsolationScope, logger } from '@sentry/core'; +import { debug, getGlobalScope, getIsolationScope } from '@sentry/core'; import * as SentryReact from '@sentry/react'; import { getClient, getCurrentScope, WINDOW } from '@sentry/react'; import { JSDOM } from 'jsdom'; @@ -7,7 +7,7 @@ import { afterAll, afterEach, describe, expect, it, vi } from 'vitest'; import { breadcrumbsIntegration, browserTracingIntegration, init } from '../src/client'; const reactInit = vi.spyOn(SentryReact, 'init'); -const loggerLogSpy = vi.spyOn(logger, 'log'); +const debugLogSpy = vi.spyOn(debug, 'log'); // We're setting up JSDom here because the Next.js routing instrumentations requires a few things to be present on pageload: // 1. Access to window.document API for `window.document.getElementById` @@ -90,7 +90,7 @@ describe('Client init()', () => { }); expect(transportSend).not.toHaveBeenCalled(); - expect(loggerLogSpy).toHaveBeenCalledWith('An event processor returned `null`, will not send event.'); + expect(debugLogSpy).toHaveBeenCalledWith('An event processor returned `null`, will not send event.'); }); describe('integrations', () => { From b9db2e687acde679019ec919e51fe56567dc2a86 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Fri, 11 Jul 2025 00:39:51 +0100 Subject: [PATCH 06/11] ref(feedback) set submission timeout to 30 seconds (#16918) --- packages/feedback/src/core/sendFeedback.ts | 4 ++-- packages/feedback/test/core/sendFeedback.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/feedback/src/core/sendFeedback.ts b/packages/feedback/src/core/sendFeedback.ts index a6d5b27163cf..f3ae1504f9b4 100644 --- a/packages/feedback/src/core/sendFeedback.ts +++ b/packages/feedback/src/core/sendFeedback.ts @@ -34,8 +34,8 @@ export const sendFeedback: SendFeedback = ( // We want to wait for the feedback to be sent (or not) return new Promise((resolve, reject) => { - // After 5s, we want to clear anyhow - const timeout = setTimeout(() => reject('Unable to determine if Feedback was correctly sent.'), 5_000); + // After 30s, we want to clear anyhow + const timeout = setTimeout(() => reject('Unable to determine if Feedback was correctly sent.'), 30_000); const cleanup = client.on('afterSendEvent', (event: Event, response: TransportMakeRequestResponse) => { if (event.event_id !== eventId) { diff --git a/packages/feedback/test/core/sendFeedback.test.ts b/packages/feedback/test/core/sendFeedback.test.ts index d9bf4b80162a..2d728e710178 100644 --- a/packages/feedback/test/core/sendFeedback.test.ts +++ b/packages/feedback/test/core/sendFeedback.test.ts @@ -321,7 +321,7 @@ describe('sendFeedback', () => { mockSdk(); vi.spyOn(getClient()!.getTransport()!, 'send').mockImplementation(() => { - return new Promise(resolve => setTimeout(resolve, 10_000)); + return new Promise(resolve => setTimeout(resolve, 40_000)); }); const promise = sendFeedback({ @@ -330,7 +330,7 @@ describe('sendFeedback', () => { message: 'mi', }); - vi.advanceTimersByTime(5_000); + vi.advanceTimersByTime(30_000); await expect(promise).rejects.toMatch('Unable to determine if Feedback was correctly sent.'); From dbdfe48be241b1f7813c609372c2419d6c29a990 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 11 Jul 2025 09:52:35 +0200 Subject: [PATCH 07/11] ref(browser): Unify CLS/LCP recording and add recording event attribute (#16866) Refactor our CLS and LCP tracking and recording logic. We now have a `listenForWebVitalReportEvents` helper function which listens to page hides and navigation span starts and ensures that the standalone span logic is only called once per web vital. Previously, we had this logic in the CLS and LCP function and it was quite a lot of duplicated and identical code. --- .../web-vitals-cls-standalone-spans/test.ts | 10 +++ .../web-vitals-lcp-standalone-spans/test.ts | 18 +++-- packages/browser-utils/src/metrics/cls.ts | 73 ++++------------- packages/browser-utils/src/metrics/lcp.ts | 67 +++------------ packages/browser-utils/src/metrics/utils.ts | 81 ++++++++++++++++++- 5 files changed, 125 insertions(+), 124 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts index b5c47fdd3ab7..24c949c63afa 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts @@ -65,6 +65,7 @@ sentryTest('captures a "GOOD" CLS vital with its source as a standalone span', a 'sentry.exclusive_time': 0, 'sentry.op': 'ui.webvital.cls', 'sentry.origin': 'auto.http.browser.cls', + 'sentry.report_event': 'pagehide', transaction: expect.stringContaining('index.html'), 'user_agent.original': expect.stringContaining('Chrome'), 'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/), @@ -134,6 +135,7 @@ sentryTest('captures a "MEH" CLS vital with its source as a standalone span', as 'sentry.exclusive_time': 0, 'sentry.op': 'ui.webvital.cls', 'sentry.origin': 'auto.http.browser.cls', + 'sentry.report_event': 'pagehide', transaction: expect.stringContaining('index.html'), 'user_agent.original': expect.stringContaining('Chrome'), 'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/), @@ -201,6 +203,7 @@ sentryTest('captures a "POOR" CLS vital with its source as a standalone span.', 'sentry.exclusive_time': 0, 'sentry.op': 'ui.webvital.cls', 'sentry.origin': 'auto.http.browser.cls', + 'sentry.report_event': 'pagehide', transaction: expect.stringContaining('index.html'), 'user_agent.original': expect.stringContaining('Chrome'), 'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/), @@ -269,6 +272,7 @@ sentryTest( 'sentry.exclusive_time': 0, 'sentry.op': 'ui.webvital.cls', 'sentry.origin': 'auto.http.browser.cls', + 'sentry.report_event': 'pagehide', transaction: expect.stringContaining('index.html'), 'user_agent.original': expect.stringContaining('Chrome'), 'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/), @@ -342,6 +346,8 @@ sentryTest( // Ensure the CLS span is connected to the pageload span and trace expect(spanEnvelopeItem.data?.['sentry.pageload.span_id']).toBe(pageloadSpanId); expect(spanEnvelopeItem.trace_id).toEqual(pageloadTraceId); + + expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('pagehide'); }, ); @@ -374,6 +380,8 @@ sentryTest('sends CLS of the initial page when soft-navigating to a new page', a expect(spanEnvelopeItem.measurements?.cls?.value).toBeLessThan(0.15); expect(spanEnvelopeItem.data?.['sentry.pageload.span_id']).toBe(pageloadEventData.contexts?.trace?.span_id); expect(spanEnvelopeItem.trace_id).toEqual(pageloadTraceId); + + expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('navigation'); }); sentryTest("doesn't send further CLS after the first navigation", async ({ getLocalTestUrl, page }) => { @@ -398,6 +406,7 @@ sentryTest("doesn't send further CLS after the first navigation", async ({ getLo const spanEnvelope = (await spanEnvelopePromise)[0]; const spanEnvelopeItem = spanEnvelope[1][0][1]; expect(spanEnvelopeItem.measurements?.cls?.value).toBeGreaterThan(0); + expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('navigation'); getMultipleSentryEnvelopeRequests(page, 1, { envelopeType: 'span' }, () => { throw new Error('Unexpected span - This should not happen!'); @@ -442,6 +451,7 @@ sentryTest("doesn't send further CLS after the first page hide", async ({ getLoc const spanEnvelope = (await spanEnvelopePromise)[0]; const spanEnvelopeItem = spanEnvelope[1][0][1]; expect(spanEnvelopeItem.measurements?.cls?.value).toBeGreaterThan(0); + expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('pagehide'); getMultipleSentryEnvelopeRequests(page, 1, { envelopeType: 'span' }, () => { throw new Error('Unexpected span - This should not happen!'); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-standalone-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-standalone-spans/test.ts index 3310c7b95004..ad21a6240793 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-standalone-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-standalone-spans/test.ts @@ -64,6 +64,7 @@ sentryTest('captures LCP vital as a standalone span', async ({ getLocalTestUrl, 'sentry.exclusive_time': 0, 'sentry.op': 'ui.webvital.lcp', 'sentry.origin': 'auto.http.browser.lcp', + 'sentry.report_event': 'pagehide', transaction: expect.stringContaining('index.html'), 'user_agent.original': expect.stringContaining('Chrome'), 'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/), @@ -181,6 +182,7 @@ sentryTest('sends LCP of the initial page when soft-navigating to a new page', a expect(spanEnvelopeItem.measurements?.lcp?.value).toBeGreaterThan(0); expect(spanEnvelopeItem.data?.['sentry.pageload.span_id']).toBe(pageloadEventData.contexts?.trace?.span_id); + expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('navigation'); expect(spanEnvelopeItem.trace_id).toBe(pageloadEventData.contexts?.trace?.trace_id); }); @@ -194,10 +196,10 @@ sentryTest("doesn't send further LCP after the first navigation", async ({ getLo const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const pageloadEventData = await getFirstSentryEnvelopeRequest(page, url); - expect(eventData.type).toBe('transaction'); - expect(eventData.contexts?.trace?.op).toBe('pageload'); + expect(pageloadEventData.type).toBe('transaction'); + expect(pageloadEventData.contexts?.trace?.op).toBe('pageload'); const spanEnvelopePromise = getMultipleSentryEnvelopeRequests( page, @@ -214,6 +216,8 @@ sentryTest("doesn't send further LCP after the first navigation", async ({ getLo const spanEnvelope = (await spanEnvelopePromise)[0]; const spanEnvelopeItem = spanEnvelope[1][0][1]; expect(spanEnvelopeItem.measurements?.lcp?.value).toBeGreaterThan(0); + expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('navigation'); + expect(spanEnvelopeItem.trace_id).toBe(pageloadEventData.contexts?.trace?.trace_id); getMultipleSentryEnvelopeRequests(page, 1, { envelopeType: 'span' }, () => { throw new Error('Unexpected span - This should not happen!'); @@ -246,10 +250,10 @@ sentryTest("doesn't send further LCP after the first page hide", async ({ getLoc const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const pageloadEventData = await getFirstSentryEnvelopeRequest(page, url); - expect(eventData.type).toBe('transaction'); - expect(eventData.contexts?.trace?.op).toBe('pageload'); + expect(pageloadEventData.type).toBe('transaction'); + expect(pageloadEventData.contexts?.trace?.op).toBe('pageload'); const spanEnvelopePromise = getMultipleSentryEnvelopeRequests( page, @@ -266,6 +270,8 @@ sentryTest("doesn't send further LCP after the first page hide", async ({ getLoc const spanEnvelope = (await spanEnvelopePromise)[0]; const spanEnvelopeItem = spanEnvelope[1][0][1]; expect(spanEnvelopeItem.measurements?.lcp?.value).toBeGreaterThan(0); + expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('pagehide'); + expect(spanEnvelopeItem.trace_id).toBe(pageloadEventData.contexts?.trace?.trace_id); getMultipleSentryEnvelopeRequests(page, 1, { envelopeType: 'span' }, () => { throw new Error('Unexpected span - This should not happen!'); diff --git a/packages/browser-utils/src/metrics/cls.ts b/packages/browser-utils/src/metrics/cls.ts index b564a0187818..8839f038fb49 100644 --- a/packages/browser-utils/src/metrics/cls.ts +++ b/packages/browser-utils/src/metrics/cls.ts @@ -1,10 +1,7 @@ import type { SpanAttributes } from '@sentry/core'; import { browserPerformanceTimeOrigin, - getActiveSpan, - getClient, getCurrentScope, - getRootSpan, htmlTreeAsString, logger, SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME, @@ -12,12 +9,11 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - spanToJSON, } from '@sentry/core'; import { DEBUG_BUILD } from '../debug-build'; import { addClsInstrumentationHandler } from './instrument'; -import { msToSec, startStandaloneWebVitalSpan } from './utils'; -import { onHidden } from './web-vitals/lib/onHidden'; +import type { WebVitalReportEvent } from './utils'; +import { listenForWebVitalReportEvents, msToSec, startStandaloneWebVitalSpan, supportsWebVital } from './utils'; /** * Starts tracking the Cumulative Layout Shift on the current page and collects the value once @@ -31,24 +27,11 @@ import { onHidden } from './web-vitals/lib/onHidden'; export function trackClsAsStandaloneSpan(): void { let standaloneCLsValue = 0; let standaloneClsEntry: LayoutShift | undefined; - let pageloadSpanId: string | undefined; - if (!supportsLayoutShift()) { + if (!supportsWebVital('layout-shift')) { return; } - let sentSpan = false; - function _collectClsOnce() { - if (sentSpan) { - return; - } - sentSpan = true; - if (pageloadSpanId) { - sendStandaloneClsSpan(standaloneCLsValue, standaloneClsEntry, pageloadSpanId); - } - cleanupClsHandler(); - } - const cleanupClsHandler = addClsInstrumentationHandler(({ metric }) => { const entry = metric.entries[metric.entries.length - 1] as LayoutShift | undefined; if (!entry) { @@ -58,40 +41,18 @@ export function trackClsAsStandaloneSpan(): void { standaloneClsEntry = entry; }, true); - onHidden(() => { - _collectClsOnce(); + listenForWebVitalReportEvents((reportEvent, pageloadSpanId) => { + sendStandaloneClsSpan(standaloneCLsValue, standaloneClsEntry, pageloadSpanId, reportEvent); + cleanupClsHandler(); }); - - // Since the call chain of this function is synchronous and evaluates before the SDK client is created, - // we need to wait with subscribing to a client hook until the client is created. Therefore, we defer - // to the next tick after the SDK setup. - setTimeout(() => { - const client = getClient(); - - if (!client) { - return; - } - - const unsubscribeStartNavigation = client.on('beforeStartNavigationSpan', (_, options) => { - // we only want to collect LCP if we actually navigate. Redirects should be ignored. - if (!options?.isRedirect) { - _collectClsOnce(); - unsubscribeStartNavigation?.(); - } - }); - - const activeSpan = getActiveSpan(); - if (activeSpan) { - const rootSpan = getRootSpan(activeSpan); - const spanJSON = spanToJSON(rootSpan); - if (spanJSON.op === 'pageload') { - pageloadSpanId = rootSpan.spanContext().spanId; - } - } - }, 0); } -function sendStandaloneClsSpan(clsValue: number, entry: LayoutShift | undefined, pageloadSpanId: string) { +function sendStandaloneClsSpan( + clsValue: number, + entry: LayoutShift | undefined, + pageloadSpanId: string, + reportEvent: WebVitalReportEvent, +) { DEBUG_BUILD && logger.log(`Sending CLS span (${clsValue})`); const startTime = msToSec((browserPerformanceTimeOrigin() || 0) + (entry?.startTime || 0)); @@ -105,6 +66,8 @@ function sendStandaloneClsSpan(clsValue: number, entry: LayoutShift | undefined, [SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: entry?.duration || 0, // attach the pageload span id to the CLS span so that we can link them in the UI 'sentry.pageload.span_id': pageloadSpanId, + // describes what triggered the web vital to be reported + 'sentry.report_event': reportEvent, }; // Add CLS sources as span attributes to help with debugging layout shifts @@ -133,11 +96,3 @@ function sendStandaloneClsSpan(clsValue: number, entry: LayoutShift | undefined, span.end(startTime); } } - -function supportsLayoutShift(): boolean { - try { - return PerformanceObserver.supportedEntryTypes.includes('layout-shift'); - } catch { - return false; - } -} diff --git a/packages/browser-utils/src/metrics/lcp.ts b/packages/browser-utils/src/metrics/lcp.ts index e34391a4a1d7..dc98b2f8f2b1 100644 --- a/packages/browser-utils/src/metrics/lcp.ts +++ b/packages/browser-utils/src/metrics/lcp.ts @@ -1,10 +1,7 @@ import type { SpanAttributes } from '@sentry/core'; import { browserPerformanceTimeOrigin, - getActiveSpan, - getClient, getCurrentScope, - getRootSpan, htmlTreeAsString, logger, SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME, @@ -12,12 +9,11 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - spanToJSON, } from '@sentry/core'; import { DEBUG_BUILD } from '../debug-build'; import { addLcpInstrumentationHandler } from './instrument'; -import { msToSec, startStandaloneWebVitalSpan } from './utils'; -import { onHidden } from './web-vitals/lib/onHidden'; +import type { WebVitalReportEvent } from './utils'; +import { listenForWebVitalReportEvents, msToSec, startStandaloneWebVitalSpan, supportsWebVital } from './utils'; /** * Starts tracking the Largest Contentful Paint on the current page and collects the value once @@ -31,24 +27,11 @@ import { onHidden } from './web-vitals/lib/onHidden'; export function trackLcpAsStandaloneSpan(): void { let standaloneLcpValue = 0; let standaloneLcpEntry: LargestContentfulPaint | undefined; - let pageloadSpanId: string | undefined; - if (!supportsLargestContentfulPaint()) { + if (!supportsWebVital('largest-contentful-paint')) { return; } - let sentSpan = false; - function _collectLcpOnce() { - if (sentSpan) { - return; - } - sentSpan = true; - if (pageloadSpanId) { - _sendStandaloneLcpSpan(standaloneLcpValue, standaloneLcpEntry, pageloadSpanId); - } - cleanupLcpHandler(); - } - const cleanupLcpHandler = addLcpInstrumentationHandler(({ metric }) => { const entry = metric.entries[metric.entries.length - 1] as LargestContentfulPaint | undefined; if (!entry) { @@ -58,37 +41,10 @@ export function trackLcpAsStandaloneSpan(): void { standaloneLcpEntry = entry; }, true); - onHidden(() => { - _collectLcpOnce(); + listenForWebVitalReportEvents((reportEvent, pageloadSpanId) => { + _sendStandaloneLcpSpan(standaloneLcpValue, standaloneLcpEntry, pageloadSpanId, reportEvent); + cleanupLcpHandler(); }); - - // Since the call chain of this function is synchronous and evaluates before the SDK client is created, - // we need to wait with subscribing to a client hook until the client is created. Therefore, we defer - // to the next tick after the SDK setup. - setTimeout(() => { - const client = getClient(); - - if (!client) { - return; - } - - const unsubscribeStartNavigation = client.on('beforeStartNavigationSpan', (_, options) => { - // we only want to collect LCP if we actually navigate. Redirects should be ignored. - if (!options?.isRedirect) { - _collectLcpOnce(); - unsubscribeStartNavigation?.(); - } - }); - - const activeSpan = getActiveSpan(); - if (activeSpan) { - const rootSpan = getRootSpan(activeSpan); - const spanJSON = spanToJSON(rootSpan); - if (spanJSON.op === 'pageload') { - pageloadSpanId = rootSpan.spanContext().spanId; - } - } - }, 0); } /** @@ -98,6 +54,7 @@ export function _sendStandaloneLcpSpan( lcpValue: number, entry: LargestContentfulPaint | undefined, pageloadSpanId: string, + reportEvent: WebVitalReportEvent, ) { DEBUG_BUILD && logger.log(`Sending LCP span (${lcpValue})`); @@ -112,6 +69,8 @@ export function _sendStandaloneLcpSpan( [SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: 0, // LCP is a point-in-time metric // attach the pageload span id to the LCP span so that we can link them in the UI 'sentry.pageload.span_id': pageloadSpanId, + // describes what triggered the web vital to be reported + 'sentry.report_event': reportEvent, }; if (entry) { @@ -149,11 +108,3 @@ export function _sendStandaloneLcpSpan( span.end(startTime); } } - -function supportsLargestContentfulPaint(): boolean { - try { - return PerformanceObserver.supportedEntryTypes.includes('largest-contentful-paint'); - } catch { - return false; - } -} diff --git a/packages/browser-utils/src/metrics/utils.ts b/packages/browser-utils/src/metrics/utils.ts index aef40d4cf613..ce3da0d4f16d 100644 --- a/packages/browser-utils/src/metrics/utils.ts +++ b/packages/browser-utils/src/metrics/utils.ts @@ -1,6 +1,17 @@ import type { Integration, SentrySpan, Span, SpanAttributes, SpanTimeInput, StartSpanOptions } from '@sentry/core'; -import { getClient, getCurrentScope, spanToJSON, startInactiveSpan, withActiveSpan } from '@sentry/core'; +import { + getActiveSpan, + getClient, + getCurrentScope, + getRootSpan, + spanToJSON, + startInactiveSpan, + withActiveSpan, +} from '@sentry/core'; import { WINDOW } from '../types'; +import { onHidden } from './web-vitals/lib/onHidden'; + +export type WebVitalReportEvent = 'pagehide' | 'navigation'; /** * Checks if a given value is a valid measurement value. @@ -168,3 +179,71 @@ export function extractNetworkProtocol(nextHopProtocol: string): { name: string; } return { name, version }; } + +/** + * Generic support check for web vitals + */ +export function supportsWebVital(entryType: 'layout-shift' | 'largest-contentful-paint'): boolean { + try { + return PerformanceObserver.supportedEntryTypes.includes(entryType); + } catch { + return false; + } +} + +/** + * Listens for events on which we want to collect a previously accumulated web vital value. + * Currently, this includes: + * + * - pagehide (i.e. user minimizes browser window, hides tab, etc) + * - soft navigation (we only care about the vital of the initially loaded route) + * + * As a "side-effect", this function will also collect the span id of the pageload span. + * + * @param collectorCallback the callback to be called when the first of these events is triggered. Parameters: + * - event: the event that triggered the reporting of the web vital value. + * - pageloadSpanId: the span id of the pageload span. This is used to link the web vital span to the pageload span. + */ +export function listenForWebVitalReportEvents( + collectorCallback: (event: WebVitalReportEvent, pageloadSpanId: string) => void, +) { + let pageloadSpanId: string | undefined; + + let collected = false; + function _runCollectorCallbackOnce(event: WebVitalReportEvent) { + if (!collected && pageloadSpanId) { + collectorCallback(event, pageloadSpanId); + } + collected = true; + } + + onHidden(() => { + if (!collected) { + _runCollectorCallbackOnce('pagehide'); + } + }); + + setTimeout(() => { + const client = getClient(); + if (!client) { + return; + } + + const unsubscribeStartNavigation = client.on('beforeStartNavigationSpan', (_, options) => { + // we only want to collect LCP if we actually navigate. Redirects should be ignored. + if (!options?.isRedirect) { + _runCollectorCallbackOnce('navigation'); + unsubscribeStartNavigation?.(); + } + }); + + const activeSpan = getActiveSpan(); + if (activeSpan) { + const rootSpan = getRootSpan(activeSpan); + const spanJSON = spanToJSON(rootSpan); + if (spanJSON.op === 'pageload') { + pageloadSpanId = rootSpan.spanContext().spanId; + } + } + }, 0); +} From 9c7c6bf9c2db94430e2698abdd1799f84d2b6b48 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Fri, 11 Jul 2025 10:05:13 +0200 Subject: [PATCH 08/11] ref: Avoid `enum` usage (#16922) Apart of directly vendored code, we should avoid `enum`, as it has bundle size impact. Also somehow part of https://github.com/getsentry/sentry-javascript/issues/16846 --- .../featureFlags/openfeature/types.ts | 21 +++++++------ packages/core/src/utils/syncpromise.ts | 31 +++++++++---------- packages/react/src/types.ts | 6 +--- 3 files changed, 26 insertions(+), 32 deletions(-) diff --git a/packages/browser/src/integrations/featureFlags/openfeature/types.ts b/packages/browser/src/integrations/featureFlags/openfeature/types.ts index 835e684d86eb..4d16e81489fb 100644 --- a/packages/browser/src/integrations/featureFlags/openfeature/types.ts +++ b/packages/browser/src/integrations/featureFlags/openfeature/types.ts @@ -17,16 +17,17 @@ export const StandardResolutionReasons = { STALE: 'STALE', ERROR: 'ERROR', } as const; -export enum ErrorCode { - PROVIDER_NOT_READY = 'PROVIDER_NOT_READY', - PROVIDER_FATAL = 'PROVIDER_FATAL', - FLAG_NOT_FOUND = 'FLAG_NOT_FOUND', - PARSE_ERROR = 'PARSE_ERROR', - TYPE_MISMATCH = 'TYPE_MISMATCH', - TARGETING_KEY_MISSING = 'TARGETING_KEY_MISSING', - INVALID_CONTEXT = 'INVALID_CONTEXT', - GENERAL = 'GENERAL', -} + +type ErrorCode = + | 'PROVIDER_NOT_READY' + | 'PROVIDER_FATAL' + | 'FLAG_NOT_FOUND' + | 'PARSE_ERROR' + | 'TYPE_MISMATCH' + | 'TARGETING_KEY_MISSING' + | 'INVALID_CONTEXT' + | 'GENERAL'; + export interface Logger { error(...args: unknown[]): void; warn(...args: unknown[]): void; diff --git a/packages/core/src/utils/syncpromise.ts b/packages/core/src/utils/syncpromise.ts index 95aa45598727..0b885910fee3 100644 --- a/packages/core/src/utils/syncpromise.ts +++ b/packages/core/src/utils/syncpromise.ts @@ -2,14 +2,11 @@ import { isThenable } from './is'; /** SyncPromise internal states */ -const enum States { - /** Pending */ - PENDING = 0, - /** Resolved / OK */ - RESOLVED = 1, - /** Rejected / Error */ - REJECTED = 2, -} +const STATE_PENDING = 0; +const STATE_RESOLVED = 1; +const STATE_REJECTED = 2; + +type State = typeof STATE_PENDING | typeof STATE_RESOLVED | typeof STATE_REJECTED; // Overloads so we can call resolvedSyncPromise without arguments and generic argument export function resolvedSyncPromise(): PromiseLike; @@ -46,12 +43,12 @@ type Executor = (resolve: (value?: T | PromiseLike | null) => void, reject * but is not async internally */ export class SyncPromise implements PromiseLike { - private _state: States; + private _state: State; private _handlers: Array<[boolean, (value: T) => void, (reason: any) => any]>; private _value: any; public constructor(executor: Executor) { - this._state = States.PENDING; + this._state = STATE_PENDING; this._handlers = []; this._runExecutor(executor); @@ -135,7 +132,7 @@ export class SyncPromise implements PromiseLike { /** Excute the resolve/reject handlers. */ private _executeHandlers(): void { - if (this._state === States.PENDING) { + if (this._state === STATE_PENDING) { return; } @@ -147,11 +144,11 @@ export class SyncPromise implements PromiseLike { return; } - if (this._state === States.RESOLVED) { + if (this._state === STATE_RESOLVED) { handler[1](this._value as unknown as any); } - if (this._state === States.REJECTED) { + if (this._state === STATE_REJECTED) { handler[2](this._value); } @@ -161,8 +158,8 @@ export class SyncPromise implements PromiseLike { /** Run the executor for the SyncPromise. */ private _runExecutor(executor: Executor): void { - const setResult = (state: States, value?: T | PromiseLike | any): void => { - if (this._state !== States.PENDING) { + const setResult = (state: State, value?: T | PromiseLike | any): void => { + if (this._state !== STATE_PENDING) { return; } @@ -178,11 +175,11 @@ export class SyncPromise implements PromiseLike { }; const resolve = (value: unknown): void => { - setResult(States.RESOLVED, value); + setResult(STATE_RESOLVED, value); }; const reject = (reason: unknown): void => { - setResult(States.REJECTED, reason); + setResult(STATE_REJECTED, reason); }; try { diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index b29a2dbd1cad..19aacffc5ac3 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -197,11 +197,7 @@ export type Navigation = NavigationStates[keyof NavigationStates]; export type RouteData = any; export type Fetcher = any; -export declare enum HistoryAction { - Pop = 'POP', - Push = 'PUSH', - Replace = 'REPLACE', -} +type HistoryAction = 'POP' | 'PUSH' | 'REPLACE'; export interface RouterState { historyAction: Action | HistoryAction | any; From c7a1e9a90ff91a707b78d3d7ba6c888abf9a8306 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Fri, 11 Jul 2025 06:33:55 -0400 Subject: [PATCH 09/11] ref(vue): Use `debug` in vue sdk (#16910) resolves https://github.com/getsentry/sentry-javascript/issues/16909 --- packages/vue/src/tracing.ts | 4 ++-- packages/vue/test/integration/VueIntegration.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/vue/src/tracing.ts b/packages/vue/src/tracing.ts index 202face147d9..f4708ddbd865 100644 --- a/packages/vue/src/tracing.ts +++ b/packages/vue/src/tracing.ts @@ -1,6 +1,6 @@ import { getActiveSpan, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startInactiveSpan } from '@sentry/browser'; import type { Span } from '@sentry/core'; -import { logger, timestampInSeconds } from '@sentry/core'; +import { debug, timestampInSeconds } from '@sentry/core'; import { DEFAULT_HOOKS } from './constants'; import { DEBUG_BUILD } from './debug-build'; import type { Hook, Operation, TracingOptions, ViewModel, Vue } from './types'; @@ -73,7 +73,7 @@ export const createTracingMixins = (options: Partial = {}): Mixi // eg. mount => ['beforeMount', 'mounted'] const internalHooks = HOOKS[operation]; if (!internalHooks) { - DEBUG_BUILD && logger.warn(`Unknown hook: ${operation}`); + DEBUG_BUILD && debug.warn(`Unknown hook: ${operation}`); continue; } diff --git a/packages/vue/test/integration/VueIntegration.test.ts b/packages/vue/test/integration/VueIntegration.test.ts index 200ec42dfaf4..81eb22254917 100644 --- a/packages/vue/test/integration/VueIntegration.test.ts +++ b/packages/vue/test/integration/VueIntegration.test.ts @@ -3,7 +3,7 @@ */ import type { Client } from '@sentry/core'; -import { logger } from '@sentry/core'; +import { debug } from '@sentry/core'; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { createApp } from 'vue'; import * as Sentry from '../../src'; @@ -35,7 +35,7 @@ describe('Sentry.VueIntegration', () => { warnings = []; loggerWarnings = []; - vi.spyOn(logger, 'warn').mockImplementation((message: unknown) => { + vi.spyOn(debug, 'warn').mockImplementation((message: unknown) => { loggerWarnings.push(message); }); From 0dec883798bd00300faaab829f50217b12e383cd Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Fri, 11 Jul 2025 06:35:25 -0400 Subject: [PATCH 10/11] ref(svelte): use `debug` in svelte sdk (#16917) resolves https://github.com/getsentry/sentry-javascript/issues/16916 --- packages/svelte/src/debug_build.ts | 8 ++++++++ packages/svelte/src/performance.ts | 10 ++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 packages/svelte/src/debug_build.ts diff --git a/packages/svelte/src/debug_build.ts b/packages/svelte/src/debug_build.ts new file mode 100644 index 000000000000..60aa50940582 --- /dev/null +++ b/packages/svelte/src/debug_build.ts @@ -0,0 +1,8 @@ +declare const __DEBUG_BUILD__: boolean; + +/** + * This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code. + * + * ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking. + */ +export const DEBUG_BUILD = __DEBUG_BUILD__; diff --git a/packages/svelte/src/performance.ts b/packages/svelte/src/performance.ts index 297e36027a8a..2f4a377aa307 100644 --- a/packages/svelte/src/performance.ts +++ b/packages/svelte/src/performance.ts @@ -1,7 +1,8 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/browser'; import type { Span } from '@sentry/core'; -import { logger, startInactiveSpan } from '@sentry/core'; +import { debug, startInactiveSpan } from '@sentry/core'; import { afterUpdate, beforeUpdate, onMount } from 'svelte'; +import { DEBUG_BUILD } from './debug_build'; import type { TrackComponentOptions } from './types'; const defaultTrackComponentOptions: { @@ -37,9 +38,10 @@ export function trackComponent(options?: TrackComponentOptions): void { try { recordUpdateSpans(componentName); } catch { - logger.warn( - "Cannot track component updates. This is likely because you're using Svelte 5 in Runes mode. Set `trackUpdates: false` in `withSentryConfig` or `trackComponent` to disable this warning.", - ); + DEBUG_BUILD && + debug.warn( + "Cannot track component updates. This is likely because you're using Svelte 5 in Runes mode. Set `trackUpdates: false` in `withSentryConfig` or `trackComponent` to disable this warning.", + ); } } } From ccf61b2034a06c7aaad52a733b32829c0bde8d81 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Fri, 11 Jul 2025 12:53:44 +0200 Subject: [PATCH 11/11] meta(changelog): Update changelog for 9.38.0 --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 722a59cca2f4..11e7473626be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 9.38.0 + +### Important Changes + +- **chore: Add craft entry for @sentry/node-native ([#16907](https://github.com/getsentry/sentry-javascript/pull/16907))** + +This release publishes the `@sentry/node-native` SDK. + +### Other Changes + +- feat(core): Introduce `debug` to replace `logger` ([#16906](https://github.com/getsentry/sentry-javascript/pull/16906)) +- fix(browser): Guard `nextHopProtocol` when adding resource spans ([#16900](https://github.com/getsentry/sentry-javascript/pull/16900)) + ## 9.37.0 ### Important Changes