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

ref(core): Refactor core integrations to functional style #9916

Merged
merged 2 commits into from
Dec 20, 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
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