From 21d2a4f3368ce18a930bca1794ee680d301c9711 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 6 Aug 2024 17:32:22 -0400 Subject: [PATCH 1/8] feat(replay): Add a replay-specific logger Removes the old `logInfo` function and replaces it with a new replay-specific logger. Configuration is done when the replay integration is first initialized to avoid needing to pass around configuration options. This also means that we cannot select individual log statements to be added as breadcrumbs with `traceInternals` options. This also adds a `logger.exception` that wraps `captureException`. Note that only the following logging levels are supported: * info * log * warn * error With two additions: * exception * infoTick (needs a better name) - same as `info` but adds the breadcrumb in the next tick due to some pre-existing race conditions There are two configuration options: * enable(/disable)CaptureInternalExceptions * enable(/disable)TraceInternals --- .../src/coreHandlers/handleGlobalEvent.ts | 4 +- .../coreHandlers/handleNetworkBreadcrumbs.ts | 3 +- .../src/coreHandlers/util/fetchUtils.ts | 14 ++- .../src/coreHandlers/util/networkUtils.ts | 10 +- .../src/coreHandlers/util/xhrUtils.ts | 14 ++- .../EventBufferCompressionWorker.ts | 5 +- .../src/eventBuffer/EventBufferProxy.ts | 9 +- .../src/eventBuffer/WorkerHandler.ts | 8 +- .../replay-internal/src/eventBuffer/index.ts | 10 +- packages/replay-internal/src/replay.ts | 78 ++++++------- .../src/session/fetchSession.ts | 7 +- .../src/session/loadOrCreateSession.ts | 11 +- packages/replay-internal/src/util/addEvent.ts | 13 +-- .../src/util/handleRecordingEmit.ts | 11 +- packages/replay-internal/src/util/log.ts | 54 --------- packages/replay-internal/src/util/logger.ts | 105 ++++++++++++++++++ .../src/util/sendReplayRequest.ts | 5 +- .../test/integration/flush.test.ts | 13 ++- 18 files changed, 211 insertions(+), 163 deletions(-) delete mode 100644 packages/replay-internal/src/util/log.ts create mode 100644 packages/replay-internal/src/util/logger.ts diff --git a/packages/replay-internal/src/coreHandlers/handleGlobalEvent.ts b/packages/replay-internal/src/coreHandlers/handleGlobalEvent.ts index 88651d449fe6..a13f4d24827e 100644 --- a/packages/replay-internal/src/coreHandlers/handleGlobalEvent.ts +++ b/packages/replay-internal/src/coreHandlers/handleGlobalEvent.ts @@ -1,10 +1,10 @@ import type { Event, EventHint } from '@sentry/types'; -import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; import type { ReplayContainer } from '../types'; import { isErrorEvent, isFeedbackEvent, isReplayEvent, isTransactionEvent } from '../util/eventUtils'; import { isRrwebError } from '../util/isRrwebError'; +import { logger } from '../util/logger'; import { addFeedbackBreadcrumb } from './util/addFeedbackBreadcrumb'; import { shouldSampleForBufferEvent } from './util/shouldSampleForBufferEvent'; @@ -50,7 +50,7 @@ export function handleGlobalEventListener(replay: ReplayContainer): (event: Even // Unless `captureExceptions` is enabled, we want to ignore errors coming from rrweb // As there can be a bunch of stuff going wrong in internals there, that we don't want to bubble up to users if (isRrwebError(event, hint) && !replay.getOptions()._experiments.captureExceptions) { - DEBUG_BUILD && logger.log('[Replay] Ignoring error from rrweb internals', event); + DEBUG_BUILD && logger.log('Ignoring error from rrweb internals', event); return null; } diff --git a/packages/replay-internal/src/coreHandlers/handleNetworkBreadcrumbs.ts b/packages/replay-internal/src/coreHandlers/handleNetworkBreadcrumbs.ts index a31fc046b17a..fb6d0c017943 100644 --- a/packages/replay-internal/src/coreHandlers/handleNetworkBreadcrumbs.ts +++ b/packages/replay-internal/src/coreHandlers/handleNetworkBreadcrumbs.ts @@ -1,9 +1,9 @@ import { getClient } from '@sentry/core'; import type { Breadcrumb, BreadcrumbHint, FetchBreadcrumbData, XhrBreadcrumbData } from '@sentry/types'; -import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; import type { FetchHint, ReplayContainer, ReplayNetworkOptions, XhrHint } from '../types'; +import { logger } from '../util/logger'; import { captureFetchBreadcrumbToReplay, enrichFetchBreadcrumb } from './util/fetchUtils'; import { captureXhrBreadcrumbToReplay, enrichXhrBreadcrumb } from './util/xhrUtils'; @@ -80,6 +80,7 @@ export function beforeAddNetworkBreadcrumb( } } catch (e) { DEBUG_BUILD && logger.warn('Error when enriching network breadcrumb'); + DEBUG_BUILD && logger.exception(e); } } diff --git a/packages/replay-internal/src/coreHandlers/util/fetchUtils.ts b/packages/replay-internal/src/coreHandlers/util/fetchUtils.ts index b5c2c3c36305..c03ab9e62eb0 100644 --- a/packages/replay-internal/src/coreHandlers/util/fetchUtils.ts +++ b/packages/replay-internal/src/coreHandlers/util/fetchUtils.ts @@ -1,6 +1,5 @@ import { setTimeout } from '@sentry-internal/browser-utils'; import type { Breadcrumb, FetchBreadcrumbData } from '@sentry/types'; -import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../../debug-build'; import type { @@ -11,6 +10,7 @@ import type { ReplayNetworkRequestData, ReplayNetworkRequestOrResponse, } from '../../types'; +import { logger } from '../../util/logger'; import { addNetworkBreadcrumb } from './addNetworkBreadcrumb'; import { buildNetworkRequestOrResponse, @@ -42,7 +42,8 @@ export async function captureFetchBreadcrumbToReplay( const result = makeNetworkReplayBreadcrumb('resource.fetch', data); addNetworkBreadcrumb(options.replay, result); } catch (error) { - DEBUG_BUILD && logger.error('[Replay] Failed to capture fetch breadcrumb', error); + DEBUG_BUILD && logger.error('Failed to capture fetch breadcrumb'); + DEBUG_BUILD && logger.exception(error); } } @@ -192,7 +193,8 @@ function getResponseData( return buildNetworkRequestOrResponse(headers, size, undefined); } catch (error) { - DEBUG_BUILD && logger.warn('[Replay] Failed to serialize response body', error); + DEBUG_BUILD && logger.warn('Failed to serialize response body'); + DEBUG_BUILD && logger.exception(error); // fallback return buildNetworkRequestOrResponse(headers, responseBodySize, undefined); } @@ -209,7 +211,8 @@ async function _parseFetchResponseBody(response: Response): Promise<[string | un const text = await _tryGetResponseText(res); return [text]; } catch (error) { - DEBUG_BUILD && logger.warn('[Replay] Failed to get text body from response', error); + DEBUG_BUILD && logger.warn('Failed to get text body from response'); + DEBUG_BUILD && logger.exception(error); return [undefined, 'BODY_PARSE_ERROR']; } } @@ -279,7 +282,8 @@ function _tryCloneResponse(response: Response): Response | void { return response.clone(); } catch (error) { // this can throw if the response was already consumed before - DEBUG_BUILD && logger.warn('[Replay] Failed to clone response body', error); + DEBUG_BUILD && logger.warn('Failed to clone response body'); + DEBUG_BUILD && logger.exception(error); } } diff --git a/packages/replay-internal/src/coreHandlers/util/networkUtils.ts b/packages/replay-internal/src/coreHandlers/util/networkUtils.ts index 06e96b7ab7df..b438f5924333 100644 --- a/packages/replay-internal/src/coreHandlers/util/networkUtils.ts +++ b/packages/replay-internal/src/coreHandlers/util/networkUtils.ts @@ -1,4 +1,4 @@ -import { dropUndefinedKeys, logger, stringMatchesSomePattern } from '@sentry/utils'; +import { dropUndefinedKeys, stringMatchesSomePattern } from '@sentry/utils'; import { NETWORK_BODY_MAX_SIZE, WINDOW } from '../../constants'; import { DEBUG_BUILD } from '../../debug-build'; @@ -10,6 +10,7 @@ import type { ReplayNetworkRequestOrResponse, ReplayPerformanceEntry, } from '../../types'; +import { logger } from '../../util/logger'; /** Get the size of a body. */ export function getBodySize(body: RequestInit['body']): number | undefined { @@ -77,12 +78,13 @@ export function getBodyString(body: unknown): [string | undefined, NetworkMetaWa if (!body) { return [undefined]; } - } catch { - DEBUG_BUILD && logger.warn('[Replay] Failed to serialize body', body); + } catch (error) { + DEBUG_BUILD && logger.warn('Failed to serialize body', body); + DEBUG_BUILD && logger.exception(error); return [undefined, 'BODY_PARSE_ERROR']; } - DEBUG_BUILD && logger.info('[Replay] Skipping network body because of body type', body); + DEBUG_BUILD && logger.info('Skipping network body because of body type', body); return [undefined, 'UNPARSEABLE_BODY_TYPE']; } diff --git a/packages/replay-internal/src/coreHandlers/util/xhrUtils.ts b/packages/replay-internal/src/coreHandlers/util/xhrUtils.ts index b86e2d2991a9..dfeb53b720b3 100644 --- a/packages/replay-internal/src/coreHandlers/util/xhrUtils.ts +++ b/packages/replay-internal/src/coreHandlers/util/xhrUtils.ts @@ -1,6 +1,5 @@ import { SENTRY_XHR_DATA_KEY } from '@sentry-internal/browser-utils'; import type { Breadcrumb, XhrBreadcrumbData } from '@sentry/types'; -import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../../debug-build'; import type { @@ -10,6 +9,7 @@ import type { ReplayNetworkRequestData, XhrHint, } from '../../types'; +import { logger } from '../../util/logger'; import { addNetworkBreadcrumb } from './addNetworkBreadcrumb'; import { buildNetworkRequestOrResponse, @@ -39,7 +39,8 @@ export async function captureXhrBreadcrumbToReplay( const result = makeNetworkReplayBreadcrumb('resource.xhr', data); addNetworkBreadcrumb(options.replay, result); } catch (error) { - DEBUG_BUILD && logger.error('[Replay] Failed to capture xhr breadcrumb', error); + DEBUG_BUILD && logger.warn('Failed to capture xhr breadcrumb'); + DEBUG_BUILD && logger.exception(error); } } @@ -161,7 +162,7 @@ function _getXhrResponseBody(xhr: XMLHttpRequest): [string | undefined, NetworkM errors.push(e); } - DEBUG_BUILD && logger.warn('[Replay] Failed to get xhr response body', ...errors); + DEBUG_BUILD && logger.warn('Failed to get xhr response body', ...errors); return [undefined]; } @@ -197,12 +198,13 @@ export function _parseXhrResponse( if (!body) { return [undefined]; } - } catch { - DEBUG_BUILD && logger.warn('[Replay] Failed to serialize body', body); + } catch (error) { + DEBUG_BUILD && logger.warn('Failed to serialize body', body); + DEBUG_BUILD && logger.exception(error); return [undefined, 'BODY_PARSE_ERROR']; } - DEBUG_BUILD && logger.info('[Replay] Skipping network body because of body type', body); + DEBUG_BUILD && logger.info('Skipping network body because of body type', body); return [undefined, 'UNPARSEABLE_BODY_TYPE']; } diff --git a/packages/replay-internal/src/eventBuffer/EventBufferCompressionWorker.ts b/packages/replay-internal/src/eventBuffer/EventBufferCompressionWorker.ts index 21206ea652ac..59e5b62b229c 100644 --- a/packages/replay-internal/src/eventBuffer/EventBufferCompressionWorker.ts +++ b/packages/replay-internal/src/eventBuffer/EventBufferCompressionWorker.ts @@ -1,9 +1,9 @@ import type { ReplayRecordingData } from '@sentry/types'; -import { logger } from '@sentry/utils'; import { REPLAY_MAX_EVENT_BUFFER_SIZE } from '../constants'; import { DEBUG_BUILD } from '../debug-build'; import type { AddEventResult, EventBuffer, EventBufferType, RecordingEvent } from '../types'; +import { logger } from '../util/logger'; import { timestampToMs } from '../util/timestamp'; import { WorkerHandler } from './WorkerHandler'; import { EventBufferSizeExceededError } from './error'; @@ -88,7 +88,8 @@ export class EventBufferCompressionWorker implements EventBuffer { // We do not wait on this, as we assume the order of messages is consistent for the worker this._worker.postMessage('clear').then(null, e => { - DEBUG_BUILD && logger.warn('[Replay] Sending "clear" message to worker failed', e); + DEBUG_BUILD && logger.warn('Sending "clear" message to worker failed', e); + DEBUG_BUILD && logger.exception(e); }); } diff --git a/packages/replay-internal/src/eventBuffer/EventBufferProxy.ts b/packages/replay-internal/src/eventBuffer/EventBufferProxy.ts index af6645a89e69..17f78ccaf8b3 100644 --- a/packages/replay-internal/src/eventBuffer/EventBufferProxy.ts +++ b/packages/replay-internal/src/eventBuffer/EventBufferProxy.ts @@ -1,9 +1,8 @@ import type { ReplayRecordingData } from '@sentry/types'; -import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; import type { AddEventResult, EventBuffer, EventBufferType, RecordingEvent } from '../types'; -import { logInfo } from '../util/log'; +import { logger } from '../util/logger'; import { EventBufferArray } from './EventBufferArray'; import { EventBufferCompressionWorker } from './EventBufferCompressionWorker'; @@ -90,7 +89,8 @@ export class EventBufferProxy implements EventBuffer { } catch (error) { // If the worker fails to load, we fall back to the simple buffer. // Nothing more to do from our side here - logInfo('[Replay] Failed to load the compression worker, falling back to simple buffer'); + DEBUG_BUILD && logger.info('Failed to load the compression worker, falling back to simple buffer'); + DEBUG_BUILD && logger.exception(error); return; } @@ -117,7 +117,8 @@ export class EventBufferProxy implements EventBuffer { try { await Promise.all(addEventPromises); } catch (error) { - DEBUG_BUILD && logger.warn('[Replay] Failed to add events when switching buffers.', error); + DEBUG_BUILD && logger.error('Failed to add events when switching buffers.'); + DEBUG_BUILD && logger.exception(error); } } } diff --git a/packages/replay-internal/src/eventBuffer/WorkerHandler.ts b/packages/replay-internal/src/eventBuffer/WorkerHandler.ts index 1014521e652f..2ccc3ee94b3c 100644 --- a/packages/replay-internal/src/eventBuffer/WorkerHandler.ts +++ b/packages/replay-internal/src/eventBuffer/WorkerHandler.ts @@ -1,8 +1,6 @@ -import { logger } from '@sentry/utils'; - import { DEBUG_BUILD } from '../debug-build'; import type { WorkerRequest, WorkerResponse } from '../types'; -import { logInfo } from '../util/log'; +import { logger } from '../util/logger'; /** * Event buffer that uses a web worker to compress events. @@ -57,7 +55,7 @@ export class WorkerHandler { * Destroy the worker. */ public destroy(): void { - logInfo('[Replay] Destroying compression worker'); + DEBUG_BUILD && logger.info('Destroying compression worker'); this._worker.terminate(); } @@ -85,7 +83,7 @@ export class WorkerHandler { if (!response.success) { // TODO: Do some error handling, not sure what - DEBUG_BUILD && logger.error('[Replay]', response.response); + DEBUG_BUILD && logger.error('Error in compression worker: ', response.response); reject(new Error('Error in compression worker')); return; diff --git a/packages/replay-internal/src/eventBuffer/index.ts b/packages/replay-internal/src/eventBuffer/index.ts index 741cb5dedc91..56b64b80dbb4 100644 --- a/packages/replay-internal/src/eventBuffer/index.ts +++ b/packages/replay-internal/src/eventBuffer/index.ts @@ -1,7 +1,8 @@ import { getWorkerURL } from '@sentry-internal/replay-worker'; +import { DEBUG_BUILD } from '../debug-build'; import type { EventBuffer } from '../types'; -import { logInfo } from '../util/log'; +import { logger } from '../util/logger'; import { EventBufferArray } from './EventBufferArray'; import { EventBufferProxy } from './EventBufferProxy'; @@ -32,7 +33,7 @@ export function createEventBuffer({ } } - logInfo('[Replay] Using simple buffer'); + DEBUG_BUILD && logger.info('Using simple buffer'); return new EventBufferArray(); } @@ -44,11 +45,12 @@ function _loadWorker(customWorkerUrl?: string): EventBufferProxy | void { return; } - logInfo(`[Replay] Using compression worker${customWorkerUrl ? ` from ${customWorkerUrl}` : ''}`); + DEBUG_BUILD && logger.info(`Using compression worker${customWorkerUrl ? ` from ${customWorkerUrl}` : ''}`); const worker = new Worker(workerUrl); return new EventBufferProxy(worker); } catch (error) { - logInfo('[Replay] Failed to create compression worker'); + DEBUG_BUILD && logger.warn('Failed to create compression worker'); + DEBUG_BUILD && logger.exception(error); // Fall back to use simple event buffer array } } diff --git a/packages/replay-internal/src/replay.ts b/packages/replay-internal/src/replay.ts index f42d6ef6964a..a70a384b7c68 100644 --- a/packages/replay-internal/src/replay.ts +++ b/packages/replay-internal/src/replay.ts @@ -1,15 +1,8 @@ /* eslint-disable max-lines */ // TODO: We might want to split this file up import { EventType, record } from '@sentry-internal/rrweb'; -import { - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - captureException, - getActiveSpan, - getClient, - getRootSpan, - spanToJSON, -} from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getActiveSpan, getClient, getRootSpan, spanToJSON } from '@sentry/core'; import type { ReplayRecordingMode, Span } from '@sentry/types'; -import { logger } from '@sentry/utils'; +import { logger } from './util/logger'; import { BUFFER_CHECKOUT_TIME, @@ -60,7 +53,6 @@ import { debounce } from './util/debounce'; import { getHandleRecordingEmit } from './util/handleRecordingEmit'; import { isExpired } from './util/isExpired'; import { isSessionExpired } from './util/isSessionExpired'; -import { logInfo, logInfoNextTick } from './util/log'; import { sendReplay } from './util/sendReplay'; import type { SKIPPED } from './util/throttle'; import { THROTTLED, throttle } from './util/throttle'; @@ -212,6 +204,15 @@ export class ReplayContainer implements ReplayContainerInterface { if (slowClickConfig) { this.clickDetector = new ClickDetector(this, slowClickConfig); } + + // Configure replay logger w/ experimental options + if (this._options._experiments.traceInternals) { + logger.enableTraceInternals(); + } + + if (this._options._experiments.captureExceptions) { + logger.enableCaptureInternalExceptions(); + } } /** Get the event context. */ @@ -243,11 +244,7 @@ export class ReplayContainer implements ReplayContainerInterface { /** A wrapper to conditionally capture exceptions. */ public handleException(error: unknown): void { - DEBUG_BUILD && logger.error('[Replay]', error); - - if (DEBUG_BUILD && this._options._experiments && this._options._experiments.captureExceptions) { - captureException(error); - } + DEBUG_BUILD && logger.exception(error); } /** @@ -273,7 +270,7 @@ export class ReplayContainer implements ReplayContainerInterface { if (!this.session) { // This should not happen, something wrong has occurred - this.handleException(new Error('Unable to initialize and create session')); + DEBUG_BUILD && logger.exception(new Error('Unable to initialize and create session')); return; } @@ -287,10 +284,7 @@ export class ReplayContainer implements ReplayContainerInterface { // In this case, we still want to continue in `session` recording mode this.recordingMode = this.session.sampled === 'buffer' && this.session.segmentId === 0 ? 'buffer' : 'session'; - logInfoNextTick( - `[Replay] Starting replay in ${this.recordingMode} mode`, - this._options._experiments.traceInternals, - ); + DEBUG_BUILD && logger.infoTick(`Starting replay in ${this.recordingMode} mode`); this._initializeRecording(); } @@ -304,16 +298,16 @@ export class ReplayContainer implements ReplayContainerInterface { */ public start(): void { if (this._isEnabled && this.recordingMode === 'session') { - DEBUG_BUILD && logger.info('[Replay] Recording is already in progress'); + DEBUG_BUILD && logger.info('Recording is already in progress'); return; } if (this._isEnabled && this.recordingMode === 'buffer') { - DEBUG_BUILD && logger.info('[Replay] Buffering is in progress, call `flush()` to save the replay'); + DEBUG_BUILD && logger.info('Buffering is in progress, call `flush()` to save the replay'); return; } - logInfoNextTick('[Replay] Starting replay in session mode', this._options._experiments.traceInternals); + DEBUG_BUILD && logger.infoTick('Starting replay in session mode'); // Required as user activity is initially set in // constructor, so if `start()` is called after @@ -325,7 +319,6 @@ export class ReplayContainer implements ReplayContainerInterface { { maxReplayDuration: this._options.maxReplayDuration, sessionIdleExpire: this.timeouts.sessionIdleExpire, - traceInternals: this._options._experiments.traceInternals, }, { stickySession: this._options.stickySession, @@ -346,17 +339,16 @@ export class ReplayContainer implements ReplayContainerInterface { */ public startBuffering(): void { if (this._isEnabled) { - DEBUG_BUILD && logger.info('[Replay] Buffering is in progress, call `flush()` to save the replay'); + DEBUG_BUILD && logger.info('Buffering is in progress, call `flush()` to save the replay'); return; } - logInfoNextTick('[Replay] Starting replay in buffer mode', this._options._experiments.traceInternals); + DEBUG_BUILD && logger.infoTick('Starting replay in buffer mode'); const session = loadOrCreateSession( { sessionIdleExpire: this.timeouts.sessionIdleExpire, maxReplayDuration: this._options.maxReplayDuration, - traceInternals: this._options._experiments.traceInternals, }, { stickySession: this._options.stickySession, @@ -436,10 +428,7 @@ export class ReplayContainer implements ReplayContainerInterface { this._isEnabled = false; try { - logInfo( - `[Replay] Stopping Replay${reason ? ` triggered by ${reason}` : ''}`, - this._options._experiments.traceInternals, - ); + DEBUG_BUILD && logger.info(`Stopping Replay${reason ? ` triggered by ${reason}` : ''}`); this._removeListeners(); this.stopRecording(); @@ -476,7 +465,7 @@ export class ReplayContainer implements ReplayContainerInterface { this._isPaused = true; this.stopRecording(); - logInfo('[Replay] Pausing replay', this._options._experiments.traceInternals); + DEBUG_BUILD && logger.info('Pausing replay'); } /** @@ -493,7 +482,7 @@ export class ReplayContainer implements ReplayContainerInterface { this._isPaused = false; this.startRecording(); - logInfo('[Replay] Resuming replay', this._options._experiments.traceInternals); + DEBUG_BUILD && logger.info('Resuming replay'); } /** @@ -510,7 +499,7 @@ export class ReplayContainer implements ReplayContainerInterface { const activityTime = Date.now(); - logInfo('[Replay] Converting buffer to session', this._options._experiments.traceInternals); + DEBUG_BUILD && logger.info('Converting buffer to session'); // Allow flush to complete before resuming as a session recording, otherwise // the checkout from `startRecording` may be included in the payload. @@ -798,7 +787,6 @@ export class ReplayContainer implements ReplayContainerInterface { { sessionIdleExpire: this.timeouts.sessionIdleExpire, maxReplayDuration: this._options.maxReplayDuration, - traceInternals: this._options._experiments.traceInternals, previousSessionId, }, { @@ -990,7 +978,7 @@ export class ReplayContainer implements ReplayContainerInterface { // If the user has come back to the page within SESSION_IDLE_PAUSE_DURATION // ms, we will re-use the existing session, otherwise create a new // session - logInfo('[Replay] Document has become active, but session has expired'); + DEBUG_BUILD && logger.info('Document has become active, but session has expired'); return; } @@ -1106,7 +1094,7 @@ export class ReplayContainer implements ReplayContainerInterface { const replayId = this.getSessionId(); if (!this.session || !this.eventBuffer || !replayId) { - DEBUG_BUILD && logger.error('[Replay] No session or eventBuffer found to flush.'); + DEBUG_BUILD && logger.error('No session or eventBuffer found to flush.'); return; } @@ -1198,7 +1186,7 @@ export class ReplayContainer implements ReplayContainerInterface { } if (!this.checkAndHandleExpiredSession()) { - DEBUG_BUILD && logger.error('[Replay] Attempting to finish replay event after session expired.'); + DEBUG_BUILD && logger.error('Attempting to finish replay event after session expired.'); return; } @@ -1219,12 +1207,12 @@ export class ReplayContainer implements ReplayContainerInterface { const tooShort = duration < this._options.minReplayDuration; const tooLong = duration > this._options.maxReplayDuration + 5_000; if (tooShort || tooLong) { - logInfo( - `[Replay] Session duration (${Math.floor(duration / 1000)}s) is too ${ - tooShort ? 'short' : 'long' - }, not sending replay.`, - this._options._experiments.traceInternals, - ); + DEBUG_BUILD && + logger.info( + `Session duration (${Math.floor(duration / 1000)}s) is too ${ + tooShort ? 'short' : 'long' + }, not sending replay.`, + ); if (tooShort) { this._debouncedFlush(); @@ -1234,7 +1222,7 @@ export class ReplayContainer implements ReplayContainerInterface { const eventBuffer = this.eventBuffer; if (eventBuffer && this.session.segmentId === 0 && !eventBuffer.hasCheckout) { - logInfo('[Replay] Flushing initial segment without checkout.', this._options._experiments.traceInternals); + DEBUG_BUILD && logger.info('Flushing initial segment without checkout.'); // TODO FN: Evaluate if we want to stop here, or remove this again? } diff --git a/packages/replay-internal/src/session/fetchSession.ts b/packages/replay-internal/src/session/fetchSession.ts index 43e162b5f3d6..031605bfde87 100644 --- a/packages/replay-internal/src/session/fetchSession.ts +++ b/packages/replay-internal/src/session/fetchSession.ts @@ -1,13 +1,14 @@ import { REPLAY_SESSION_KEY, WINDOW } from '../constants'; +import { DEBUG_BUILD } from '../debug-build'; import type { Session } from '../types'; import { hasSessionStorage } from '../util/hasSessionStorage'; -import { logInfoNextTick } from '../util/log'; +import { logger } from '../util/logger'; import { makeSession } from './Session'; /** * Fetches a session from storage */ -export function fetchSession(traceInternals?: boolean): Session | null { +export function fetchSession(): Session | null { if (!hasSessionStorage()) { return null; } @@ -22,7 +23,7 @@ export function fetchSession(traceInternals?: boolean): Session | null { const sessionObj = JSON.parse(sessionStringFromStorage) as Session; - logInfoNextTick('[Replay] Loading existing session', traceInternals); + DEBUG_BUILD && logger.infoTick('Loading existing session'); return makeSession(sessionObj); } catch { diff --git a/packages/replay-internal/src/session/loadOrCreateSession.ts b/packages/replay-internal/src/session/loadOrCreateSession.ts index 1e1ac7664d40..d37c51590d54 100644 --- a/packages/replay-internal/src/session/loadOrCreateSession.ts +++ b/packages/replay-internal/src/session/loadOrCreateSession.ts @@ -1,5 +1,6 @@ +import { DEBUG_BUILD } from '../debug-build'; import type { Session, SessionOptions } from '../types'; -import { logInfoNextTick } from '../util/log'; +import { logger } from '../util/logger'; import { createSession } from './createSession'; import { fetchSession } from './fetchSession'; import { shouldRefreshSession } from './shouldRefreshSession'; @@ -10,23 +11,21 @@ import { shouldRefreshSession } from './shouldRefreshSession'; */ export function loadOrCreateSession( { - traceInternals, sessionIdleExpire, maxReplayDuration, previousSessionId, }: { sessionIdleExpire: number; maxReplayDuration: number; - traceInternals?: boolean; previousSessionId?: string; }, sessionOptions: SessionOptions, ): Session { - const existingSession = sessionOptions.stickySession && fetchSession(traceInternals); + const existingSession = sessionOptions.stickySession && fetchSession(); // No session exists yet, just create a new one if (!existingSession) { - logInfoNextTick('[Replay] Creating new session', traceInternals); + DEBUG_BUILD && logger.infoTick('Creating new session'); return createSession(sessionOptions, { previousSessionId }); } @@ -34,6 +33,6 @@ export function loadOrCreateSession( return existingSession; } - logInfoNextTick('[Replay] Session in sessionStorage is expired, creating new one...'); + DEBUG_BUILD && logger.infoTick('Session in sessionStorage is expired, creating new one...'); return createSession(sessionOptions, { previousSessionId: existingSession.id }); } diff --git a/packages/replay-internal/src/util/addEvent.ts b/packages/replay-internal/src/util/addEvent.ts index f397ea0564f6..4a1c9dc2f6d2 100644 --- a/packages/replay-internal/src/util/addEvent.ts +++ b/packages/replay-internal/src/util/addEvent.ts @@ -1,11 +1,10 @@ import { EventType } from '@sentry-internal/rrweb'; import { getClient } from '@sentry/core'; -import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; import { EventBufferSizeExceededError } from '../eventBuffer/error'; import type { AddEventResult, RecordingEvent, ReplayContainer, ReplayFrameEvent, ReplayPluginOptions } from '../types'; -import { logInfoNextTick } from './log'; +import { logger } from './logger'; import { timestampToMs } from './timestamp'; function isCustomEvent(event: RecordingEvent): event is ReplayFrameEvent { @@ -109,10 +108,8 @@ export function shouldAddEvent(replay: ReplayContainer, event: RecordingEvent): // Throw out events that are +60min from the initial timestamp if (timestampInMs > replay.getContext().initialTimestamp + replay.getOptions().maxReplayDuration) { - logInfoNextTick( - `[Replay] Skipping event with timestamp ${timestampInMs} because it is after maxReplayDuration`, - replay.getOptions()._experiments.traceInternals, - ); + DEBUG_BUILD && + logger.infoTick(`Skipping event with timestamp ${timestampInMs} because it is after maxReplayDuration`); return false; } @@ -128,8 +125,8 @@ function maybeApplyCallback( return callback(event); } } catch (error) { - DEBUG_BUILD && - logger.error('[Replay] An error occured in the `beforeAddRecordingEvent` callback, skipping the event...', error); + DEBUG_BUILD && logger.error('An error occured in the `beforeAddRecordingEvent` callback, skipping the event...'); + DEBUG_BUILD && logger.exception(error); return null; } diff --git a/packages/replay-internal/src/util/handleRecordingEmit.ts b/packages/replay-internal/src/util/handleRecordingEmit.ts index eaec29be261a..6b87845d793f 100644 --- a/packages/replay-internal/src/util/handleRecordingEmit.ts +++ b/packages/replay-internal/src/util/handleRecordingEmit.ts @@ -1,12 +1,11 @@ import { EventType } from '@sentry-internal/rrweb'; -import { logger } from '@sentry/utils'; import { updateClickDetectorForRecordingEvent } from '../coreHandlers/handleClick'; import { DEBUG_BUILD } from '../debug-build'; import { saveSession } from '../session/saveSession'; import type { RecordingEvent, ReplayContainer, ReplayOptionFrameEvent } from '../types'; import { addEventSync } from './addEvent'; -import { logInfo } from './log'; +import { logger } from './logger'; type RecordingEmitCallback = (event: RecordingEvent, isCheckout?: boolean) => void; @@ -21,7 +20,7 @@ export function getHandleRecordingEmit(replay: ReplayContainer): RecordingEmitCa return (event: RecordingEvent, _isCheckout?: boolean) => { // If this is false, it means session is expired, create and a new session and wait for checkout if (!replay.checkAndHandleExpiredSession()) { - DEBUG_BUILD && logger.warn('[Replay] Received replay event after session expired.'); + DEBUG_BUILD && logger.warn('Received replay event after session expired.'); return; } @@ -82,10 +81,8 @@ export function getHandleRecordingEmit(replay: ReplayContainer): RecordingEmitCa if (replay.recordingMode === 'buffer' && replay.session && replay.eventBuffer) { const earliestEvent = replay.eventBuffer.getEarliestTimestamp(); if (earliestEvent) { - logInfo( - `[Replay] Updating session start time to earliest event in buffer to ${new Date(earliestEvent)}`, - replay.getOptions()._experiments.traceInternals, - ); + DEBUG_BUILD && + logger.info(`Updating session start time to earliest event in buffer to ${new Date(earliestEvent)}`); replay.session.started = earliestEvent; diff --git a/packages/replay-internal/src/util/log.ts b/packages/replay-internal/src/util/log.ts deleted file mode 100644 index c847a093f02f..000000000000 --- a/packages/replay-internal/src/util/log.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { setTimeout } from '@sentry-internal/browser-utils'; -import { addBreadcrumb } from '@sentry/core'; -import { logger } from '@sentry/utils'; - -import { DEBUG_BUILD } from '../debug-build'; - -/** - * Log a message in debug mode, and add a breadcrumb when _experiment.traceInternals is enabled. - */ -export function logInfo(message: string, shouldAddBreadcrumb?: boolean): void { - if (!DEBUG_BUILD) { - return; - } - - logger.info(message); - - if (shouldAddBreadcrumb) { - addLogBreadcrumb(message); - } -} - -/** - * Log a message, and add a breadcrumb in the next tick. - * This is necessary when the breadcrumb may be added before the replay is initialized. - */ -export function logInfoNextTick(message: string, shouldAddBreadcrumb?: boolean): void { - if (!DEBUG_BUILD) { - return; - } - - logger.info(message); - - if (shouldAddBreadcrumb) { - // Wait a tick here to avoid race conditions for some initial logs - // which may be added before replay is initialized - setTimeout(() => { - addLogBreadcrumb(message); - }, 0); - } -} - -function addLogBreadcrumb(message: string): void { - addBreadcrumb( - { - category: 'console', - data: { - logger: 'replay', - }, - level: 'info', - message, - }, - { level: 'info' }, - ); -} diff --git a/packages/replay-internal/src/util/logger.ts b/packages/replay-internal/src/util/logger.ts new file mode 100644 index 000000000000..57c11fa74caf --- /dev/null +++ b/packages/replay-internal/src/util/logger.ts @@ -0,0 +1,105 @@ +import { addBreadcrumb, captureException } from '@sentry/core'; +import type { SeverityLevel } from '@sentry/types'; +import { logger as coreLogger } from '@sentry/utils'; + +import { DEBUG_BUILD } from '../debug-build'; + +const CONSOLE_LEVELS = ['info', 'warn', 'error', 'log'] as const; + +type LoggerMethod = (...args: unknown[]) => void; +type LoggerConsoleMethods = Record<'info' | 'warn' | 'error' | 'log', LoggerMethod>; + +/** JSDoc */ +interface ReplayLogger extends LoggerConsoleMethods { + /** + * Calls `logger.info` but saves breadcrumb in the next tick due to race + * conditions before replay is initialized. + */ + infoTick(...args: unknown[]): void; + /** + * Captures exceptions (`Error`) if "capture internal exceptions" is enabled + */ + exception(error: unknown): void; + enableCaptureInternalExceptions(): void; + disableCaptureInternalExceptions(): void; + enableTraceInternals(): void; + disableTraceInternals(): void; +} + +function _addBreadcrumb(message: string, level: SeverityLevel = 'info'): void { + // Wait a tick here to avoid race conditions for some initial logs + // which may be added before replay is initialized + addBreadcrumb( + { + category: 'console', + data: { + logger: 'replay', + }, + level, + message: `[Replay] ${message}`, + }, + { level }, + ); +} + +function makeReplayLogger(): ReplayLogger { + let _capture = false; + let _trace = false; + + const _logger: Partial = { + exception: () => undefined, + infoTick: () => undefined, + enableCaptureInternalExceptions: () => { + _capture = true; + }, + disableCaptureInternalExceptions: () => { + _capture = false; + }, + enableTraceInternals: () => { + _trace = true; + }, + disableTraceInternals: () => { + _trace = false; + }, + }; + + if (DEBUG_BUILD) { + _logger.exception = (error: unknown) => { + coreLogger.error('[Replay] ', error); + + if (_capture) { + captureException(error); + } + + // No need for a breadcrumb is `_capture` is enabled since it should be + // captured as an exception + if (_trace && !_capture) { + _addBreadcrumb(error instanceof Error ? error.message : 'Unknown error'); + } + }; + + _logger.infoTick = (...args: unknown[]) => { + coreLogger.info('[Replay] ', ...args); + if (_trace) { + setTimeout(() => typeof args[0] === 'string' && _addBreadcrumb(args[0]), 0); + } + }; + + CONSOLE_LEVELS.forEach(name => { + _logger[name] = (...args: unknown[]) => { + coreLogger[name]('[Replay] ', ...args); + if (_trace && typeof args[0] === 'string') { + _addBreadcrumb(args[0]); + } + }; + }); + } else { + CONSOLE_LEVELS.forEach(name => { + _logger[name] = () => undefined; + }); + } + + return _logger as ReplayLogger; +} + +export const logger = makeReplayLogger(); diff --git a/packages/replay-internal/src/util/sendReplayRequest.ts b/packages/replay-internal/src/util/sendReplayRequest.ts index 03945bb479af..a623771af75b 100644 --- a/packages/replay-internal/src/util/sendReplayRequest.ts +++ b/packages/replay-internal/src/util/sendReplayRequest.ts @@ -5,9 +5,10 @@ import { resolvedSyncPromise } from '@sentry/utils'; import { isRateLimited, updateRateLimits } from '@sentry/utils'; import { REPLAY_EVENT_NAME, UNABLE_TO_SEND_REPLAY } from '../constants'; +import { DEBUG_BUILD } from '../debug-build'; import type { SendReplayData } from '../types'; import { createReplayEnvelope } from './createReplayEnvelope'; -import { logInfo } from './log'; +import { logger } from './logger'; import { prepareRecordingData } from './prepareRecordingData'; import { prepareReplayEvent } from './prepareReplayEvent'; @@ -57,7 +58,7 @@ export async function sendReplayRequest({ if (!replayEvent) { // Taken from baseclient's `_processEvent` method, where this is handled for errors/transactions client.recordDroppedEvent('event_processor', 'replay', baseEvent); - logInfo('An event processor returned `null`, will not send event.'); + DEBUG_BUILD && logger.info('An event processor returned `null`, will not send event.'); return resolvedSyncPromise({}); } diff --git a/packages/replay-internal/test/integration/flush.test.ts b/packages/replay-internal/test/integration/flush.test.ts index 999811de0a81..78a945d5fddd 100644 --- a/packages/replay-internal/test/integration/flush.test.ts +++ b/packages/replay-internal/test/integration/flush.test.ts @@ -19,6 +19,7 @@ import { clearSession } from '../../src/session/clearSession'; import type { EventBuffer } from '../../src/types'; import { createPerformanceEntries } from '../../src/util/createPerformanceEntries'; import { createPerformanceSpans } from '../../src/util/createPerformanceSpans'; +import { logger } from '../../src/util/logger'; import * as SendReplay from '../../src/util/sendReplay'; import { BASE_TIMESTAMP, mockRrweb, mockSdk } from '../index'; import type { DomHandler } from '../types'; @@ -335,7 +336,8 @@ describe('Integration | flush', () => { }); it('logs warning if flushing initial segment without checkout', async () => { - replay.getOptions()._experiments.traceInternals = true; + // replay.getOptions()._experiments.traceInternals = true; + logger.enableTraceInternals(); sessionStorage.clear(); clearSession(replay); @@ -408,11 +410,11 @@ describe('Integration | flush', () => { }, ]); - replay.getOptions()._experiments.traceInternals = false; + logger.disableTraceInternals(); }); it('logs warning if adding event that is after maxReplayDuration', async () => { - replay.getOptions()._experiments.traceInternals = true; + logger.enableTraceInternals(); const spyLogger = vi.spyOn(SentryUtils.logger, 'info'); @@ -440,12 +442,13 @@ describe('Integration | flush', () => { expect(mockSendReplay).toHaveBeenCalledTimes(0); expect(spyLogger).toHaveBeenLastCalledWith( - `[Replay] Skipping event with timestamp ${ + '[Replay] ', + `Skipping event with timestamp ${ BASE_TIMESTAMP + MAX_REPLAY_DURATION + 100 } because it is after maxReplayDuration`, ); - replay.getOptions()._experiments.traceInternals = false; + logger.disableTraceInternals(); spyLogger.mockRestore(); }); From 885e312e51fea4f07b0ccfba7a90ec06a8cb15a8 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 6 Aug 2024 17:57:52 -0400 Subject: [PATCH 2/8] tweak types --- packages/replay-internal/src/util/logger.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/replay-internal/src/util/logger.ts b/packages/replay-internal/src/util/logger.ts index 57c11fa74caf..4c427c0d570f 100644 --- a/packages/replay-internal/src/util/logger.ts +++ b/packages/replay-internal/src/util/logger.ts @@ -1,10 +1,11 @@ import { addBreadcrumb, captureException } from '@sentry/core'; -import type { SeverityLevel } from '@sentry/types'; +import type { ConsoleLevel, SeverityLevel } from '@sentry/types'; import { logger as coreLogger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; -const CONSOLE_LEVELS = ['info', 'warn', 'error', 'log'] as const; +type ReplayConsoleLevels = Extract; +const CONSOLE_LEVELS: readonly ReplayConsoleLevels[] = ['info', 'warn', 'error', 'log'] as const; type LoggerMethod = (...args: unknown[]) => void; type LoggerConsoleMethods = Record<'info' | 'warn' | 'error' | 'log', LoggerMethod>; @@ -15,11 +16,11 @@ interface ReplayLogger extends LoggerConsoleMethods { * Calls `logger.info` but saves breadcrumb in the next tick due to race * conditions before replay is initialized. */ - infoTick(...args: unknown[]): void; + infoTick: LoggerMethod; /** * Captures exceptions (`Error`) if "capture internal exceptions" is enabled */ - exception(error: unknown): void; + exception: LoggerMethod; enableCaptureInternalExceptions(): void; disableCaptureInternalExceptions(): void; enableTraceInternals(): void; From 11415f55f413b05f021c2ef8b6d1261ecd1fb4b2 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 7 Aug 2024 12:33:09 -0400 Subject: [PATCH 3/8] refactor --- packages/replay-internal/src/replay.ts | 12 ++-- packages/replay-internal/src/util/logger.ts | 57 ++++++++++--------- .../test/integration/flush.test.ts | 9 ++- 3 files changed, 38 insertions(+), 40 deletions(-) diff --git a/packages/replay-internal/src/replay.ts b/packages/replay-internal/src/replay.ts index a70a384b7c68..d563fe10288c 100644 --- a/packages/replay-internal/src/replay.ts +++ b/packages/replay-internal/src/replay.ts @@ -206,13 +206,11 @@ export class ReplayContainer implements ReplayContainerInterface { } // Configure replay logger w/ experimental options - if (this._options._experiments.traceInternals) { - logger.enableTraceInternals(); - } - - if (this._options._experiments.captureExceptions) { - logger.enableCaptureInternalExceptions(); - } + const experiments = options._experiments; + logger.setConfig({ + captureExceptions: !!experiments.captureExceptions, + traceInternals: !!experiments.traceInternals, + }); } /** Get the event context. */ diff --git a/packages/replay-internal/src/util/logger.ts b/packages/replay-internal/src/util/logger.ts index 4c427c0d570f..28d4883d5c78 100644 --- a/packages/replay-internal/src/util/logger.ts +++ b/packages/replay-internal/src/util/logger.ts @@ -6,9 +6,15 @@ import { DEBUG_BUILD } from '../debug-build'; type ReplayConsoleLevels = Extract; const CONSOLE_LEVELS: readonly ReplayConsoleLevels[] = ['info', 'warn', 'error', 'log'] as const; +const PREFIX = '[Replay] '; type LoggerMethod = (...args: unknown[]) => void; -type LoggerConsoleMethods = Record<'info' | 'warn' | 'error' | 'log', LoggerMethod>; +type LoggerConsoleMethods = Record; + +interface LoggerConfig { + captureExceptions: boolean; + traceInternals: boolean; +} /** JSDoc */ interface ReplayLogger extends LoggerConsoleMethods { @@ -21,13 +27,18 @@ interface ReplayLogger extends LoggerConsoleMethods { * Captures exceptions (`Error`) if "capture internal exceptions" is enabled */ exception: LoggerMethod; - enableCaptureInternalExceptions(): void; - disableCaptureInternalExceptions(): void; - enableTraceInternals(): void; - disableTraceInternals(): void; + /** + * Configures the logger with additional debugging behavior + */ + setConfig(config: LoggerConfig): void; } -function _addBreadcrumb(message: string, level: SeverityLevel = 'info'): void { +function _addBreadcrumb(message: unknown, level: SeverityLevel = 'info'): void { + // Only support strings for breadcrumbs + if (typeof message !== 'string') { + return; + } + // Wait a tick here to avoid race conditions for some initial logs // which may be added before replay is initialized addBreadcrumb( @@ -37,7 +48,7 @@ function _addBreadcrumb(message: string, level: SeverityLevel = 'info'): void { logger: 'replay', }, level, - message: `[Replay] ${message}`, + message: `${PREFIX} ${message}`, }, { level }, ); @@ -50,46 +61,36 @@ function makeReplayLogger(): ReplayLogger { const _logger: Partial = { exception: () => undefined, infoTick: () => undefined, - enableCaptureInternalExceptions: () => { - _capture = true; - }, - disableCaptureInternalExceptions: () => { - _capture = false; - }, - enableTraceInternals: () => { - _trace = true; - }, - disableTraceInternals: () => { - _trace = false; + setConfig: (opts: LoggerConfig) => { + _capture = opts.captureExceptions; + _trace = opts.traceInternals; }, }; if (DEBUG_BUILD) { _logger.exception = (error: unknown) => { - coreLogger.error('[Replay] ', error); + coreLogger.error(PREFIX, error); if (_capture) { captureException(error); - } - - // No need for a breadcrumb is `_capture` is enabled since it should be - // captured as an exception - if (_trace && !_capture) { + } else if (_trace) { + // No need for a breadcrumb is `_capture` is enabled since it should be + // captured as an exception _addBreadcrumb(error instanceof Error ? error.message : 'Unknown error'); } }; _logger.infoTick = (...args: unknown[]) => { - coreLogger.info('[Replay] ', ...args); + coreLogger.info(PREFIX, ...args); if (_trace) { - setTimeout(() => typeof args[0] === 'string' && _addBreadcrumb(args[0]), 0); + setTimeout(() => _addBreadcrumb(args[0]), 0); } }; CONSOLE_LEVELS.forEach(name => { _logger[name] = (...args: unknown[]) => { - coreLogger[name]('[Replay] ', ...args); - if (_trace && typeof args[0] === 'string') { + coreLogger[name](PREFIX, ...args); + if (_trace) { _addBreadcrumb(args[0]); } }; diff --git a/packages/replay-internal/test/integration/flush.test.ts b/packages/replay-internal/test/integration/flush.test.ts index 78a945d5fddd..ffc0a83bb141 100644 --- a/packages/replay-internal/test/integration/flush.test.ts +++ b/packages/replay-internal/test/integration/flush.test.ts @@ -336,8 +336,7 @@ describe('Integration | flush', () => { }); it('logs warning if flushing initial segment without checkout', async () => { - // replay.getOptions()._experiments.traceInternals = true; - logger.enableTraceInternals(); + logger.setConfig({ traceInternals: true }); sessionStorage.clear(); clearSession(replay); @@ -410,11 +409,11 @@ describe('Integration | flush', () => { }, ]); - logger.disableTraceInternals(); + logger.setConfig({ traceInternals: false }); }); it('logs warning if adding event that is after maxReplayDuration', async () => { - logger.enableTraceInternals(); + logger.setConfig({ traceInternals: true }); const spyLogger = vi.spyOn(SentryUtils.logger, 'info'); @@ -448,7 +447,7 @@ describe('Integration | flush', () => { } because it is after maxReplayDuration`, ); - logger.disableTraceInternals(); + logger.setConfig({ traceInternals: false }); spyLogger.mockRestore(); }); From 39979430cbd8c43db1d0dd45c514b7a3631458a5 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 7 Aug 2024 13:21:49 -0400 Subject: [PATCH 4/8] extra space --- packages/replay-internal/src/util/logger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/replay-internal/src/util/logger.ts b/packages/replay-internal/src/util/logger.ts index 28d4883d5c78..154002983f3e 100644 --- a/packages/replay-internal/src/util/logger.ts +++ b/packages/replay-internal/src/util/logger.ts @@ -48,7 +48,7 @@ function _addBreadcrumb(message: unknown, level: SeverityLevel = 'info'): void { logger: 'replay', }, level, - message: `${PREFIX} ${message}`, + message: `${PREFIX}${message}`, }, { level }, ); From bb541df45b62a7b49ad5be302920f778b62d82e7 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 7 Aug 2024 13:31:47 -0400 Subject: [PATCH 5/8] only configure with DEBUG_BUILD --- packages/replay-internal/src/replay.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/replay-internal/src/replay.ts b/packages/replay-internal/src/replay.ts index d563fe10288c..b48ac787543b 100644 --- a/packages/replay-internal/src/replay.ts +++ b/packages/replay-internal/src/replay.ts @@ -206,11 +206,13 @@ export class ReplayContainer implements ReplayContainerInterface { } // Configure replay logger w/ experimental options - const experiments = options._experiments; - logger.setConfig({ - captureExceptions: !!experiments.captureExceptions, - traceInternals: !!experiments.traceInternals, - }); + if (DEBUG_BUILD) { + const experiments = options._experiments; + logger.setConfig({ + captureExceptions: !!experiments.captureExceptions, + traceInternals: !!experiments.traceInternals, + }); + } } /** Get the event context. */ From ebea1a510a117da63dac399dadbf7bf81407281e Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Thu, 8 Aug 2024 14:43:37 -0400 Subject: [PATCH 6/8] combine logger.exception --- .../coreHandlers/handleNetworkBreadcrumbs.ts | 3 +-- .../src/coreHandlers/util/fetchUtils.ts | 12 ++++------ .../src/coreHandlers/util/networkUtils.ts | 3 +-- .../src/coreHandlers/util/xhrUtils.ts | 6 ++--- .../EventBufferCompressionWorker.ts | 3 +-- .../src/eventBuffer/EventBufferProxy.ts | 6 ++--- .../replay-internal/src/eventBuffer/index.ts | 3 +-- packages/replay-internal/src/util/addEvent.ts | 4 ++-- packages/replay-internal/src/util/logger.ts | 24 +++++++++++-------- 9 files changed, 28 insertions(+), 36 deletions(-) diff --git a/packages/replay-internal/src/coreHandlers/handleNetworkBreadcrumbs.ts b/packages/replay-internal/src/coreHandlers/handleNetworkBreadcrumbs.ts index fb6d0c017943..8b95a1f5fabe 100644 --- a/packages/replay-internal/src/coreHandlers/handleNetworkBreadcrumbs.ts +++ b/packages/replay-internal/src/coreHandlers/handleNetworkBreadcrumbs.ts @@ -79,8 +79,7 @@ export function beforeAddNetworkBreadcrumb( captureFetchBreadcrumbToReplay(breadcrumb, hint, options); } } catch (e) { - DEBUG_BUILD && logger.warn('Error when enriching network breadcrumb'); - DEBUG_BUILD && logger.exception(e); + DEBUG_BUILD && logger.exception(e, 'Error when enriching network breadcrumb'); } } diff --git a/packages/replay-internal/src/coreHandlers/util/fetchUtils.ts b/packages/replay-internal/src/coreHandlers/util/fetchUtils.ts index c03ab9e62eb0..6502206b58b6 100644 --- a/packages/replay-internal/src/coreHandlers/util/fetchUtils.ts +++ b/packages/replay-internal/src/coreHandlers/util/fetchUtils.ts @@ -42,8 +42,7 @@ export async function captureFetchBreadcrumbToReplay( const result = makeNetworkReplayBreadcrumb('resource.fetch', data); addNetworkBreadcrumb(options.replay, result); } catch (error) { - DEBUG_BUILD && logger.error('Failed to capture fetch breadcrumb'); - DEBUG_BUILD && logger.exception(error); + DEBUG_BUILD && logger.exception(error, 'Failed to capture fetch breadcrumb'); } } @@ -193,8 +192,7 @@ function getResponseData( return buildNetworkRequestOrResponse(headers, size, undefined); } catch (error) { - DEBUG_BUILD && logger.warn('Failed to serialize response body'); - DEBUG_BUILD && logger.exception(error); + DEBUG_BUILD && logger.exception(error, 'Failed to serialize response body'); // fallback return buildNetworkRequestOrResponse(headers, responseBodySize, undefined); } @@ -211,8 +209,7 @@ async function _parseFetchResponseBody(response: Response): Promise<[string | un const text = await _tryGetResponseText(res); return [text]; } catch (error) { - DEBUG_BUILD && logger.warn('Failed to get text body from response'); - DEBUG_BUILD && logger.exception(error); + DEBUG_BUILD && logger.exception(error, 'Failed to get text body from response'); return [undefined, 'BODY_PARSE_ERROR']; } } @@ -282,8 +279,7 @@ function _tryCloneResponse(response: Response): Response | void { return response.clone(); } catch (error) { // this can throw if the response was already consumed before - DEBUG_BUILD && logger.warn('Failed to clone response body'); - DEBUG_BUILD && logger.exception(error); + DEBUG_BUILD && logger.exception(error, 'Failed to clone response body'); } } diff --git a/packages/replay-internal/src/coreHandlers/util/networkUtils.ts b/packages/replay-internal/src/coreHandlers/util/networkUtils.ts index b438f5924333..2267fa502333 100644 --- a/packages/replay-internal/src/coreHandlers/util/networkUtils.ts +++ b/packages/replay-internal/src/coreHandlers/util/networkUtils.ts @@ -79,8 +79,7 @@ export function getBodyString(body: unknown): [string | undefined, NetworkMetaWa return [undefined]; } } catch (error) { - DEBUG_BUILD && logger.warn('Failed to serialize body', body); - DEBUG_BUILD && logger.exception(error); + DEBUG_BUILD && logger.exception(error, 'Failed to serialize body', body); return [undefined, 'BODY_PARSE_ERROR']; } diff --git a/packages/replay-internal/src/coreHandlers/util/xhrUtils.ts b/packages/replay-internal/src/coreHandlers/util/xhrUtils.ts index dfeb53b720b3..52b6cafdfec7 100644 --- a/packages/replay-internal/src/coreHandlers/util/xhrUtils.ts +++ b/packages/replay-internal/src/coreHandlers/util/xhrUtils.ts @@ -39,8 +39,7 @@ export async function captureXhrBreadcrumbToReplay( const result = makeNetworkReplayBreadcrumb('resource.xhr', data); addNetworkBreadcrumb(options.replay, result); } catch (error) { - DEBUG_BUILD && logger.warn('Failed to capture xhr breadcrumb'); - DEBUG_BUILD && logger.exception(error); + DEBUG_BUILD && logger.exception(error, 'Failed to capture xhr breadcrumb'); } } @@ -199,8 +198,7 @@ export function _parseXhrResponse( return [undefined]; } } catch (error) { - DEBUG_BUILD && logger.warn('Failed to serialize body', body); - DEBUG_BUILD && logger.exception(error); + DEBUG_BUILD && logger.exception(error, 'Failed to serialize body', body); return [undefined, 'BODY_PARSE_ERROR']; } diff --git a/packages/replay-internal/src/eventBuffer/EventBufferCompressionWorker.ts b/packages/replay-internal/src/eventBuffer/EventBufferCompressionWorker.ts index 59e5b62b229c..90a54bbf07f3 100644 --- a/packages/replay-internal/src/eventBuffer/EventBufferCompressionWorker.ts +++ b/packages/replay-internal/src/eventBuffer/EventBufferCompressionWorker.ts @@ -88,8 +88,7 @@ export class EventBufferCompressionWorker implements EventBuffer { // We do not wait on this, as we assume the order of messages is consistent for the worker this._worker.postMessage('clear').then(null, e => { - DEBUG_BUILD && logger.warn('Sending "clear" message to worker failed', e); - DEBUG_BUILD && logger.exception(e); + DEBUG_BUILD && logger.exception(e, 'Sending "clear" message to worker failed', e); }); } diff --git a/packages/replay-internal/src/eventBuffer/EventBufferProxy.ts b/packages/replay-internal/src/eventBuffer/EventBufferProxy.ts index 17f78ccaf8b3..413bb6fb6372 100644 --- a/packages/replay-internal/src/eventBuffer/EventBufferProxy.ts +++ b/packages/replay-internal/src/eventBuffer/EventBufferProxy.ts @@ -89,8 +89,7 @@ export class EventBufferProxy implements EventBuffer { } catch (error) { // If the worker fails to load, we fall back to the simple buffer. // Nothing more to do from our side here - DEBUG_BUILD && logger.info('Failed to load the compression worker, falling back to simple buffer'); - DEBUG_BUILD && logger.exception(error); + DEBUG_BUILD && logger.exception(error, 'Failed to load the compression worker, falling back to simple buffer'); return; } @@ -117,8 +116,7 @@ export class EventBufferProxy implements EventBuffer { try { await Promise.all(addEventPromises); } catch (error) { - DEBUG_BUILD && logger.error('Failed to add events when switching buffers.'); - DEBUG_BUILD && logger.exception(error); + DEBUG_BUILD && logger.exception(error, 'Failed to add events when switching buffers.'); } } } diff --git a/packages/replay-internal/src/eventBuffer/index.ts b/packages/replay-internal/src/eventBuffer/index.ts index 56b64b80dbb4..bc000da5db7e 100644 --- a/packages/replay-internal/src/eventBuffer/index.ts +++ b/packages/replay-internal/src/eventBuffer/index.ts @@ -49,8 +49,7 @@ function _loadWorker(customWorkerUrl?: string): EventBufferProxy | void { const worker = new Worker(workerUrl); return new EventBufferProxy(worker); } catch (error) { - DEBUG_BUILD && logger.warn('Failed to create compression worker'); - DEBUG_BUILD && logger.exception(error); + DEBUG_BUILD && logger.exception(error, 'Failed to create compression worker'); // Fall back to use simple event buffer array } } diff --git a/packages/replay-internal/src/util/addEvent.ts b/packages/replay-internal/src/util/addEvent.ts index 4a1c9dc2f6d2..700627cf954f 100644 --- a/packages/replay-internal/src/util/addEvent.ts +++ b/packages/replay-internal/src/util/addEvent.ts @@ -125,8 +125,8 @@ function maybeApplyCallback( return callback(event); } } catch (error) { - DEBUG_BUILD && logger.error('An error occured in the `beforeAddRecordingEvent` callback, skipping the event...'); - DEBUG_BUILD && logger.exception(error); + DEBUG_BUILD && + logger.exception(error, 'An error occured in the `beforeAddRecordingEvent` callback, skipping the event...'); return null; } diff --git a/packages/replay-internal/src/util/logger.ts b/packages/replay-internal/src/util/logger.ts index 154002983f3e..ed1963b7b91d 100644 --- a/packages/replay-internal/src/util/logger.ts +++ b/packages/replay-internal/src/util/logger.ts @@ -68,7 +68,20 @@ function makeReplayLogger(): ReplayLogger { }; if (DEBUG_BUILD) { - _logger.exception = (error: unknown) => { + CONSOLE_LEVELS.forEach(name => { + _logger[name] = (...args: unknown[]) => { + coreLogger[name](PREFIX, ...args); + if (_trace) { + _addBreadcrumb(args[0]); + } + }; + }); + + _logger.exception = (error: unknown, ...message: unknown[]) => { + if (message && _logger.error) { + _logger.error(...message); + } + coreLogger.error(PREFIX, error); if (_capture) { @@ -86,15 +99,6 @@ function makeReplayLogger(): ReplayLogger { setTimeout(() => _addBreadcrumb(args[0]), 0); } }; - - CONSOLE_LEVELS.forEach(name => { - _logger[name] = (...args: unknown[]) => { - coreLogger[name](PREFIX, ...args); - if (_trace) { - _addBreadcrumb(args[0]); - } - }; - }); } else { CONSOLE_LEVELS.forEach(name => { _logger[name] = () => undefined; From ff32c1427ee3e4f858068d10a5ba804715a1ac82 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Mon, 12 Aug 2024 15:36:06 -0400 Subject: [PATCH 7/8] CR updates --- packages/replay-internal/src/util/logger.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/replay-internal/src/util/logger.ts b/packages/replay-internal/src/util/logger.ts index ed1963b7b91d..9fb6cf4b5362 100644 --- a/packages/replay-internal/src/util/logger.ts +++ b/packages/replay-internal/src/util/logger.ts @@ -16,7 +16,6 @@ interface LoggerConfig { traceInternals: boolean; } -/** JSDoc */ interface ReplayLogger extends LoggerConsoleMethods { /** * Calls `logger.info` but saves breadcrumb in the next tick due to race @@ -34,11 +33,6 @@ interface ReplayLogger extends LoggerConsoleMethods { } function _addBreadcrumb(message: unknown, level: SeverityLevel = 'info'): void { - // Only support strings for breadcrumbs - if (typeof message !== 'string') { - return; - } - // Wait a tick here to avoid race conditions for some initial logs // which may be added before replay is initialized addBreadcrumb( @@ -78,7 +72,7 @@ function makeReplayLogger(): ReplayLogger { }); _logger.exception = (error: unknown, ...message: unknown[]) => { - if (message && _logger.error) { + if (_logger.error) { _logger.error(...message); } @@ -89,7 +83,7 @@ function makeReplayLogger(): ReplayLogger { } else if (_trace) { // No need for a breadcrumb is `_capture` is enabled since it should be // captured as an exception - _addBreadcrumb(error instanceof Error ? error.message : 'Unknown error'); + _addBreadcrumb(error); } }; From 2252f2a2623e9eb147916b2fc4c772a38cf72d3d Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Mon, 12 Aug 2024 17:25:24 -0400 Subject: [PATCH 8/8] fix comment --- packages/replay-internal/src/util/logger.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/replay-internal/src/util/logger.ts b/packages/replay-internal/src/util/logger.ts index 9fb6cf4b5362..80445409164b 100644 --- a/packages/replay-internal/src/util/logger.ts +++ b/packages/replay-internal/src/util/logger.ts @@ -33,8 +33,6 @@ interface ReplayLogger extends LoggerConsoleMethods { } function _addBreadcrumb(message: unknown, level: SeverityLevel = 'info'): void { - // Wait a tick here to avoid race conditions for some initial logs - // which may be added before replay is initialized addBreadcrumb( { category: 'console', @@ -90,6 +88,8 @@ function makeReplayLogger(): ReplayLogger { _logger.infoTick = (...args: unknown[]) => { coreLogger.info(PREFIX, ...args); if (_trace) { + // Wait a tick here to avoid race conditions for some initial logs + // which may be added before replay is initialized setTimeout(() => _addBreadcrumb(args[0]), 0); } };