Skip to content

Commit

Permalink
ref(core): Refactor core integrations to functional style (#9916)
Browse files Browse the repository at this point in the history
Also make a small adjustment to the legacy converter util to ensure
`setupOnce()` is callable with or without arguments, to remain stable.
  • Loading branch information
mydea authored Dec 20, 2023
1 parent dc76369 commit 74a12d9
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 252 deletions.
24 changes: 21 additions & 3 deletions packages/core/src/integration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import type { Client, Event, EventHint, Integration, IntegrationClass, IntegrationFn, Options } from '@sentry/types';
import type {
Client,
Event,
EventHint,
EventProcessor,
Hub,
Integration,
IntegrationClass,
IntegrationFn,
Options,
} from '@sentry/types';
import { arrayify, logger } from '@sentry/utils';

import { DEBUG_BUILD } from './debug-build';
Expand Down Expand Up @@ -165,7 +175,11 @@ function findIndex<T>(arr: T[], callback: (item: T) => boolean): number {
export function convertIntegrationFnToClass<Fn extends IntegrationFn>(
name: string,
fn: Fn,
): IntegrationClass<Integration> {
): IntegrationClass<
Integration & {
setupOnce: (addGlobalEventProcessor?: (callback: EventProcessor) => void, getCurrentHub?: () => Hub) => void;
}
> {
return Object.assign(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function ConvertedIntegration(...rest: any[]) {
Expand All @@ -176,5 +190,9 @@ export function convertIntegrationFnToClass<Fn extends IntegrationFn>(
};
},
{ id: name },
) as unknown as IntegrationClass<Integration>;
) as unknown as IntegrationClass<
Integration & {
setupOnce: (addGlobalEventProcessor?: (callback: EventProcessor) => void, getCurrentHub?: () => Hub) => void;
}
>;
}
60 changes: 26 additions & 34 deletions packages/core/src/integrations/functiontostring.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,33 @@
import type { Integration, WrappedFunction } from '@sentry/types';
import type { IntegrationFn, WrappedFunction } from '@sentry/types';
import { getOriginalFunction } from '@sentry/utils';
import { convertIntegrationFnToClass } from '../integration';

let originalFunctionToString: () => void;

/** Patch toString calls to return proper name for wrapped functions */
export class FunctionToString implements Integration {
/**
* @inheritDoc
*/
public static id: string = 'FunctionToString';

/**
* @inheritDoc
*/
public name: string;
const INTEGRATION_NAME = 'FunctionToString';

public constructor() {
this.name = FunctionToString.id;
}
const functionToStringIntegration: IntegrationFn = () => {
return {
name: INTEGRATION_NAME,
setupOnce() {
// eslint-disable-next-line @typescript-eslint/unbound-method
originalFunctionToString = Function.prototype.toString;

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

// intrinsics (like Function.prototype) might be immutable in some environments
// e.g. Node with --frozen-intrinsics, XS (an embedded JavaScript engine) or SES (a JavaScript proposal)
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Function.prototype.toString = function (this: WrappedFunction, ...args: any[]): string {
const context = getOriginalFunction(this) || this;
return originalFunctionToString.apply(context, args);
};
} catch {
// ignore errors here, just don't patch this
}
}
}
/** Patch toString calls to return proper name for wrapped functions */
// eslint-disable-next-line deprecation/deprecation
export const FunctionToString = convertIntegrationFnToClass(INTEGRATION_NAME, functionToStringIntegration);
86 changes: 33 additions & 53 deletions packages/core/src/integrations/linkederrors.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,39 @@
import type { Client, Event, EventHint, Integration } from '@sentry/types';
import type { IntegrationFn } from '@sentry/types';
import { applyAggregateErrorsToEvent, exceptionFromError } from '@sentry/utils';
import { convertIntegrationFnToClass } from '../integration';

interface LinkedErrorsOptions {
key?: string;
limit?: number;
}

const DEFAULT_KEY = 'cause';
const DEFAULT_LIMIT = 5;

/** Adds SDK info to an event. */
export class LinkedErrors implements Integration {
/**
* @inheritDoc
*/
public static id: string = 'LinkedErrors';

/**
* @inheritDoc
*/
public readonly name: string;

/**
* @inheritDoc
*/
private readonly _key: string;

/**
* @inheritDoc
*/
private readonly _limit: number;
const INTEGRATION_NAME = 'LinkedErrors';

const linkedErrorsIntegration: IntegrationFn = (options: LinkedErrorsOptions = {}) => {
const limit = options.limit || DEFAULT_LIMIT;
const key = options.key || DEFAULT_KEY;

return {
name: INTEGRATION_NAME,
preprocessEvent(event, hint, client) {
const options = client.getOptions();

applyAggregateErrorsToEvent(
exceptionFromError,
options.stackParser,
options.maxValueLength,
key,
limit,
event,
hint,
);
},
};
};

/**
* @inheritDoc
*/
public constructor(options: { key?: string; limit?: number } = {}) {
this._key = options.key || DEFAULT_KEY;
this._limit = options.limit || DEFAULT_LIMIT;
this.name = LinkedErrors.id;
}

/** @inheritdoc */
public setupOnce(): void {
// noop
}

/**
* @inheritDoc
*/
public preprocessEvent(event: Event, hint: EventHint | undefined, client: Client): void {
const options = client.getOptions();

applyAggregateErrorsToEvent(
exceptionFromError,
options.stackParser,
options.maxValueLength,
this._key,
this._limit,
event,
hint,
);
}
}
/** Adds SDK info to an event. */
// eslint-disable-next-line deprecation/deprecation
export const LinkedErrors = convertIntegrationFnToClass(INTEGRATION_NAME, linkedErrorsIntegration);
88 changes: 37 additions & 51 deletions packages/core/src/integrations/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,42 @@
import type { Client, Event, EventItem, EventProcessor, Hub, Integration } from '@sentry/types';
import type { Event, EventItem, IntegrationFn } from '@sentry/types';
import { forEachEnvelopeItem } from '@sentry/utils';
import { convertIntegrationFnToClass } from '../integration';

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

const INTEGRATION_NAME = 'ModuleMetadata';

const moduleMetadataIntegration: IntegrationFn = () => {
return {
name: INTEGRATION_NAME,
setup(client) {
if (typeof client.on !== 'function') {
return;
}

// We need to strip metadata from stack frames before sending them to Sentry since these are client side only.
client.on('beforeEnvelope', envelope => {
forEachEnvelopeItem(envelope, (item, type) => {
if (type === 'event') {
const event = Array.isArray(item) ? (item as EventItem)[1] : undefined;

if (event) {
stripMetadataFromStackFrames(event);
item[1] = event;
}
}
});
});
},

processEvent(event, _hint, client) {
const stackParser = client.getOptions().stackParser;
addMetadataToStackFrames(stackParser, event);
return event;
},
};
};

/**
* Adds module metadata to stack frames.
*
Expand All @@ -12,53 +46,5 @@ import { addMetadataToStackFrames, stripMetadataFromStackFrames } from '../metad
* under the `module_metadata` property. This can be used to help in tagging or routing of events from different teams
* our sources
*/
export class ModuleMetadata implements Integration {
/*
* @inheritDoc
*/
public static id: string = 'ModuleMetadata';

/**
* @inheritDoc
*/
public name: string;

public constructor() {
this.name = ModuleMetadata.id;
}

/**
* @inheritDoc
*/
public setupOnce(_addGlobalEventProcessor: (processor: EventProcessor) => void, _getCurrentHub: () => Hub): void {
// noop
}

/** @inheritDoc */
public setup(client: Client): void {
if (typeof client.on !== 'function') {
return;
}

// We need to strip metadata from stack frames before sending them to Sentry since these are client side only.
client.on('beforeEnvelope', envelope => {
forEachEnvelopeItem(envelope, (item, type) => {
if (type === 'event') {
const event = Array.isArray(item) ? (item as EventItem)[1] : undefined;

if (event) {
stripMetadataFromStackFrames(event);
item[1] = event;
}
}
});
});
}

/** @inheritDoc */
public processEvent(event: Event, _hint: unknown, client: Client): Event {
const stackParser = client.getOptions().stackParser;
addMetadataToStackFrames(stackParser, event);
return event;
}
}
// eslint-disable-next-line deprecation/deprecation
export const ModuleMetadata = convertIntegrationFnToClass(INTEGRATION_NAME, moduleMetadataIntegration);
Loading

0 comments on commit 74a12d9

Please sign in to comment.