Skip to content

Commit 12a4fce

Browse files
authored
ref(deno): Refactor deno integrations to use functional syntax (#9929)
Refactors deno integrations to functional syntax.
1 parent eb9bc56 commit 12a4fce

File tree

5 files changed

+177
-212
lines changed

5 files changed

+177
-212
lines changed

packages/deno/src/integrations/context.ts

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import type { Event, EventProcessor, Integration } from '@sentry/types';
1+
import { convertIntegrationFnToClass } from '@sentry/core';
2+
import type { Event, IntegrationFn } from '@sentry/types';
3+
4+
const INTEGRATION_NAME = 'DenoContext';
25

36
function getOSName(): string {
47
switch (Deno.build.os) {
@@ -19,7 +22,7 @@ function getOSRelease(): string | undefined {
1922
: undefined;
2023
}
2124

22-
async function denoRuntime(event: Event): Promise<Event> {
25+
async function addDenoRuntimeContext(event: Event): Promise<Event> {
2326
event.contexts = {
2427
...{
2528
app: {
@@ -49,21 +52,15 @@ async function denoRuntime(event: Event): Promise<Event> {
4952
return event;
5053
}
5154

52-
/** Adds Electron context to events. */
53-
export class DenoContext implements Integration {
54-
/** @inheritDoc */
55-
public static id = 'DenoContext';
56-
57-
/** @inheritDoc */
58-
public name: string = DenoContext.id;
59-
60-
/** @inheritDoc */
61-
public setupOnce(_addGlobalEventProcessor: (callback: EventProcessor) => void): void {
62-
// noop
63-
}
55+
const denoContextIntegration: IntegrationFn = () => {
56+
return {
57+
name: INTEGRATION_NAME,
58+
processEvent(event) {
59+
return addDenoRuntimeContext(event);
60+
},
61+
};
62+
};
6463

65-
/** @inheritDoc */
66-
public processEvent(event: Event): Promise<Event> {
67-
return denoRuntime(event);
68-
}
69-
}
64+
/** Adds Deno context to events. */
65+
// eslint-disable-next-line deprecation/deprecation
66+
export const DenoContext = convertIntegrationFnToClass(INTEGRATION_NAME, denoContextIntegration);

packages/deno/src/integrations/contextlines.ts

Lines changed: 44 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import type { Event, EventProcessor, Integration, StackFrame } from '@sentry/types';
1+
import { convertIntegrationFnToClass } from '@sentry/core';
2+
import type { Event, IntegrationFn, StackFrame } from '@sentry/types';
23
import { LRUMap, addContextToFrame } from '@sentry/utils';
34

5+
const INTEGRATION_NAME = 'ContextLines';
46
const FILE_CONTENT_CACHE = new LRUMap<string, string | null>(100);
57
const DEFAULT_LINES_OF_CONTEXT = 7;
68

@@ -45,73 +47,54 @@ interface ContextLinesOptions {
4547
frameContextLines?: number;
4648
}
4749

48-
/** Add node modules / packages to the event */
49-
export class ContextLines implements Integration {
50-
/**
51-
* @inheritDoc
52-
*/
53-
public static id = 'ContextLines';
54-
55-
/**
56-
* @inheritDoc
57-
*/
58-
public name: string = ContextLines.id;
59-
60-
public constructor(private readonly _options: ContextLinesOptions = {}) {}
61-
62-
/** Get's the number of context lines to add */
63-
private get _contextLines(): number {
64-
return this._options.frameContextLines !== undefined ? this._options.frameContextLines : DEFAULT_LINES_OF_CONTEXT;
65-
}
66-
67-
/**
68-
* @inheritDoc
69-
*/
70-
public setupOnce(_addGlobalEventProcessor: (callback: EventProcessor) => void): void {
71-
// noop
72-
}
50+
const denoContextLinesIntegration: IntegrationFn = (options: ContextLinesOptions = {}) => {
51+
const contextLines = options.frameContextLines !== undefined ? options.frameContextLines : DEFAULT_LINES_OF_CONTEXT;
7352

74-
/** @inheritDoc */
75-
public processEvent(event: Event): Promise<Event> {
76-
return this.addSourceContext(event);
77-
}
53+
return {
54+
name: INTEGRATION_NAME,
55+
processEvent(event) {
56+
return addSourceContext(event, contextLines);
57+
},
58+
};
59+
};
7860

79-
/** Processes an event and adds context lines */
80-
public async addSourceContext(event: Event): Promise<Event> {
81-
if (this._contextLines > 0 && event.exception && event.exception.values) {
82-
for (const exception of event.exception.values) {
83-
if (exception.stacktrace && exception.stacktrace.frames) {
84-
await this.addSourceContextToFrames(exception.stacktrace.frames);
85-
}
61+
/** Add node modules / packages to the event */
62+
// eslint-disable-next-line deprecation/deprecation
63+
export const ContextLines = convertIntegrationFnToClass(INTEGRATION_NAME, denoContextLinesIntegration);
64+
65+
/** Processes an event and adds context lines */
66+
async function addSourceContext(event: Event, contextLines: number): Promise<Event> {
67+
if (contextLines > 0 && event.exception && event.exception.values) {
68+
for (const exception of event.exception.values) {
69+
if (exception.stacktrace && exception.stacktrace.frames) {
70+
await addSourceContextToFrames(exception.stacktrace.frames, contextLines);
8671
}
8772
}
88-
89-
return event;
9073
}
9174

92-
/** Adds context lines to frames */
93-
public async addSourceContextToFrames(frames: StackFrame[]): Promise<void> {
94-
const contextLines = this._contextLines;
95-
96-
for (const frame of frames) {
97-
// Only add context if we have a filename and it hasn't already been added
98-
if (frame.filename && frame.in_app && frame.context_line === undefined) {
99-
const permission = await Deno.permissions.query({
100-
name: 'read',
101-
path: frame.filename,
102-
});
103-
104-
if (permission.state == 'granted') {
105-
const sourceFile = await readSourceFile(frame.filename);
75+
return event;
76+
}
10677

107-
if (sourceFile) {
108-
try {
109-
const lines = sourceFile.split('\n');
110-
addContextToFrame(lines, frame, contextLines);
111-
} catch (_) {
112-
// anomaly, being defensive in case
113-
// unlikely to ever happen in practice but can definitely happen in theory
114-
}
78+
/** Adds context lines to frames */
79+
async function addSourceContextToFrames(frames: StackFrame[], contextLines: number): Promise<void> {
80+
for (const frame of frames) {
81+
// Only add context if we have a filename and it hasn't already been added
82+
if (frame.filename && frame.in_app && frame.context_line === undefined) {
83+
const permission = await Deno.permissions.query({
84+
name: 'read',
85+
path: frame.filename,
86+
});
87+
88+
if (permission.state == 'granted') {
89+
const sourceFile = await readSourceFile(frame.filename);
90+
91+
if (sourceFile) {
92+
try {
93+
const lines = sourceFile.split('\n');
94+
addContextToFrame(lines, frame, contextLines);
95+
} catch (_) {
96+
// anomaly, being defensive in case
97+
// unlikely to ever happen in practice but can definitely happen in theory
11598
}
11699
}
117100
}
Lines changed: 57 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,65 @@
1-
import { withMonitor } from '@sentry/core';
2-
import type { Integration } from '@sentry/types';
3-
import type { DenoClient } from '../client';
1+
import { convertIntegrationFnToClass, getClient, withMonitor } from '@sentry/core';
2+
import type { Client, IntegrationFn } from '@sentry/types';
43
import { parseScheduleToString } from './deno-cron-format';
54

65
type CronOptions = { backoffSchedule?: number[]; signal?: AbortSignal };
76
type CronFn = () => void | Promise<void>;
87
// Parameters<typeof Deno.cron> doesn't work well with the overloads 🤔
98
type CronParams = [string, string | Deno.CronSchedule, CronFn | CronOptions, CronFn | CronOptions | undefined];
109

10+
const INTEGRATION_NAME = 'DenoCron';
11+
12+
const SETUP_CLIENTS: Client[] = [];
13+
14+
const denoCronIntegration = (() => {
15+
return {
16+
name: INTEGRATION_NAME,
17+
setupOnce() {
18+
// eslint-disable-next-line deprecation/deprecation
19+
if (!Deno.cron) {
20+
// The cron API is not available in this Deno version use --unstable flag!
21+
return;
22+
}
23+
24+
// eslint-disable-next-line deprecation/deprecation
25+
Deno.cron = new Proxy(Deno.cron, {
26+
apply(target, thisArg, argArray: CronParams) {
27+
const [monitorSlug, schedule, opt1, opt2] = argArray;
28+
let options: CronOptions | undefined;
29+
let fn: CronFn;
30+
31+
if (typeof opt1 === 'function' && typeof opt2 !== 'function') {
32+
fn = opt1;
33+
options = opt2;
34+
} else if (typeof opt1 !== 'function' && typeof opt2 === 'function') {
35+
fn = opt2;
36+
options = opt1;
37+
}
38+
39+
async function cronCalled(): Promise<void> {
40+
if (SETUP_CLIENTS.includes(getClient() as Client)) {
41+
return;
42+
}
43+
44+
await withMonitor(monitorSlug, async () => fn(), {
45+
schedule: { type: 'crontab', value: parseScheduleToString(schedule) },
46+
// (minutes) so 12 hours - just a very high arbitrary number since we don't know the actual duration of the users cron job
47+
maxRuntime: 60 * 12,
48+
// Deno Deploy docs say that the cron job will be called within 1 minute of the scheduled time
49+
checkinMargin: 1,
50+
});
51+
}
52+
53+
return target.call(thisArg, monitorSlug, schedule, options || {}, cronCalled);
54+
},
55+
});
56+
},
57+
setup(client) {
58+
SETUP_CLIENTS.push(client);
59+
},
60+
};
61+
}) satisfies IntegrationFn;
62+
1163
/** Instruments Deno.cron to automatically capture cron check-ins */
12-
export class DenoCron implements Integration {
13-
/** @inheritDoc */
14-
public static id = 'DenoCron';
15-
16-
/** @inheritDoc */
17-
public name: string = DenoCron.id;
18-
19-
/** @inheritDoc */
20-
public setupOnce(): void {
21-
//
22-
}
23-
24-
/** @inheritDoc */
25-
public setup(): void {
26-
// eslint-disable-next-line deprecation/deprecation
27-
if (!Deno.cron) {
28-
// The cron API is not available in this Deno version use --unstable flag!
29-
return;
30-
}
31-
32-
// eslint-disable-next-line deprecation/deprecation
33-
Deno.cron = new Proxy(Deno.cron, {
34-
apply(target, thisArg, argArray: CronParams) {
35-
const [monitorSlug, schedule, opt1, opt2] = argArray;
36-
let options: CronOptions | undefined;
37-
let fn: CronFn;
38-
39-
if (typeof opt1 === 'function' && typeof opt2 !== 'function') {
40-
fn = opt1;
41-
options = opt2;
42-
} else if (typeof opt1 !== 'function' && typeof opt2 === 'function') {
43-
fn = opt2;
44-
options = opt1;
45-
}
46-
47-
async function cronCalled(): Promise<void> {
48-
await withMonitor(monitorSlug, async () => fn(), {
49-
schedule: { type: 'crontab', value: parseScheduleToString(schedule) },
50-
// (minutes) so 12 hours - just a very high arbitrary number since we don't know the actual duration of the users cron job
51-
maxRuntime: 60 * 12,
52-
// Deno Deploy docs say that the cron job will be called within 1 minute of the scheduled time
53-
checkinMargin: 1,
54-
});
55-
}
56-
57-
return target.call(thisArg, monitorSlug, schedule, options || {}, cronCalled);
58-
},
59-
});
60-
}
61-
}
64+
// eslint-disable-next-line deprecation/deprecation
65+
export const DenoCron = convertIntegrationFnToClass(INTEGRATION_NAME, denoCronIntegration);

packages/deno/src/integrations/globalhandlers.ts

Lines changed: 25 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,41 @@
11
import type { ServerRuntimeClient } from '@sentry/core';
2+
import { convertIntegrationFnToClass } from '@sentry/core';
23
import { captureEvent } from '@sentry/core';
34
import { getClient } from '@sentry/core';
45
import { flush } from '@sentry/core';
5-
import type { Client, Event, Integration, Primitive, StackParser } from '@sentry/types';
6+
import type { Client, Event, IntegrationFn, Primitive, StackParser } from '@sentry/types';
67
import { eventFromUnknownInput, isPrimitive } from '@sentry/utils';
78

89
type GlobalHandlersIntegrationsOptionKeys = 'error' | 'unhandledrejection';
910

10-
/** JSDoc */
1111
type GlobalHandlersIntegrations = Record<GlobalHandlersIntegrationsOptionKeys, boolean>;
1212

13+
const INTEGRATION_NAME = 'GlobalHandlers';
1314
let isExiting = false;
1415

15-
/** Global handlers */
16-
export class GlobalHandlers implements Integration {
17-
/**
18-
* @inheritDoc
19-
*/
20-
public static id = 'GlobalHandlers';
21-
22-
/**
23-
* @inheritDoc
24-
*/
25-
public name: string = GlobalHandlers.id;
26-
27-
/** JSDoc */
28-
private readonly _options: GlobalHandlersIntegrations;
29-
30-
/** JSDoc */
31-
public constructor(options?: GlobalHandlersIntegrations) {
32-
this._options = {
33-
error: true,
34-
unhandledrejection: true,
35-
...options,
36-
};
37-
}
38-
/**
39-
* @inheritDoc
40-
*/
41-
public setupOnce(): void {
42-
// noop
43-
}
16+
const globalHandlersIntegration: IntegrationFn = (options?: GlobalHandlersIntegrations) => {
17+
const _options = {
18+
error: true,
19+
unhandledrejection: true,
20+
...options,
21+
};
4422

45-
/** @inheritdoc */
46-
public setup(client: Client): void {
47-
if (this._options.error) {
48-
installGlobalErrorHandler(client);
49-
}
50-
if (this._options.unhandledrejection) {
51-
installGlobalUnhandledRejectionHandler(client);
52-
}
53-
}
54-
}
23+
return {
24+
name: INTEGRATION_NAME,
25+
setup(client) {
26+
if (_options.error) {
27+
installGlobalErrorHandler(client);
28+
}
29+
if (_options.unhandledrejection) {
30+
installGlobalUnhandledRejectionHandler(client);
31+
}
32+
},
33+
};
34+
};
35+
36+
/** Global handlers */
37+
// eslint-disable-next-line deprecation/deprecation
38+
export const GlobalHandlers = convertIntegrationFnToClass(INTEGRATION_NAME, globalHandlersIntegration);
5539

5640
function installGlobalErrorHandler(client: Client): void {
5741
globalThis.addEventListener('error', data => {

0 commit comments

Comments
 (0)