Skip to content

fix(utils): Update eventFromUnknownInput to avoid scope pollution & getCurrentHub #9868

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

Merged
merged 3 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/core/src/server-runtime-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { eventFromMessage, eventFromUnknownInput, logger, resolvedSyncPromise, u
import { BaseClient } from './baseclient';
import { createCheckInEnvelope } from './checkin';
import { DEBUG_BUILD } from './debug-build';
import { getCurrentHub } from './hub';
import { getClient } from './exports';
import type { Scope } from './scope';
import { SessionFlusher } from './sessionflusher';
import { addTracingExtensions, getDynamicSamplingContextFromClient } from './tracing';
Expand Down Expand Up @@ -50,7 +50,7 @@ export class ServerRuntimeClient<
* @inheritDoc
*/
public eventFromException(exception: unknown, hint?: EventHint): PromiseLike<Event> {
return resolvedSyncPromise(eventFromUnknownInput(getCurrentHub, this._options.stackParser, exception, hint));
return resolvedSyncPromise(eventFromUnknownInput(getClient(), this._options.stackParser, exception, hint));
}

/**
Expand Down
7 changes: 4 additions & 3 deletions packages/deno/src/integrations/globalhandlers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ServerRuntimeClient } from '@sentry/core';
import { flush, getCurrentHub } from '@sentry/core';
import { getClient, getCurrentHub, getCurrentScope } from '@sentry/core';
import { flush } from '@sentry/core';
import type { Event, Hub, Integration, Primitive, StackParser } from '@sentry/types';
import { eventFromUnknownInput, isPrimitive } from '@sentry/utils';

Expand Down Expand Up @@ -70,7 +71,7 @@ function installGlobalErrorHandler(): void {
const [hub, stackParser] = getHubAndOptions();
const { message, error } = data;

const event = eventFromUnknownInput(getCurrentHub, stackParser, error || message);
const event = eventFromUnknownInput(getClient(), stackParser, error || message);

event.level = 'fatal';

Expand Down Expand Up @@ -113,7 +114,7 @@ function installGlobalUnhandledRejectionHandler(): void {

const event = isPrimitive(error)
? eventFromRejectionWithPrimitive(error)
: eventFromUnknownInput(getCurrentHub, stackParser, error, undefined);
: eventFromUnknownInput(getClient(), stackParser, error, undefined);

event.level = 'fatal';

Expand Down
85 changes: 53 additions & 32 deletions packages/node/test/eventbuilders.test.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,51 @@
import type { Client } from '@sentry/types';
import type { Hub } from '@sentry/types';
import { eventFromUnknownInput } from '@sentry/utils';

import { Scope, defaultStackParser, getCurrentHub } from '../src';
import { defaultStackParser } from '../src';

const testScope = new Scope();

jest.mock('@sentry/core', () => {
const original = jest.requireActual('@sentry/core');
return {
...original,
getCurrentHub(): {
getClient(): Client;
getScope(): Scope;
} {
return {
getClient(): any {
return {
getOptions(): any {
return { normalizeDepth: 6 };
describe('eventFromUnknownInput', () => {
test('uses normalizeDepth from init options', () => {
const deepObject = {
a: {
b: {
c: {
d: {
e: {
f: {
g: 'foo',
},
},
},
};
},
getScope(): Scope {
return testScope;
},
},
};
},
};
});
},
};

afterEach(() => {
jest.resetAllMocks();
});
const client = {
getOptions(): any {
return { normalizeDepth: 6 };
},
} as any;
const event = eventFromUnknownInput(client, defaultStackParser, deepObject);

describe('eventFromUnknownInput', () => {
test('uses normalizeDepth from init options', () => {
const serializedObject = event.extra?.__serialized__;
expect(serializedObject).toBeDefined();
expect(serializedObject).toEqual({
a: {
b: {
c: {
d: {
e: {
f: '[Object]',
},
},
},
},
},
});
});

test('uses normalizeDepth from init options (passing getCurrentHub)', () => {
const deepObject = {
a: {
b: {
Expand All @@ -51,9 +62,19 @@ describe('eventFromUnknownInput', () => {
},
};

eventFromUnknownInput(getCurrentHub, defaultStackParser, deepObject);
const getCurrentHub = jest.fn(() => {
return {
getClient: () => ({
getOptions(): any {
return { normalizeDepth: 6 };
},
}),
} as unknown as Hub;
});

const event = eventFromUnknownInput(getCurrentHub, defaultStackParser, deepObject);

const serializedObject = (testScope as any)._extra.__serialized__;
const serializedObject = event.extra?.__serialized__;
expect(serializedObject).toBeDefined();
expect(serializedObject).toEqual({
a: {
Expand Down
20 changes: 15 additions & 5 deletions packages/utils/src/eventbuilder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type {
Client,
Event,
EventHint,
Exception,
Extras,
Hub,
Mechanism,
Severity,
Expand Down Expand Up @@ -61,14 +63,18 @@ function getMessageForObject(exception: object): string {

/**
* Builds and Event from a Exception
*
* TODO(v8): Remove getHub fallback
* @hidden
*/
export function eventFromUnknownInput(
getCurrentHub: () => Hub,
getHubOrClient: (() => Hub) | Client | undefined,
stackParser: StackParser,
exception: unknown,
hint?: EventHint,
): Event {
const client = typeof getHubOrClient === 'function' ? getHubOrClient().getClient() : getHubOrClient;

let ex: unknown = exception;
const providedMechanism: Mechanism | undefined =
hint && hint.data && (hint.data as { mechanism: Mechanism }).mechanism;
Expand All @@ -77,12 +83,12 @@ export function eventFromUnknownInput(
type: 'generic',
};

let extras: Extras | undefined;

if (!isError(exception)) {
if (isPlainObject(exception)) {
const hub = getCurrentHub();
const client = hub.getClient();
const normalizeDepth = client && client.getOptions().normalizeDepth;
hub.getScope().setExtra('__serialized__', normalizeToSize(exception, normalizeDepth));
extras = { ['__serialized__']: normalizeToSize(exception as Record<string, unknown>, normalizeDepth) };

const message = getMessageForObject(exception);
ex = (hint && hint.syntheticException) || new Error(message);
Expand All @@ -96,12 +102,16 @@ export function eventFromUnknownInput(
mechanism.synthetic = true;
}

const event = {
const event: Event = {
exception: {
values: [exceptionFromError(stackParser, ex as Error)],
},
};

if (extras) {
event.extra = extras;
}

addExceptionTypeValue(event, undefined, undefined);
addExceptionMechanism(event, mechanism);

Expand Down
5 changes: 5 additions & 0 deletions packages/utils/test/eventbuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,9 @@ describe('eventFromUnknownInput', () => {
const event = eventFromUnknownInput(getCurrentHub, stackParser, { foo: { bar: 'baz' }, message: 'Some message' });
expect(event.exception?.values?.[0].value).toBe('Some message');
});

test('passing client directly', () => {
const event = eventFromUnknownInput(undefined, stackParser, { foo: { bar: 'baz' }, prop: 1 });
expect(event.exception?.values?.[0].value).toBe('Object captured as exception with keys: foo, prop');
});
});