Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Limit normalize edge nodes #4687

Closed
wants to merge 2 commits into from
Closed

Conversation

timfish
Copy link
Collaborator

@timfish timfish commented Mar 5, 2022

I have been reviewing normalize and walk as part of #4579.

Looking through the recent PRs from @lobsterkatie it looks like most of the issues have already been sorted!

The last remaining thing I wanted to improve was limiting the overall work normalisation/serialisation has to do if users inadvertently log large objects. The existing implementation only considers depth but this can still result in huge output.

For example, the following will result in a huge serialised event, even with a normalisation depth set to 3:

const smallArray = new Array(1_000).fill('a');
const bigArray = new Array(10_000).fill(smallArrar);
console.log('my data', bigArray)

This PR moves walk inside normalize which allows counting enge nodes and bailing out when the limit is reached.

This means this:

JSON.stringify(someObj, walk)

now becomes:

JSON.stringify(normalize(someObj))

Alternatives

This PR halts object/array properties output when the limit is reached which favours properties that are encountered earlier and this may not be desirable.

An alternative would be to limit individual objects/arrays to a maximum number of properties/elements.

@@ -390,7 +389,7 @@ export function normalize(input: any, depth?: number): any {
* eg. `Non-error exception captured with keys: foo, bar, baz`
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function extractExceptionKeysForMessage(exception: any, maxLength: number = 40): string {
export function extractExceptionKeysForMessage(exception: unknown, maxLength: number = 40): string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated change, let's move into another PR?

return serializeValue(value);
}
// If value implements `toJSON` method, call it and return early
if (typeof value?.toJSON === 'function') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunately optional chaining is less bundle size efficient.

Suggested change
if (typeof value?.toJSON === 'function') {
if (value != null && typeof value.toJSON === 'function') {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we just early return if value is undefined/null?

// Create source that we will use for the next iteration. It will either be an objectified error object (`Error` type
// with extracted key:value pairs) or the input itself.
const source = getWalkSource(value);
// Create an accumulator that will act as a parent for all future itterations of that branch
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Create an accumulator that will act as a parent for all future itterations of that branch
// Create an accumulator that will act as a parent for all future iterations of that branch

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function walk(key: string, value: any, depth: number = +Infinity, memo: MemoFunc = memoBuilder()): any {
const [memoize, unmemoize] = memo;
export function normalize(input: unknown, maxDepth: number = +Infinity, maxEdges: number = 10_000): any {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait 10_000 works :0

Does it get transpiled properly?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll probably have to make maxEdges configurable like maxDepth

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, all gets transpiled by tsc.

Make maxEdges configurable, or remove the default value?

Copy link
Member

@AbhiPrasad AbhiPrasad Mar 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should keep the default, but add an option similar to

normalizeDepth?: number;

Also I wonder if this change will be disruptive to existing customers in any way. Any reason to choose 10_000?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10k is just a rough guestimate for what will likely not be noticed by customers but still limit crazy large objects.

I think the alternative of limiting number of properties/elements per object/array might be less disruptive and output more useful serialised output. The issue I see with this edges approach is that it's hard to pick a maxEdges value and when that number is reached, nothing more is outputted. This includes skipping properties further up the object tree.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue I see with this edges approach is that it's hard to pick a maxEdges value and when that number is reached, nothing more is outputted

Yeah good observation. I think limiting number of properties/elements per object/array is also much more intuitive to understand - and adjusting it will be much more obvious.

if (!Object.prototype.hasOwnProperty.call(source, innerKey)) {
continue;
if (edges >= maxEdges) {
acc[innerKey] = '[Max Edges Reached...]';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe lets call this [MaxEdges ~] to make it consistent with [Circular ~]?

Copy link
Contributor

@kamilogorek kamilogorek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a small nitpick, but other than that looks good

@timfish
Copy link
Collaborator Author

timfish commented Mar 7, 2022

Closing in favour of #4689

@timfish timfish closed this Mar 7, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants