Skip to content

Commit 68061b9

Browse files
committed
ref(core): Refactor core integrations to functional style
Also make a small adjustment to the legacy converter util to ensure `setupOnce()` is callable with or without arguments, to remain stable.
1 parent d8eba90 commit 68061b9

File tree

5 files changed

+192
-244
lines changed

5 files changed

+192
-244
lines changed

packages/core/src/integration.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
import type { Client, Event, EventHint, Integration, IntegrationClass, IntegrationFn, Options } from '@sentry/types';
1+
import type {
2+
Client,
3+
Event,
4+
EventHint,
5+
EventProcessor,
6+
Hub,
7+
Integration,
8+
IntegrationClass,
9+
IntegrationFn,
10+
Options,
11+
} from '@sentry/types';
212
import { arrayify, logger } from '@sentry/utils';
313

414
import { DEBUG_BUILD } from './debug-build';
@@ -156,6 +166,12 @@ function findIndex<T>(arr: T[], callback: (item: T) => boolean): number {
156166
return -1;
157167
}
158168

169+
// We want to ensure that any Integration Class generated by `convertIntegrationFnToClass`
170+
// can be called with or without setupOnce arguments.
171+
interface IntegrationWithoutSetupOnce extends Integration {
172+
setupOnce: (addGlobalEventProcessor?: (callback: EventProcessor) => void, getCurrentHub?: () => Hub) => void;
173+
}
174+
159175
/**
160176
* Convert a new integration function to the legacy class syntax.
161177
* In v8, we can remove this and instead export the integration functions directly.
@@ -165,7 +181,7 @@ function findIndex<T>(arr: T[], callback: (item: T) => boolean): number {
165181
export function convertIntegrationFnToClass<Fn extends IntegrationFn>(
166182
name: string,
167183
fn: Fn,
168-
): IntegrationClass<Integration> {
184+
): IntegrationClass<IntegrationWithoutSetupOnce> {
169185
return Object.assign(
170186
// eslint-disable-next-line @typescript-eslint/no-explicit-any
171187
function ConvertedIntegration(...rest: any[]) {
@@ -176,5 +192,5 @@ export function convertIntegrationFnToClass<Fn extends IntegrationFn>(
176192
};
177193
},
178194
{ id: name },
179-
) as unknown as IntegrationClass<Integration>;
195+
) as unknown as IntegrationClass<IntegrationWithoutSetupOnce>;
180196
}
Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,33 @@
1-
import type { Integration, WrappedFunction } from '@sentry/types';
1+
import type { IntegrationFn, WrappedFunction } from '@sentry/types';
22
import { getOriginalFunction } from '@sentry/utils';
3+
import { convertIntegrationFnToClass } from '../integration';
34

45
let originalFunctionToString: () => void;
56

6-
/** Patch toString calls to return proper name for wrapped functions */
7-
export class FunctionToString implements Integration {
8-
/**
9-
* @inheritDoc
10-
*/
11-
public static id: string = 'FunctionToString';
12-
13-
/**
14-
* @inheritDoc
15-
*/
16-
public name: string;
7+
const INTEGRATION_NAME = 'FunctionToString';
178

18-
public constructor() {
19-
this.name = FunctionToString.id;
20-
}
9+
const functionToStringIntegration: IntegrationFn = () => {
10+
return {
11+
name: INTEGRATION_NAME,
12+
setupOnce() {
13+
// eslint-disable-next-line @typescript-eslint/unbound-method
14+
originalFunctionToString = Function.prototype.toString;
2115

22-
/**
23-
* @inheritDoc
24-
*/
25-
public setupOnce(): void {
26-
// eslint-disable-next-line @typescript-eslint/unbound-method
27-
originalFunctionToString = Function.prototype.toString;
16+
// intrinsics (like Function.prototype) might be immutable in some environments
17+
// e.g. Node with --frozen-intrinsics, XS (an embedded JavaScript engine) or SES (a JavaScript proposal)
18+
try {
19+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
20+
Function.prototype.toString = function (this: WrappedFunction, ...args: any[]): string {
21+
const context = getOriginalFunction(this) || this;
22+
return originalFunctionToString.apply(context, args);
23+
};
24+
} catch {
25+
// ignore errors here, just don't patch this
26+
}
27+
},
28+
};
29+
};
2830

29-
// intrinsics (like Function.prototype) might be immutable in some environments
30-
// e.g. Node with --frozen-intrinsics, XS (an embedded JavaScript engine) or SES (a JavaScript proposal)
31-
try {
32-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
33-
Function.prototype.toString = function (this: WrappedFunction, ...args: any[]): string {
34-
const context = getOriginalFunction(this) || this;
35-
return originalFunctionToString.apply(context, args);
36-
};
37-
} catch {
38-
// ignore errors here, just don't patch this
39-
}
40-
}
41-
}
31+
/** Patch toString calls to return proper name for wrapped functions */
32+
// eslint-disable-next-line deprecation/deprecation
33+
export const FunctionToString = convertIntegrationFnToClass(INTEGRATION_NAME, functionToStringIntegration);
Lines changed: 33 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,39 @@
1-
import type { Client, Event, EventHint, Integration } from '@sentry/types';
1+
import type { IntegrationFn } from '@sentry/types';
22
import { applyAggregateErrorsToEvent, exceptionFromError } from '@sentry/utils';
3+
import { convertIntegrationFnToClass } from '../integration';
4+
5+
interface LinkedErrorsOptions {
6+
key?: string;
7+
limit?: number;
8+
}
39

410
const DEFAULT_KEY = 'cause';
511
const DEFAULT_LIMIT = 5;
612

7-
/** Adds SDK info to an event. */
8-
export class LinkedErrors implements Integration {
9-
/**
10-
* @inheritDoc
11-
*/
12-
public static id: string = 'LinkedErrors';
13-
14-
/**
15-
* @inheritDoc
16-
*/
17-
public readonly name: string;
18-
19-
/**
20-
* @inheritDoc
21-
*/
22-
private readonly _key: string;
23-
24-
/**
25-
* @inheritDoc
26-
*/
27-
private readonly _limit: number;
13+
const INTEGRATION_NAME = 'LinkedErrors';
14+
15+
const linkedErrorsIntegration: IntegrationFn = (options: LinkedErrorsOptions = {}) => {
16+
const limit = options.limit || DEFAULT_LIMIT;
17+
const key = options.key || DEFAULT_KEY;
18+
19+
return {
20+
name: INTEGRATION_NAME,
21+
preprocessEvent(event, hint, client) {
22+
const options = client.getOptions();
23+
24+
applyAggregateErrorsToEvent(
25+
exceptionFromError,
26+
options.stackParser,
27+
options.maxValueLength,
28+
key,
29+
limit,
30+
event,
31+
hint,
32+
);
33+
},
34+
};
35+
};
2836

29-
/**
30-
* @inheritDoc
31-
*/
32-
public constructor(options: { key?: string; limit?: number } = {}) {
33-
this._key = options.key || DEFAULT_KEY;
34-
this._limit = options.limit || DEFAULT_LIMIT;
35-
this.name = LinkedErrors.id;
36-
}
37-
38-
/** @inheritdoc */
39-
public setupOnce(): void {
40-
// noop
41-
}
42-
43-
/**
44-
* @inheritDoc
45-
*/
46-
public preprocessEvent(event: Event, hint: EventHint | undefined, client: Client): void {
47-
const options = client.getOptions();
48-
49-
applyAggregateErrorsToEvent(
50-
exceptionFromError,
51-
options.stackParser,
52-
options.maxValueLength,
53-
this._key,
54-
this._limit,
55-
event,
56-
hint,
57-
);
58-
}
59-
}
37+
/** Adds SDK info to an event. */
38+
// eslint-disable-next-line deprecation/deprecation
39+
export const LinkedErrors = convertIntegrationFnToClass(INTEGRATION_NAME, linkedErrorsIntegration);
Lines changed: 37 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,42 @@
1-
import type { Client, Event, EventItem, EventProcessor, Hub, Integration } from '@sentry/types';
1+
import type { Event, EventItem, IntegrationFn } from '@sentry/types';
22
import { forEachEnvelopeItem } from '@sentry/utils';
3+
import { convertIntegrationFnToClass } from '../integration';
34

45
import { addMetadataToStackFrames, stripMetadataFromStackFrames } from '../metadata';
56

7+
const INTEGRATION_NAME = 'ModuleMetadata';
8+
9+
const moduleMetadataIntegration: IntegrationFn = () => {
10+
return {
11+
name: INTEGRATION_NAME,
12+
setup(client) {
13+
if (typeof client.on !== 'function') {
14+
return;
15+
}
16+
17+
// We need to strip metadata from stack frames before sending them to Sentry since these are client side only.
18+
client.on('beforeEnvelope', envelope => {
19+
forEachEnvelopeItem(envelope, (item, type) => {
20+
if (type === 'event') {
21+
const event = Array.isArray(item) ? (item as EventItem)[1] : undefined;
22+
23+
if (event) {
24+
stripMetadataFromStackFrames(event);
25+
item[1] = event;
26+
}
27+
}
28+
});
29+
});
30+
},
31+
32+
processEvent(event, _hint, client) {
33+
const stackParser = client.getOptions().stackParser;
34+
addMetadataToStackFrames(stackParser, event);
35+
return event;
36+
},
37+
};
38+
};
39+
640
/**
741
* Adds module metadata to stack frames.
842
*
@@ -12,53 +46,5 @@ import { addMetadataToStackFrames, stripMetadataFromStackFrames } from '../metad
1246
* under the `module_metadata` property. This can be used to help in tagging or routing of events from different teams
1347
* our sources
1448
*/
15-
export class ModuleMetadata implements Integration {
16-
/*
17-
* @inheritDoc
18-
*/
19-
public static id: string = 'ModuleMetadata';
20-
21-
/**
22-
* @inheritDoc
23-
*/
24-
public name: string;
25-
26-
public constructor() {
27-
this.name = ModuleMetadata.id;
28-
}
29-
30-
/**
31-
* @inheritDoc
32-
*/
33-
public setupOnce(_addGlobalEventProcessor: (processor: EventProcessor) => void, _getCurrentHub: () => Hub): void {
34-
// noop
35-
}
36-
37-
/** @inheritDoc */
38-
public setup(client: Client): void {
39-
if (typeof client.on !== 'function') {
40-
return;
41-
}
42-
43-
// We need to strip metadata from stack frames before sending them to Sentry since these are client side only.
44-
client.on('beforeEnvelope', envelope => {
45-
forEachEnvelopeItem(envelope, (item, type) => {
46-
if (type === 'event') {
47-
const event = Array.isArray(item) ? (item as EventItem)[1] : undefined;
48-
49-
if (event) {
50-
stripMetadataFromStackFrames(event);
51-
item[1] = event;
52-
}
53-
}
54-
});
55-
});
56-
}
57-
58-
/** @inheritDoc */
59-
public processEvent(event: Event, _hint: unknown, client: Client): Event {
60-
const stackParser = client.getOptions().stackParser;
61-
addMetadataToStackFrames(stackParser, event);
62-
return event;
63-
}
64-
}
49+
// eslint-disable-next-line deprecation/deprecation
50+
export const ModuleMetadata = convertIntegrationFnToClass(INTEGRATION_NAME, moduleMetadataIntegration);

0 commit comments

Comments
 (0)