From ec70b54a26e90b36b285f17de1c0409ee1e416fa Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 8 Mar 2023 13:35:24 +0000 Subject: [PATCH 1/2] fix: Make redux integration be configurable via `normalizeDepth` --- packages/react/src/redux.ts | 20 +++++++++- packages/utils/src/normalize.ts | 15 ++++++-- packages/utils/test/normalize.test.ts | 54 +++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/packages/react/src/redux.ts b/packages/react/src/redux.ts index 5ef64a3d2522..eeecd7290d68 100644 --- a/packages/react/src/redux.ts +++ b/packages/react/src/redux.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { configureScope } from '@sentry/browser'; +import { configureScope, getCurrentHub } from '@sentry/browser'; import type { Scope } from '@sentry/types'; +import { addNonEnumerableProperty } from '@sentry/utils'; interface Action { type: T; @@ -105,7 +106,22 @@ function createReduxEnhancer(enhancerOptions?: Partial): /* Set latest state to scope */ const transformedState = options.stateTransformer(newState); if (typeof transformedState !== 'undefined' && transformedState !== null) { - scope.setContext('state', { state: { type: 'redux', value: transformedState } }); + const client = getCurrentHub().getClient(); + const options = client && client.getOptions(); + const normalizationDepth = options && options.normalizeDepth; + + // Set the normalization depth of the redux state to the configured `normalizeDepth` option or a sane number as a fallback + addNonEnumerableProperty( + transformedState, + '__sentry_override_normalization_depth__', + normalizationDepth || 3, + ); + + // Make sure the state is always reached during normalization + const newStateContext = { state: { type: 'redux', value: transformedState } }; + addNonEnumerableProperty(newStateContext, '__sentry_override_normalization_depth__', 3); + + scope.setContext('state', newStateContext); } else { scope.setContext('state', null); } diff --git a/packages/utils/src/normalize.ts b/packages/utils/src/normalize.ts index b10f78f39e98..20e72e158caa 100644 --- a/packages/utils/src/normalize.ts +++ b/packages/utils/src/normalize.ts @@ -100,8 +100,17 @@ function visit( return value as ObjOrArray; } + // Do not normalize objects that we know have already been normalized. As a general rule, the + // "__sentry_skip_normalization__" property should only be used sparingly and only should only be set on objects that + // have already been normalized. + let overriddenDepth = depth; + + if ((value as ObjOrArray)['__sentry_override_normalization_depth__']) { + overriddenDepth = (value as ObjOrArray)['__sentry_override_normalization_depth__'] as number; + } + // We're also done if we've reached the max depth - if (depth === 0) { + if (overriddenDepth === 0) { // At this point we know `serialized` is a string of the form `"[object XXXX]"`. Clean it up so it's just `"[XXXX]"`. return stringified.replace('object ', ''); } @@ -117,7 +126,7 @@ function visit( try { const jsonValue = valueWithToJSON.toJSON(); // We need to normalize the return value of `.toJSON()` in case it has circular references - return visit('', jsonValue, depth - 1, maxProperties, memo); + return visit('', jsonValue, overriddenDepth - 1, maxProperties, memo); } catch (err) { // pass (The built-in `toJSON` failed, but we can still try to do it ourselves) } @@ -146,7 +155,7 @@ function visit( // Recursively visit all the child nodes const visitValue = visitable[visitKey]; - normalized[visitKey] = visit(visitKey, visitValue, depth - 1, maxProperties, memo); + normalized[visitKey] = visit(visitKey, visitValue, overriddenDepth - 1, maxProperties, memo); numAdded++; } diff --git a/packages/utils/test/normalize.test.ts b/packages/utils/test/normalize.test.ts index 1319805e9488..94676c1449da 100644 --- a/packages/utils/test/normalize.test.ts +++ b/packages/utils/test/normalize.test.ts @@ -582,4 +582,58 @@ describe('normalize()', () => { expect(result?.foo?.bar?.boo?.bam?.pow).not.toBe('poof'); }); }); + + describe('overrides normalization depth with a non-enumerable property __sentry_override_normalization_depth__', () => { + test('by increasing depth if it is higher', () => { + const normalizationTarget = { + foo: 'bar', + baz: 42, + obj: { + obj: { + obj: { + bestSmashCharacter: 'Cpt. Falcon', + }, + }, + }, + }; + + addNonEnumerableProperty(normalizationTarget, '__sentry_override_normalization_depth__', 3); + + const result = normalize(normalizationTarget, 1); + + expect(result).toEqual({ + baz: 42, + foo: 'bar', + obj: { + obj: { + obj: '[Object]', + }, + }, + }); + }); + + test('by decreasing depth if it is lower', () => { + const normalizationTarget = { + foo: 'bar', + baz: 42, + obj: { + obj: { + obj: { + bestSmashCharacter: 'Cpt. Falcon', + }, + }, + }, + }; + + addNonEnumerableProperty(normalizationTarget, '__sentry_override_normalization_depth__', 1); + + const result = normalize(normalizationTarget, 3); + + expect(result).toEqual({ + baz: 42, + foo: 'bar', + obj: '[Object]', + }); + }); + }); }); From ec78a028090adad5a53e2b23c8f34f5c761d6f5d Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 8 Mar 2023 15:20:15 +0000 Subject: [PATCH 2/2] adress review --- packages/react/src/redux.ts | 12 +++++------- packages/utils/src/normalize.ts | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/react/src/redux.ts b/packages/react/src/redux.ts index eeecd7290d68..5bac99dbe511 100644 --- a/packages/react/src/redux.ts +++ b/packages/react/src/redux.ts @@ -108,19 +108,17 @@ function createReduxEnhancer(enhancerOptions?: Partial): if (typeof transformedState !== 'undefined' && transformedState !== null) { const client = getCurrentHub().getClient(); const options = client && client.getOptions(); - const normalizationDepth = options && options.normalizeDepth; + const normalizationDepth = (options && options.normalizeDepth) || 3; // default state normalization depth to 3 // Set the normalization depth of the redux state to the configured `normalizeDepth` option or a sane number as a fallback + const newStateContext = { state: { type: 'redux', value: transformedState } }; addNonEnumerableProperty( - transformedState, + newStateContext, '__sentry_override_normalization_depth__', - normalizationDepth || 3, + 3 + // 3 layers for `state.value.transformedState` + normalizationDepth, // rest for the actual state ); - // Make sure the state is always reached during normalization - const newStateContext = { state: { type: 'redux', value: transformedState } }; - addNonEnumerableProperty(newStateContext, '__sentry_override_normalization_depth__', 3); - scope.setContext('state', newStateContext); } else { scope.setContext('state', null); diff --git a/packages/utils/src/normalize.ts b/packages/utils/src/normalize.ts index 20e72e158caa..508442c2d14e 100644 --- a/packages/utils/src/normalize.ts +++ b/packages/utils/src/normalize.ts @@ -105,7 +105,7 @@ function visit( // have already been normalized. let overriddenDepth = depth; - if ((value as ObjOrArray)['__sentry_override_normalization_depth__']) { + if (typeof (value as ObjOrArray)['__sentry_override_normalization_depth__'] === 'number') { overriddenDepth = (value as ObjOrArray)['__sentry_override_normalization_depth__'] as number; }