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 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 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/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/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; 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); +} 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/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/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 669917dfbadf..590d02afb34d 100644 --- a/packages/core/src/utils/logger.ts +++ b/packages/core/src/utils/logger.ts @@ -3,8 +3,28 @@ 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 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', @@ -16,20 +36,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 +62,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 +78,112 @@ 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: _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/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/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. 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/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/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 new file mode 100644 index 000000000000..4bac1b2b64e4 --- /dev/null +++ b/packages/core/test/lib/utils/logger.test.ts @@ -0,0 +1,111 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { debug, 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); + }); +}); + +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/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.'); 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', () => { 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; 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.", + ); } } } 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); });