Skip to content

ref(node): Refactor LocalVariables integration to avoid setupOnce #9897

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 1 commit into from
Dec 19, 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/baseclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,14 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
/** Number of calls being processed */
protected _numProcessing: number;

protected _eventProcessors: EventProcessor[];

/** Holds flushable */
private _outcomes: { [key: string]: number };

// eslint-disable-next-line @typescript-eslint/ban-types
private _hooks: Record<string, Function[]>;

private _eventProcessors: EventProcessor[];

/**
* Initializes this client instance.
*
Expand Down
29 changes: 19 additions & 10 deletions packages/node/src/integrations/localvariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import type { Event, EventProcessor, Exception, Hub, Integration, StackFrame, StackParser } from '@sentry/types';
import { LRUMap, logger } from '@sentry/utils';
import type { Debugger, InspectorNotification, Runtime, Session } from 'inspector';
import type { NodeClient } from '../client';

import { NODE_VERSION } from '../nodeVersion';
import type { NodeClientOptions } from '../types';

type Variables = Record<string, unknown>;
type OnPauseEvent = InspectorNotification<Debugger.PausedEventDataType>;
Expand Down Expand Up @@ -332,6 +332,7 @@ export class LocalVariables implements Integration {

private readonly _cachedFrames: LRUMap<string, FrameVariables[]> = new LRUMap(20);
private _rateLimiter: RateLimitIncrement | undefined;
private _shouldProcessEvent = false;

public constructor(
private readonly _options: Options = {},
Expand All @@ -341,16 +342,15 @@ export class LocalVariables implements Integration {
/**
* @inheritDoc
*/
public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void {
this._setup(addGlobalEventProcessor, getCurrentHub().getClient()?.getOptions());
public setupOnce(_addGlobalEventProcessor: (callback: EventProcessor) => void, _getCurrentHub: () => Hub): void {
// noop
}

/** Setup in a way that's easier to call from tests */
private _setup(
addGlobalEventProcessor: (callback: EventProcessor) => void,
clientOptions: NodeClientOptions | undefined,
): void {
if (this._session && clientOptions?.includeLocalVariables) {
/** @inheritdoc */
public setup(client: NodeClient): void {
const clientOptions = client.getOptions();

if (this._session && clientOptions.includeLocalVariables) {
// Only setup this integration if the Node version is >= v18
// https://github.com/getsentry/sentry-javascript/issues/7697
const unsupportedNodeVersion = (NODE_VERSION.major || 0) < 18;
Expand Down Expand Up @@ -386,10 +386,19 @@ export class LocalVariables implements Integration {
);
}

addGlobalEventProcessor(async event => this._addLocalVariables(event));
this._shouldProcessEvent = true;
}
}

/** @inheritdoc */
public processEvent(event: Event): Event {
if (this._shouldProcessEvent) {
return this._addLocalVariables(event);
}

return event;
}

/**
* Handle the pause event
*/
Expand Down
68 changes: 30 additions & 38 deletions packages/node/test/integrations/localvariables.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { ClientOptions, EventProcessor } from '@sentry/types';
import type { LRUMap } from '@sentry/utils';
import type { Debugger, InspectorNotification } from 'inspector';

import { defaultStackParser } from '../../src';
import { NodeClient, defaultStackParser } from '../../src';
import type { DebugSession, FrameVariables } from '../../src/integrations/localvariables';
import { LocalVariables, createCallbackList, createRateLimiter } from '../../src/integrations/localvariables';
import { NODE_VERSION } from '../../src/nodeVersion';
Expand Down Expand Up @@ -52,7 +52,6 @@ class MockDebugSession implements DebugSession {

interface LocalVariablesPrivate {
_cachedFrames: LRUMap<string, FrameVariables[]>;
_setup(addGlobalEventProcessor: (callback: EventProcessor) => void, clientOptions: ClientOptions): void;
}

const exceptionEvent = {
Expand Down Expand Up @@ -154,8 +153,6 @@ const exceptionEvent100Frames = {

describeIf((NODE_VERSION.major || 0) >= 18)('LocalVariables', () => {
it('Adds local variables to stack frames', async () => {
expect.assertions(7);

const session = new MockDebugSession({
'-6224981551105448869.1.2': { name: 'tim' },
'-6224981551105448869.1.6': { arr: [1, 2, 3] },
Expand All @@ -164,13 +161,14 @@ describeIf((NODE_VERSION.major || 0) >= 18)('LocalVariables', () => {
const options = getDefaultNodeClientOptions({
stackParser: defaultStackParser,
includeLocalVariables: true,
integrations: [localVariables],
});

let eventProcessor: EventProcessor | undefined;
const client = new NodeClient(options);
client.setupIntegrations(true);

(localVariables as unknown as LocalVariablesPrivate)._setup(callback => {
eventProcessor = callback;
}, options);
const eventProcessors = client['_eventProcessors'];
const eventProcessor = eventProcessors.find(processor => processor.id === 'LocalVariables');

expect(eventProcessor).toBeDefined();

Expand All @@ -189,7 +187,7 @@ describeIf((NODE_VERSION.major || 0) >= 18)('LocalVariables', () => {
{ function: 'one', vars: { arr: [1, 2, 3] } },
]);

const event = await eventProcessor?.(
const event = await eventProcessor!(
{
event_id: '9cbf882ade9a415986632ac4e16918eb',
platform: 'node',
Expand Down Expand Up @@ -249,22 +247,16 @@ describeIf((NODE_VERSION.major || 0) >= 18)('LocalVariables', () => {
});

it('Only considers the first 5 frames', async () => {
expect.assertions(4);

const session = new MockDebugSession({});
const localVariables = new LocalVariables({}, session);
const options = getDefaultNodeClientOptions({
stackParser: defaultStackParser,
includeLocalVariables: true,
integrations: [localVariables],
});

let eventProcessor: EventProcessor | undefined;

(localVariables as unknown as LocalVariablesPrivate)._setup(callback => {
eventProcessor = callback;
}, options);

expect(eventProcessor).toBeDefined();
const client = new NodeClient(options);
client.setupIntegrations(true);

await session.runPause(exceptionEvent100Frames);

Expand All @@ -280,16 +272,16 @@ describeIf((NODE_VERSION.major || 0) >= 18)('LocalVariables', () => {
});

it('Should not lookup variables for non-exception reasons', async () => {
expect.assertions(1);

const session = new MockDebugSession({}, { getLocalVariables: true });
const localVariables = new LocalVariables({}, session);
const options = getDefaultNodeClientOptions({
stackParser: defaultStackParser,
includeLocalVariables: true,
integrations: [localVariables],
});

(localVariables as unknown as LocalVariablesPrivate)._setup(_ => {}, options);
const client = new NodeClient(options);
client.setupIntegrations(true);

const nonExceptionEvent = {
method: exceptionEvent.method,
Expand All @@ -302,43 +294,41 @@ describeIf((NODE_VERSION.major || 0) >= 18)('LocalVariables', () => {
});

it('Should not initialize when disabled', async () => {
expect.assertions(1);

const session = new MockDebugSession({}, { configureAndConnect: true });
const localVariables = new LocalVariables({}, session);
const options = getDefaultNodeClientOptions({
stackParser: defaultStackParser,
integrations: [localVariables],
});

let eventProcessor: EventProcessor | undefined;
const client = new NodeClient(options);
client.setupIntegrations(true);

(localVariables as unknown as LocalVariablesPrivate)._setup(callback => {
eventProcessor = callback;
}, options);
const eventProcessors = client['_eventProcessors'];
const eventProcessor = eventProcessors.find(processor => processor.id === 'LocalVariables');

expect(eventProcessor).toBeUndefined();
expect(eventProcessor).toBeDefined();
expect(localVariables['_shouldProcessEvent']).toBe(false);
});

it('Should not initialize when inspector not loaded', async () => {
expect.assertions(1);

const localVariables = new LocalVariables({}, undefined);
const options = getDefaultNodeClientOptions({
stackParser: defaultStackParser,
integrations: [localVariables],
});

let eventProcessor: EventProcessor | undefined;
const client = new NodeClient(options);
client.setupIntegrations(true);

(localVariables as unknown as LocalVariablesPrivate)._setup(callback => {
eventProcessor = callback;
}, options);
const eventProcessors = client['_eventProcessors'];
const eventProcessor = eventProcessors.find(processor => processor.id === 'LocalVariables');

expect(eventProcessor).toBeUndefined();
expect(eventProcessor).toBeDefined();
expect(localVariables['_shouldProcessEvent']).toBe(false);
});

it('Should cache identical uncaught exception events', async () => {
expect.assertions(1);

const session = new MockDebugSession({
'-6224981551105448869.1.2': { name: 'tim' },
'-6224981551105448869.1.6': { arr: [1, 2, 3] },
Expand All @@ -347,9 +337,11 @@ describeIf((NODE_VERSION.major || 0) >= 18)('LocalVariables', () => {
const options = getDefaultNodeClientOptions({
stackParser: defaultStackParser,
includeLocalVariables: true,
integrations: [localVariables],
});

(localVariables as unknown as LocalVariablesPrivate)._setup(_ => {}, options);
const client = new NodeClient(options);
client.setupIntegrations(true);

await session.runPause(exceptionEvent);
await session.runPause(exceptionEvent);
Expand Down