diff --git a/packages/browser/src/helpers.ts b/packages/browser/src/helpers.ts index 1b802d2a22de..1e4647d2d7bd 100644 --- a/packages/browser/src/helpers.ts +++ b/packages/browser/src/helpers.ts @@ -6,6 +6,7 @@ import { addNonEnumerableProperty, getGlobalObject, getOriginalFunction, + isDebugBuild, logger, markFunctionWrapped, } from '@sentry/utils'; @@ -191,12 +192,16 @@ export function injectReportDialog(options: ReportDialogOptions = {}): void { } if (!options.eventId) { - logger.error(`Missing eventId option in showReportDialog call`); + if (isDebugBuild()) { + logger.error(`Missing eventId option in showReportDialog call`); + } return; } if (!options.dsn) { - logger.error(`Missing dsn option in showReportDialog call`); + if (isDebugBuild()) { + logger.error(`Missing dsn option in showReportDialog call`); + } return; } diff --git a/packages/browser/src/integrations/dedupe.ts b/packages/browser/src/integrations/dedupe.ts index 641823bbde3c..791516922956 100644 --- a/packages/browser/src/integrations/dedupe.ts +++ b/packages/browser/src/integrations/dedupe.ts @@ -1,5 +1,5 @@ import { Event, EventProcessor, Exception, Hub, Integration, StackFrame } from '@sentry/types'; -import { logger } from '@sentry/utils'; +import { isDebugBuild, logger } from '@sentry/utils'; /** Deduplication filter */ export class Dedupe implements Integration { @@ -28,7 +28,9 @@ export class Dedupe implements Integration { // Juuust in case something goes wrong try { if (_shouldDropEvent(currentEvent, self._previousEvent)) { - logger.warn(`Event dropped due to being a duplicate of previously captured event.`); + if (isDebugBuild()) { + logger.warn(`Event dropped due to being a duplicate of previously captured event.`); + } return null; } } catch (_oO) { diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index d729648dc8fb..49f05284b141 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -1,5 +1,5 @@ import { getCurrentHub, initAndBind, Integrations as CoreIntegrations } from '@sentry/core'; -import { addInstrumentationHandler, getGlobalObject, logger, SyncPromise } from '@sentry/utils'; +import { addInstrumentationHandler, getGlobalObject, isDebugBuild, logger, SyncPromise } from '@sentry/utils'; import { BrowserOptions } from './backend'; import { BrowserClient } from './client'; @@ -161,7 +161,9 @@ export function flush(timeout?: number): PromiseLike { if (client) { return client.flush(timeout); } - logger.warn('Cannot flush events. No client defined.'); + if (isDebugBuild()) { + logger.warn('Cannot flush events. No client defined.'); + } return SyncPromise.resolve(false); } @@ -178,7 +180,9 @@ export function close(timeout?: number): PromiseLike { if (client) { return client.close(timeout); } - logger.warn('Cannot flush events and disable SDK. No client defined.'); + if (isDebugBuild()) { + logger.warn('Cannot flush events and disable SDK. No client defined.'); + } return SyncPromise.resolve(false); } @@ -202,7 +206,9 @@ function startSessionTracking(): void { const document = window.document; if (typeof document === 'undefined') { - logger.warn('Session tracking in non-browser environment with @sentry/browser is not supported.'); + if (isDebugBuild()) { + logger.warn('Session tracking in non-browser environment with @sentry/browser is not supported.'); + } return; } diff --git a/packages/browser/src/transports/base.ts b/packages/browser/src/transports/base.ts index 042311d53694..909543da314e 100644 --- a/packages/browser/src/transports/base.ts +++ b/packages/browser/src/transports/base.ts @@ -16,6 +16,7 @@ import { dateTimestampInSeconds, eventStatusFromHttpCode, getGlobalObject, + isDebugBuild, logger, makePromiseBuffer, parseRetryAfterHeader, @@ -91,7 +92,9 @@ export abstract class BaseTransport implements Transport { // A correct type for map-based implementation if we want to go that route // would be `Partial>>>` const key = `${requestTypeToCategory(category)}:${reason}`; - logger.log(`Adding outcome: ${key}`); + if (isDebugBuild()) { + logger.log(`Adding outcome: ${key}`); + } this._outcomes[key] = (this._outcomes[key] ?? 0) + 1; } @@ -108,11 +111,15 @@ export abstract class BaseTransport implements Transport { // Nothing to send if (!Object.keys(outcomes).length) { - logger.log('No outcomes to flush'); + if (isDebugBuild()) { + logger.log('No outcomes to flush'); + } return; } - logger.log(`Flushing outcomes:\n${JSON.stringify(outcomes, null, 2)}`); + if (isDebugBuild()) { + logger.log(`Flushing outcomes:\n${JSON.stringify(outcomes, null, 2)}`); + } const url = getEnvelopeEndpointWithUrlEncodedAuth(this._api.dsn, this._api.tunnel); // Envelope header is required to be at least an empty object @@ -163,7 +170,9 @@ export abstract class BaseTransport implements Transport { */ const limited = this._handleRateLimit(headers); if (limited) - logger.warn(`Too many ${requestType} requests, backing off until: ${this._disabledUntil(requestType)}`); + if (isDebugBuild()) { + logger.warn(`Too many ${requestType} requests, backing off until: ${this._disabledUntil(requestType)}`); + } if (status === 'success') { resolve({ status }); diff --git a/packages/browser/src/transports/utils.ts b/packages/browser/src/transports/utils.ts index 5d958b531034..5b48c9e93cbc 100644 --- a/packages/browser/src/transports/utils.ts +++ b/packages/browser/src/transports/utils.ts @@ -1,4 +1,4 @@ -import { forget, getGlobalObject, isNativeFetch, logger, supportsFetch } from '@sentry/utils'; +import { forget, getGlobalObject, isDebugBuild, isNativeFetch, logger, supportsFetch } from '@sentry/utils'; const global = getGlobalObject(); let cachedFetchImpl: FetchImpl; @@ -69,7 +69,9 @@ export function getNativeFetchImplementation(): FetchImpl { } document.head.removeChild(sandbox); } catch (e) { - logger.warn('Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ', e); + if (isDebugBuild()) { + logger.warn('Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ', e); + } } } diff --git a/packages/core/src/basebackend.ts b/packages/core/src/basebackend.ts index ffdcd22af6c0..89f6588d22bf 100644 --- a/packages/core/src/basebackend.ts +++ b/packages/core/src/basebackend.ts @@ -1,5 +1,5 @@ import { Event, EventHint, Options, Session, SeverityLevel, Transport } from '@sentry/types'; -import { logger, SentryError } from '@sentry/utils'; +import { isDebugBuild, logger, SentryError } from '@sentry/utils'; import { NoopTransport } from './transports/noop'; @@ -66,7 +66,7 @@ export abstract class BaseBackend implements Backend { /** Creates a new backend instance. */ public constructor(options: O) { this._options = options; - if (!this._options.dsn) { + if (isDebugBuild() && !this._options.dsn) { logger.warn('No DSN provided, backend will not do anything.'); } this._transport = this._setupTransport(); @@ -92,7 +92,9 @@ export abstract class BaseBackend implements Backend { */ public sendEvent(event: Event): void { void this._transport.sendEvent(event).then(null, reason => { - logger.error(`Error while sending event: ${reason}`); + if (isDebugBuild()) { + logger.error(`Error while sending event: ${reason}`); + } }); } @@ -101,12 +103,16 @@ export abstract class BaseBackend implements Backend { */ public sendSession(session: Session): void { if (!this._transport.sendSession) { - logger.warn("Dropping session because custom transport doesn't implement sendSession"); + if (isDebugBuild()) { + logger.warn("Dropping session because custom transport doesn't implement sendSession"); + } return; } void this._transport.sendSession(session).then(null, reason => { - logger.error(`Error while sending session: ${reason}`); + if (isDebugBuild()) { + logger.error(`Error while sending session: ${reason}`); + } }); } diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index e9c4f1ca48bf..1e97dc72fb46 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -14,6 +14,7 @@ import { checkOrSetAlreadyCaught, dateTimestampInSeconds, Dsn, + isDebugBuild, isPlainObject, isPrimitive, isThenable, @@ -104,7 +105,9 @@ export abstract class BaseClient implement public captureException(exception: any, hint?: EventHint, scope?: Scope): string | undefined { // ensure we haven't captured this very object before if (checkOrSetAlreadyCaught(exception)) { - logger.log(ALREADY_SEEN_ERROR); + if (isDebugBuild()) { + logger.log(ALREADY_SEEN_ERROR); + } return; } @@ -149,7 +152,9 @@ export abstract class BaseClient implement public captureEvent(event: Event, hint?: EventHint, scope?: Scope): string | undefined { // ensure we haven't captured this very object before if (hint && hint.originalException && checkOrSetAlreadyCaught(hint.originalException)) { - logger.log(ALREADY_SEEN_ERROR); + if (isDebugBuild()) { + logger.log(ALREADY_SEEN_ERROR); + } return; } @@ -169,12 +174,16 @@ export abstract class BaseClient implement */ public captureSession(session: Session): void { if (!this._isEnabled()) { - logger.warn('SDK not enabled, will not capture session.'); + if (isDebugBuild()) { + logger.warn('SDK not enabled, will not capture session.'); + } return; } if (!(typeof session.release === 'string')) { - logger.warn('Discarded session because of missing or non-string release'); + if (isDebugBuild()) { + logger.warn('Discarded session because of missing or non-string release'); + } } else { this._sendSession(session); // After sending, we set init false to indicate it's not the first occurrence @@ -240,7 +249,9 @@ export abstract class BaseClient implement try { return (this._integrations[integration.id] as T) || null; } catch (_oO) { - logger.warn(`Cannot retrieve integration ${integration.id} from the current Client`); + if (isDebugBuild()) { + logger.warn(`Cannot retrieve integration ${integration.id} from the current Client`); + } return null; } } diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts index d6bf819a3269..4dce113d747d 100644 --- a/packages/core/src/integration.ts +++ b/packages/core/src/integration.ts @@ -1,8 +1,12 @@ import { addGlobalEventProcessor, getCurrentHub } from '@sentry/hub'; import { Integration, Options } from '@sentry/types'; -import { addNonEnumerableProperty, logger } from '@sentry/utils'; +import { addNonEnumerableProperty, getGlobalSingleton, isDebugBuild, logger } from '@sentry/utils'; -export const installedIntegrations: string[] = []; +// we have to remember integrations globally on the __SENTRY__ object in case +// sentry is bundled twice. In that case integrations would patch over themselves. +// this is problematic because it's unclear if sentry versions will agree on this +// behavior. There are likely to be better ways to accomplish this. +export const onceInitializedIntegrations: Array = getGlobalSingleton('_integrations', () => []); /** Map of integrations assigned to a client */ export type IntegrationIndex = { @@ -54,12 +58,14 @@ export function getIntegrationsToSetup(options: Options): Integration[] { /** Setup given integration */ export function setupIntegration(integration: Integration): void { - if (installedIntegrations.indexOf(integration.name) !== -1) { + if (onceInitializedIntegrations.indexOf(integration.name) >= 0) { return; } integration.setupOnce(addGlobalEventProcessor, getCurrentHub); - installedIntegrations.push(integration.name); - logger.log(`Integration installed: ${integration.name}`); + onceInitializedIntegrations.push(integration.name); + if (isDebugBuild()) { + logger.log(`Integration installed: ${integration.name}`); + } } /** diff --git a/packages/core/src/integrations/inboundfilters.ts b/packages/core/src/integrations/inboundfilters.ts index 7bfda9e56a92..f779c3be6266 100644 --- a/packages/core/src/integrations/inboundfilters.ts +++ b/packages/core/src/integrations/inboundfilters.ts @@ -1,6 +1,6 @@ import { addGlobalEventProcessor, getCurrentHub } from '@sentry/hub'; import { Event, Integration, StackFrame } from '@sentry/types'; -import { getEventDescription, isMatchingPattern, logger } from '@sentry/utils'; +import { getEventDescription, isDebugBuild, isMatchingPattern, logger } from '@sentry/utils'; // "Script error." is hard coded into browsers for errors that it can't read. // this is the result of a script being pulled in from an external domain and CORS. @@ -64,29 +64,37 @@ export class InboundFilters implements Integration { /** JSDoc */ private _shouldDropEvent(event: Event, options: Partial): boolean { if (this._isSentryError(event, options)) { - logger.warn(`Event dropped due to being internal Sentry Error.\nEvent: ${getEventDescription(event)}`); + if (isDebugBuild()) { + logger.warn(`Event dropped due to being internal Sentry Error.\nEvent: ${getEventDescription(event)}`); + } return true; } if (this._isIgnoredError(event, options)) { - logger.warn( - `Event dropped due to being matched by \`ignoreErrors\` option.\nEvent: ${getEventDescription(event)}`, - ); + if (isDebugBuild()) { + logger.warn( + `Event dropped due to being matched by \`ignoreErrors\` option.\nEvent: ${getEventDescription(event)}`, + ); + } return true; } if (this._isDeniedUrl(event, options)) { - logger.warn( - `Event dropped due to being matched by \`denyUrls\` option.\nEvent: ${getEventDescription( - event, - )}.\nUrl: ${this._getEventFilterUrl(event)}`, - ); + if (isDebugBuild()) { + logger.warn( + `Event dropped due to being matched by \`denyUrls\` option.\nEvent: ${getEventDescription( + event, + )}.\nUrl: ${this._getEventFilterUrl(event)}`, + ); + } return true; } if (!this._isAllowedUrl(event, options)) { - logger.warn( - `Event dropped due to not being matched by \`allowUrls\` option.\nEvent: ${getEventDescription( - event, - )}.\nUrl: ${this._getEventFilterUrl(event)}`, - ); + if (isDebugBuild()) { + logger.warn( + `Event dropped due to not being matched by \`allowUrls\` option.\nEvent: ${getEventDescription( + event, + )}.\nUrl: ${this._getEventFilterUrl(event)}`, + ); + } return true; } return false; @@ -179,7 +187,9 @@ export class InboundFilters implements Integration { const { type = '', value = '' } = (event.exception.values && event.exception.values[0]) || {}; return [`${value}`, `${type}: ${value}`]; } catch (oO) { - logger.error(`Cannot extract message for event ${getEventDescription(event)}`); + if (isDebugBuild()) { + logger.error(`Cannot extract message for event ${getEventDescription(event)}`); + } return []; } } @@ -214,7 +224,9 @@ export class InboundFilters implements Integration { } return frames ? this._getLastValidUrl(frames) : null; } catch (oO) { - logger.error(`Cannot extract url for event ${getEventDescription(event)}`); + if (isDebugBuild()) { + logger.error(`Cannot extract url for event ${getEventDescription(event)}`); + } return null; } } diff --git a/packages/core/src/sdk.ts b/packages/core/src/sdk.ts index e6af9a5e2336..301bdc9b8284 100644 --- a/packages/core/src/sdk.ts +++ b/packages/core/src/sdk.ts @@ -1,6 +1,6 @@ import { getCurrentHub } from '@sentry/hub'; import { Client, Options } from '@sentry/types'; -import { logger } from '@sentry/utils'; +import { isDebugBuild, logger } from '@sentry/utils'; /** A class object that can instantiate Client objects. */ export type ClientClass = new (options: O) => F; @@ -14,6 +14,10 @@ export type ClientClass = new (options: O) */ export function initAndBind(clientClass: ClientClass, options: O): void { if (options.debug === true) { + if (!isDebugBuild()) { + // eslint-disable-next-line no-console + console.warn('warning: non debug Sentry SDK loaded, debug mode unavailable!'); + } logger.enable(); } const hub = getCurrentHub(); diff --git a/packages/core/test/lib/sdk.test.ts b/packages/core/test/lib/sdk.test.ts index 04f704d0698e..32c0d549d688 100644 --- a/packages/core/test/lib/sdk.test.ts +++ b/packages/core/test/lib/sdk.test.ts @@ -1,7 +1,7 @@ import { Scope } from '@sentry/hub'; import { Client, Integration } from '@sentry/types'; -import { installedIntegrations } from '../../src/integration'; +import { onceInitializedIntegrations } from '../../src/integration'; import { initAndBind } from '../../src/sdk'; import { TestClient } from '../mocks/client'; @@ -46,7 +46,7 @@ class MockIntegration implements Integration { describe('SDK', () => { beforeEach(() => { global.__SENTRY__ = {}; - installedIntegrations.splice(0); + onceInitializedIntegrations.splice(0); }); describe('initAndBind', () => { diff --git a/packages/hub/src/hub.ts b/packages/hub/src/hub.ts index c7fd4cac85e4..57ce451daad7 100644 --- a/packages/hub/src/hub.ts +++ b/packages/hub/src/hub.ts @@ -20,7 +20,15 @@ import { TransactionContext, User, } from '@sentry/types'; -import { consoleSandbox, dateTimestampInSeconds, getGlobalObject, isNodeEnv, logger, uuid4 } from '@sentry/utils'; +import { + consoleSandbox, + dateTimestampInSeconds, + getGlobalObject, + getGlobalSingleton, + isNodeEnv, + logger, + uuid4, +} from '@sentry/utils'; import { Scope } from './scope'; import { Session } from './session'; @@ -515,7 +523,10 @@ export class Hub implements HubInterface { * at the call-site. We always access the carrier through this function, so we can guarantee that `__SENTRY__` is there. **/ export function getMainCarrier(): Carrier { - const carrier = getGlobalObject(); + // FIXME: this makes no sense. getGlobalObject return value's __SENTRY__ + // type does not match up with the carrier's __SENTRY__ type. This all + // needs cleaning up. + const carrier = getGlobalObject() as Carrier; carrier.__SENTRY__ = carrier.__SENTRY__ || { extensions: {}, hub: undefined, @@ -616,10 +627,7 @@ function hasHubOnCarrier(carrier: Carrier): boolean { * @hidden */ export function getHubFromCarrier(carrier: Carrier): Hub { - if (carrier && carrier.__SENTRY__ && carrier.__SENTRY__.hub) return carrier.__SENTRY__.hub; - carrier.__SENTRY__ = carrier.__SENTRY__ || {}; - carrier.__SENTRY__.hub = new Hub(); - return carrier.__SENTRY__.hub; + return getGlobalSingleton('hub', () => new Hub(), carrier); } /** @@ -630,7 +638,7 @@ export function getHubFromCarrier(carrier: Carrier): Hub { */ export function setHubOnCarrier(carrier: Carrier, hub: Hub): boolean { if (!carrier) return false; - carrier.__SENTRY__ = carrier.__SENTRY__ || {}; - carrier.__SENTRY__.hub = hub; + const sentry = (carrier.__SENTRY__ = carrier.__SENTRY__ || {}); + sentry.hub = hub; return true; } diff --git a/packages/hub/src/scope.ts b/packages/hub/src/scope.ts index 940d0443c9f7..116583c3f7ce 100644 --- a/packages/hub/src/scope.ts +++ b/packages/hub/src/scope.ts @@ -18,7 +18,7 @@ import { Transaction, User, } from '@sentry/types'; -import { dateTimestampInSeconds, getGlobalObject, isPlainObject, isThenable, SyncPromise } from '@sentry/utils'; +import { dateTimestampInSeconds, getGlobalSingleton, isPlainObject, isThenable, SyncPromise } from '@sentry/utils'; import { Session } from './session'; @@ -516,12 +516,7 @@ export class Scope implements ScopeInterface { * Returns the global event processors. */ function getGlobalEventProcessors(): EventProcessor[] { - /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */ - const global = getGlobalObject(); - global.__SENTRY__ = global.__SENTRY__ || {}; - global.__SENTRY__.globalEventProcessors = global.__SENTRY__.globalEventProcessors || []; - return global.__SENTRY__.globalEventProcessors; - /* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */ + return getGlobalSingleton('globalEventProcessors', () => []); } /** diff --git a/packages/hub/src/sessionflusher.ts b/packages/hub/src/sessionflusher.ts index 82aa0af8c4c2..c6469eb33f5e 100644 --- a/packages/hub/src/sessionflusher.ts +++ b/packages/hub/src/sessionflusher.ts @@ -5,7 +5,7 @@ import { SessionFlusherLike, Transport, } from '@sentry/types'; -import { dropUndefinedKeys, logger } from '@sentry/utils'; +import { dropUndefinedKeys, isDebugBuild, logger } from '@sentry/utils'; import { getCurrentHub } from './hub'; @@ -35,11 +35,15 @@ export class SessionFlusher implements SessionFlusherLike { /** Sends session aggregates to Transport */ public sendSessionAggregates(sessionAggregates: SessionAggregates): void { if (!this._transport.sendSession) { - logger.warn("Dropping session because custom transport doesn't implement sendSession"); + if (isDebugBuild()) { + logger.warn("Dropping session because custom transport doesn't implement sendSession"); + } return; } void this._transport.sendSession(sessionAggregates).then(null, reason => { - logger.error(`Error while sending session: ${reason}`); + if (isDebugBuild()) { + logger.error(`Error while sending session: ${reason}`); + } }); } diff --git a/packages/integrations/src/captureconsole.ts b/packages/integrations/src/captureconsole.ts index bd3a7c055083..5ce2f362741b 100644 --- a/packages/integrations/src/captureconsole.ts +++ b/packages/integrations/src/captureconsole.ts @@ -1,5 +1,5 @@ import { EventProcessor, Hub, Integration } from '@sentry/types'; -import { fill, getGlobalObject, safeJoin, severityFromString } from '@sentry/utils'; +import { bypassConsoleInstrumentation, fill, getGlobalObject, safeJoin, severityFromString } from '@sentry/utils'; const global = getGlobalObject(); @@ -46,7 +46,7 @@ export class CaptureConsole implements Integration { fill(global.console, level, (originalConsoleLevel: () => any) => (...args: any[]): void => { const hub = getCurrentHub(); - if (hub.getIntegration(CaptureConsole)) { + if (hub.getIntegration(CaptureConsole) && !bypassConsoleInstrumentation()) { hub.withScope(scope => { scope.setLevel(severityFromString(level)); scope.setExtra('arguments', args); diff --git a/packages/integrations/src/dedupe.ts b/packages/integrations/src/dedupe.ts index f36e9770b908..44f437c23be8 100644 --- a/packages/integrations/src/dedupe.ts +++ b/packages/integrations/src/dedupe.ts @@ -1,5 +1,5 @@ import { Event, EventProcessor, Exception, Hub, Integration, StackFrame } from '@sentry/types'; -import { logger } from '@sentry/utils'; +import { isDebugBuild, logger } from '@sentry/utils'; /** Deduplication filter */ export class Dedupe implements Integration { @@ -28,7 +28,9 @@ export class Dedupe implements Integration { // Juuust in case something goes wrong try { if (_shouldDropEvent(currentEvent, self._previousEvent)) { - logger.warn(`Event dropped due to being a duplicate of previously captured event.`); + if (isDebugBuild()) { + logger.warn(`Event dropped due to being a duplicate of previously captured event.`); + } return null; } } catch (_oO) { diff --git a/packages/integrations/src/extraerrordata.ts b/packages/integrations/src/extraerrordata.ts index de01df6605c0..fee6e13c14d6 100644 --- a/packages/integrations/src/extraerrordata.ts +++ b/packages/integrations/src/extraerrordata.ts @@ -1,5 +1,5 @@ import { Event, EventHint, EventProcessor, ExtendedError, Hub, Integration } from '@sentry/types'; -import { isError, isPlainObject, logger, normalize } from '@sentry/utils'; +import { isDebugBuild, isError, isPlainObject, logger, normalize } from '@sentry/utils'; /** JSDoc */ interface ExtraErrorDataOptions { @@ -112,7 +112,9 @@ export class ExtraErrorData implements Integration { return extraErrorInfo; } catch (oO) { - logger.error('Unable to extract extra data from the Error object:', oO); + if (isDebugBuild()) { + logger.error('Unable to extract extra data from the Error object:', oO); + } } return null; diff --git a/packages/utils/src/global.ts b/packages/utils/src/global.ts index 4e63babf1dbd..1c72c4c9faa5 100644 --- a/packages/utils/src/global.ts +++ b/packages/utils/src/global.ts @@ -11,6 +11,7 @@ import { isNodeEnv } from './node'; /** Internal */ interface SentryGlobal { + console: Console; Sentry?: { Integrations?: Integration[]; }; @@ -22,7 +23,8 @@ interface SentryGlobal { __SENTRY__: { globalEventProcessors: any; hub: any; - logger: any; + logger?: any; + _integrations?: Array; }; } @@ -42,3 +44,16 @@ export function getGlobalObject(): T & SentryGlobal { ? self : fallbackGlobalObject) as T & SentryGlobal; } + +/** + * Retains a global singleton. + * + * @param name name of the global singleton on __SENTRY__ + * @param creator creation function + * @returns the singleton + */ +export function getGlobalSingleton(name: keyof SentryGlobal['__SENTRY__'], creator: () => T, obj?: unknown): T { + const global = (obj || getGlobalObject()) as SentryGlobal; + const sentry = (global.__SENTRY__ = global.__SENTRY__ || {}); + return sentry[name] || (sentry[name] = creator()); +} diff --git a/packages/utils/src/instrument.ts b/packages/utils/src/instrument.ts index 5b96eea11a2b..3d086fb82520 100644 --- a/packages/utils/src/instrument.ts +++ b/packages/utils/src/instrument.ts @@ -3,6 +3,7 @@ /* eslint-disable @typescript-eslint/ban-types */ import { WrappedFunction } from '@sentry/types'; +import { isDebugBuild } from '.'; import { getGlobalObject } from './global'; import { isInstanceOf, isString } from './is'; import { logger } from './logger'; @@ -68,7 +69,9 @@ function instrument(type: InstrumentHandlerType): void { instrumentUnhandledRejection(); break; default: - logger.warn('unknown instrumentation type:', type); + if (isDebugBuild()) { + logger.warn('unknown instrumentation type:', type); + } } } diff --git a/packages/utils/src/logger.ts b/packages/utils/src/logger.ts index 9c4d366ecb62..1218726e6165 100644 --- a/packages/utils/src/logger.ts +++ b/packages/utils/src/logger.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { WrappedFunction } from '@sentry/types'; - -import { getGlobalObject } from './global'; +import { isDebugBuild } from './env'; +import { getGlobalObject, getGlobalSingleton } from './global'; // TODO: Implement different loggers for different environments const global = getGlobalObject(); @@ -9,103 +8,80 @@ const global = getGlobalObject(); /** Prefix for logging strings */ const PREFIX = 'Sentry Logger '; -/** JSDoc */ -interface ExtensibleConsole extends Console { - [key: string]: any; +let _bypassConsoleInstrumentation = false; + +/** + * Returns true if the console should be bypassed. This is used by the + * captureconsole integration to disable itself. + * + * @returns true if the console instrumentation is bypassed. + */ +export function bypassConsoleInstrumentation(): boolean { + return _bypassConsoleInstrumentation; } /** - * Temporarily unwrap `console.log` and friends in order to perform the given callback using the original methods. - * Restores wrapping after the callback completes. + * Temporarily disable sentry console instrumentations. * * @param callback The function to run against the original `console` messages * @returns The results of the callback */ export function consoleSandbox(callback: () => any): any { - const global = getGlobalObject(); - const levels = ['debug', 'info', 'warn', 'error', 'log', 'assert']; - - if (!('console' in global)) { + const old = _bypassConsoleInstrumentation; + _bypassConsoleInstrumentation = true; + try { return callback(); + } finally { + _bypassConsoleInstrumentation = old; } - - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const originalConsole = (global as any).console as ExtensibleConsole; - const wrappedLevels: { [key: string]: any } = {}; - - // Restore all wrapped console methods - levels.forEach(level => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (level in (global as any).console && (originalConsole[level] as WrappedFunction).__sentry_original__) { - wrappedLevels[level] = originalConsole[level] as WrappedFunction; - originalConsole[level] = (originalConsole[level] as WrappedFunction).__sentry_original__; - } - }); - - // Perform callback manipulations - const result = callback(); - - // Revert restoration to wrapped state - Object.keys(wrappedLevels).forEach(level => { - originalConsole[level] = wrappedLevels[level]; - }); - - return result; } -/** JSDoc */ -class Logger { - /** JSDoc */ - private _enabled: boolean; - - /** JSDoc */ - public constructor() { - this._enabled = false; - } - - /** JSDoc */ - public disable(): void { - this._enabled = false; - } - - /** JSDoc */ - public enable(): void { - this._enabled = true; - } - - /** JSDoc */ - public log(...args: any[]): void { - if (!this._enabled) { - return; - } - consoleSandbox(() => { - global.console.log(`${PREFIX}[Log]: ${args.join(' ')}`); +function makeLogger(): Logger { + let enabled = false; + const logger: Logger = { + enable: () => { + enabled = !isDebugBuild(); + }, + disable: () => { + enabled = false; + }, + } as any; + + const methods = ['log', 'warn', 'error']; + + if (isDebugBuild()) { + methods.forEach(name => { + // @ts-ignore meh + logger[name] = (...args: any[]) => { + if (enabled) { + consoleSandbox(() => { + // @ts-ignore meh + global.console[name](`${PREFIX}[${name}]:`, ...args); + }); + } + }; }); - } - - /** JSDoc */ - public warn(...args: any[]): void { - if (!this._enabled) { - return; - } - consoleSandbox(() => { - global.console.warn(`${PREFIX}[Warn]: ${args.join(' ')}`); + } else { + methods.forEach(name => { + // @ts-ignore meh + // eslint-disable-next-line @typescript-eslint/no-empty-function + logger[name] = function() {}; }); } - /** JSDoc */ - public error(...args: any[]): void { - if (!this._enabled) { - return; - } - consoleSandbox(() => { - global.console.error(`${PREFIX}[Error]: ${args.join(' ')}`); - }); - } + return logger; +} + +/** JSDoc */ +interface Logger { + disable(): void; + enable(): void; + log(...args: any[]): void; + warn(...args: any[]): void; + error(...args: any[]): void; } // Ensure we only have a single logger instance, even if multiple versions of @sentry/utils are being used -global.__SENTRY__ = global.__SENTRY__ || {}; -const logger = (global.__SENTRY__.logger as Logger) || (global.__SENTRY__.logger = new Logger()); +const logger = getGlobalSingleton('logger', makeLogger); export { logger }; diff --git a/packages/utils/src/supports.ts b/packages/utils/src/supports.ts index 1a548b45f0d0..d422df7622a2 100644 --- a/packages/utils/src/supports.ts +++ b/packages/utils/src/supports.ts @@ -1,3 +1,4 @@ +import { isDebugBuild } from '.'; import { getGlobalObject } from './global'; import { logger } from './logger'; @@ -112,7 +113,9 @@ export function supportsNativeFetch(): boolean { } doc.head.removeChild(sandbox); } catch (err) { - logger.warn('Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ', err); + if (isDebugBuild()) { + logger.warn('Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ', err); + } } }