feat(editor): Improve Sentry ignore definitions for class instance types (no-changelog) (#11208)

Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
Alex Grozav 2024-10-10 17:21:58 +03:00 committed by GitHub
parent 6c6a8efdea
commit 3d2fbcfd93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 109 additions and 23 deletions

View file

@ -252,6 +252,7 @@ export class Server extends AbstractServer {
JSON.stringify({ JSON.stringify({
dsn: this.globalConfig.sentry.frontendDsn, dsn: this.globalConfig.sentry.frontendDsn,
environment: process.env.ENVIRONMENT || 'development', environment: process.env.ENVIRONMENT || 'development',
serverName: process.env.DEPLOYMENT_NAME,
release: N8N_VERSION, release: N8N_VERSION,
}), }),
); );

View file

@ -1,5 +1,4 @@
import { createApp } from 'vue'; import { createApp } from 'vue';
import * as Sentry from '@sentry/vue';
import '@vue-flow/core/dist/style.css'; import '@vue-flow/core/dist/style.css';
import '@vue-flow/core/dist/theme-default.css'; import '@vue-flow/core/dist/theme-default.css';
@ -30,32 +29,13 @@ import { FontAwesomePlugin } from './plugins/icons';
import { createPinia, PiniaVuePlugin } from 'pinia'; import { createPinia, PiniaVuePlugin } from 'pinia';
import { JsPlumbPlugin } from '@/plugins/jsplumb'; import { JsPlumbPlugin } from '@/plugins/jsplumb';
import { ChartJSPlugin } from '@/plugins/chartjs'; import { ChartJSPlugin } from '@/plugins/chartjs';
import { AxiosError } from 'axios'; import { SentryPlugin } from '@/plugins/sentry';
const pinia = createPinia(); const pinia = createPinia();
const app = createApp(App); const app = createApp(App);
if (window.sentry?.dsn) { app.use(SentryPlugin);
const { dsn, release, environment } = window.sentry;
Sentry.init({
app,
dsn,
release,
environment,
beforeSend(event, { originalException }) {
if (
!originalException ||
originalException instanceof AxiosError ||
(originalException instanceof Error && originalException.message.includes('ResizeObserver'))
) {
return null;
}
return event;
},
});
}
app.use(TelemetryPlugin); app.use(TelemetryPlugin);
app.use(PiniaVuePlugin); app.use(PiniaVuePlugin);
app.use(I18nPlugin); app.use(I18nPlugin);

View file

@ -0,0 +1,46 @@
import type * as Sentry from '@sentry/vue';
import { beforeSend } from '@/plugins/sentry';
import { AxiosError } from 'axios';
import { ResponseError } from '@/utils/apiUtils';
function createErrorEvent(): Sentry.ErrorEvent {
return {} as Sentry.ErrorEvent;
}
describe('beforeSend', () => {
it('should return null when originalException is undefined', () => {
const event = createErrorEvent();
const hint = { originalException: undefined };
expect(beforeSend(event, hint)).toBeNull();
});
it('should return null when originalException matches ignoredErrors by instance and message', () => {
const event = createErrorEvent();
const hint = { originalException: new ResponseError("Can't connect to n8n.") };
expect(beforeSend(event, hint)).toBeNull();
});
it('should return null when originalException matches ignoredErrors by instance and message regex', () => {
const event = createErrorEvent();
const hint = { originalException: new ResponseError('ECONNREFUSED') };
expect(beforeSend(event, hint)).toBeNull();
});
it('should return null when originalException matches ignoredErrors by instance only', () => {
const event = createErrorEvent();
const hint = { originalException: new AxiosError() };
expect(beforeSend(event, hint)).toBeNull();
});
it('should return null when originalException matches ignoredErrors by instance and message regex (ResizeObserver)', () => {
const event = createErrorEvent();
const hint = { originalException: new Error('ResizeObserver loop limit exceeded') };
expect(beforeSend(event, hint)).toBeNull();
});
it('should return event when originalException does not match any ignoredErrors', () => {
const event = createErrorEvent();
const hint = { originalException: new Error('Some other error') };
expect(beforeSend(event, hint)).toEqual(event);
});
});

View file

@ -0,0 +1,59 @@
import type { Plugin } from 'vue';
import { AxiosError } from 'axios';
import { ResponseError } from '@/utils/apiUtils';
import * as Sentry from '@sentry/vue';
const ignoredErrors = [
{ instanceof: AxiosError },
{ instanceof: ResponseError, message: /ECONNREFUSED/ },
{ instanceof: ResponseError, message: "Can't connect to n8n." },
{ instanceof: Error, message: /ResizeObserver/ },
] as const;
export function beforeSend(event: Sentry.ErrorEvent, { originalException }: Sentry.EventHint) {
if (
!originalException ||
ignoredErrors.some((entry) => {
const typeMatch = originalException instanceof entry.instanceof;
if (!typeMatch) {
return false;
}
if ('message' in entry) {
if (entry.message instanceof RegExp) {
return entry.message.test(originalException.message ?? '');
} else {
return originalException.message === entry.message;
}
}
return true;
})
) {
return null;
}
return event;
}
export const SentryPlugin: Plugin = {
install: (app) => {
if (!window.sentry?.dsn) {
return;
}
const { dsn, release, environment, serverName } = window.sentry;
Sentry.init({
app,
dsn,
release,
environment,
beforeSend,
});
if (serverName) {
Sentry.setTag('server_name', serverName);
}
},
};

View file

@ -18,7 +18,7 @@ declare global {
interface Window { interface Window {
BASE_PATH: string; BASE_PATH: string;
REST_ENDPOINT: string; REST_ENDPOINT: string;
sentry?: { dsn?: string; environment: string; release: string }; sentry?: { dsn?: string; environment: string; release: string; serverName?: string };
n8nExternalHooks?: PartialDeep<ExternalHooks>; n8nExternalHooks?: PartialDeep<ExternalHooks>;
preventNodeViewBeforeUnload?: boolean; preventNodeViewBeforeUnload?: boolean;
maxPinnedDataSize?: number; maxPinnedDataSize?: number;