mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-14 08:34:07 -08:00
Rename to ErrorReporter and improve code
This commit is contained in:
parent
e795d0bae7
commit
4fceebf8c2
|
@ -35,10 +35,10 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@n8n/config": "workspace:*",
|
"@n8n/config": "workspace:*",
|
||||||
"acorn": "8.14.0",
|
|
||||||
"acorn-walk": "8.3.4",
|
|
||||||
"@sentry/integrations": "catalog:",
|
"@sentry/integrations": "catalog:",
|
||||||
"@sentry/node": "catalog:",
|
"@sentry/node": "catalog:",
|
||||||
|
"acorn": "8.14.0",
|
||||||
|
"acorn-walk": "8.3.4",
|
||||||
"n8n-core": "workspace:*",
|
"n8n-core": "workspace:*",
|
||||||
"n8n-workflow": "workspace:*",
|
"n8n-workflow": "workspace:*",
|
||||||
"nanoid": "^3.3.6",
|
"nanoid": "^3.3.6",
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import { ApplicationError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { ErrorReporter } from '../error-reporter';
|
||||||
|
|
||||||
|
describe('ErrorReporter', () => {
|
||||||
|
const errorReporting = new ErrorReporter(mock());
|
||||||
|
|
||||||
|
describe('beforeSend', () => {
|
||||||
|
it('should return null if originalException is an ApplicationError with level warning', () => {
|
||||||
|
const hint = { originalException: new ApplicationError('Test error', { level: 'warning' }) };
|
||||||
|
expect(errorReporting.beforeSend(mock(), hint)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return event if originalException is an ApplicationError with level error', () => {
|
||||||
|
const hint = { originalException: new ApplicationError('Test error', { level: 'error' }) };
|
||||||
|
expect(errorReporting.beforeSend(mock(), hint)).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null if originalException is an Error with a non-unique stack', () => {
|
||||||
|
const hint = { originalException: new Error('Test error') };
|
||||||
|
errorReporting.beforeSend(mock(), hint);
|
||||||
|
expect(errorReporting.beforeSend(mock(), hint)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return event if originalException is an Error with a unique stack', () => {
|
||||||
|
const hint = { originalException: new Error('Test error') };
|
||||||
|
expect(errorReporting.beforeSend(mock(), hint)).not.toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,5 +1,6 @@
|
||||||
import { RewriteFrames } from '@sentry/integrations';
|
import { RewriteFrames } from '@sentry/integrations';
|
||||||
import { init, setTag, captureException, close } from '@sentry/node';
|
import { init, setTag, captureException, close } from '@sentry/node';
|
||||||
|
import type { ErrorEvent, EventHint } from '@sentry/types';
|
||||||
import * as a from 'assert/strict';
|
import * as a from 'assert/strict';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
import { ApplicationError } from 'n8n-workflow';
|
import { ApplicationError } from 'n8n-workflow';
|
||||||
|
@ -9,9 +10,12 @@ import type { SentryConfig } from '@/config/sentry-config';
|
||||||
/**
|
/**
|
||||||
* Handles error reporting using Sentry
|
* Handles error reporting using Sentry
|
||||||
*/
|
*/
|
||||||
export class ErrorReporting {
|
export class ErrorReporter {
|
||||||
private isInitialized = false;
|
private isInitialized = false;
|
||||||
|
|
||||||
|
/** Hashes of error stack traces, to deduplicate error reports. */
|
||||||
|
private readonly seenErrors = new Set<string>();
|
||||||
|
|
||||||
private get dsn() {
|
private get dsn() {
|
||||||
return this.sentryConfig.sentryDsn;
|
return this.sentryConfig.sentryDsn;
|
||||||
}
|
}
|
||||||
|
@ -37,7 +41,8 @@ export class ErrorReporting {
|
||||||
'OnUnhandledRejection',
|
'OnUnhandledRejection',
|
||||||
'ContextLines',
|
'ContextLines',
|
||||||
];
|
];
|
||||||
const seenErrors = new Set<string>();
|
|
||||||
|
setTag('server_type', 'task_runner');
|
||||||
|
|
||||||
init({
|
init({
|
||||||
dsn: this.dsn,
|
dsn: this.dsn,
|
||||||
|
@ -46,37 +51,13 @@ export class ErrorReporting {
|
||||||
enableTracing: false,
|
enableTracing: false,
|
||||||
serverName: this.sentryConfig.deploymentName,
|
serverName: this.sentryConfig.deploymentName,
|
||||||
beforeBreadcrumb: () => null,
|
beforeBreadcrumb: () => null,
|
||||||
|
beforeSend: (event, hint) => this.beforeSend(event, hint),
|
||||||
integrations: (integrations) => [
|
integrations: (integrations) => [
|
||||||
...integrations.filter(({ name }) => enabledIntegrations.includes(name)),
|
...integrations.filter(({ name }) => enabledIntegrations.includes(name)),
|
||||||
new RewriteFrames({ root: process.cwd() }),
|
new RewriteFrames({ root: process.cwd() }),
|
||||||
],
|
],
|
||||||
async beforeSend(event, { originalException }) {
|
|
||||||
if (!originalException) return null;
|
|
||||||
|
|
||||||
if (originalException instanceof Promise) {
|
|
||||||
originalException = await originalException.catch((error) => error as Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (originalException instanceof ApplicationError) {
|
|
||||||
const { level, extra, tags } = originalException;
|
|
||||||
if (level === 'warning') return null;
|
|
||||||
event.level = level;
|
|
||||||
if (extra) event.extra = { ...event.extra, ...extra };
|
|
||||||
if (tags) event.tags = { ...event.tags, ...tags };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (originalException instanceof Error && originalException.stack) {
|
|
||||||
const eventHash = createHash('sha1').update(originalException.stack).digest('base64');
|
|
||||||
if (seenErrors.has(eventHash)) return null;
|
|
||||||
seenErrors.add(eventHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
return event;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setTag('server_type', 'task_runner');
|
|
||||||
|
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,4 +68,24 @@ export class ErrorReporting {
|
||||||
|
|
||||||
await close(1000);
|
await close(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beforeSend(event: ErrorEvent, { originalException }: EventHint) {
|
||||||
|
if (!originalException) return null;
|
||||||
|
|
||||||
|
if (originalException instanceof ApplicationError) {
|
||||||
|
const { level, extra, tags } = originalException;
|
||||||
|
if (level === 'warning') return null;
|
||||||
|
event.level = level;
|
||||||
|
if (extra) event.extra = { ...event.extra, ...extra };
|
||||||
|
if (tags) event.tags = { ...event.tags, ...tags };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (originalException instanceof Error && originalException.stack) {
|
||||||
|
const eventHash = createHash('sha1').update(originalException.stack).digest('base64');
|
||||||
|
if (this.seenErrors.has(eventHash)) return null;
|
||||||
|
this.seenErrors.add(eventHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -2,12 +2,12 @@ import { ensureError } from 'n8n-workflow';
|
||||||
import Container from 'typedi';
|
import Container from 'typedi';
|
||||||
|
|
||||||
import { MainConfig } from './config/main-config';
|
import { MainConfig } from './config/main-config';
|
||||||
import type { ErrorReporting } from './error-reporting';
|
import type { ErrorReporter } from './error-reporter';
|
||||||
import { JsTaskRunner } from './js-task-runner/js-task-runner';
|
import { JsTaskRunner } from './js-task-runner/js-task-runner';
|
||||||
|
|
||||||
let runner: JsTaskRunner | undefined;
|
let runner: JsTaskRunner | undefined;
|
||||||
let isShuttingDown = false;
|
let isShuttingDown = false;
|
||||||
let errorReporting: ErrorReporting | undefined;
|
let errorReporter: ErrorReporter | undefined;
|
||||||
|
|
||||||
function createSignalHandler(signal: string) {
|
function createSignalHandler(signal: string) {
|
||||||
return async function onSignal() {
|
return async function onSignal() {
|
||||||
|
@ -24,9 +24,9 @@ function createSignalHandler(signal: string) {
|
||||||
runner = undefined;
|
runner = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorReporting) {
|
if (errorReporter) {
|
||||||
await errorReporting.stop();
|
await errorReporter.stop();
|
||||||
errorReporting = undefined;
|
errorReporter = undefined;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = ensureError(e);
|
const error = ensureError(e);
|
||||||
|
@ -42,9 +42,9 @@ void (async function start() {
|
||||||
const config = Container.get(MainConfig);
|
const config = Container.get(MainConfig);
|
||||||
|
|
||||||
if (config.sentryConfig.sentryDsn) {
|
if (config.sentryConfig.sentryDsn) {
|
||||||
const { ErrorReporting } = await import('@/error-reporting');
|
const { ErrorReporter } = await import('@/error-reporter');
|
||||||
errorReporting = new ErrorReporting(config.sentryConfig);
|
errorReporter = new ErrorReporter(config.sentryConfig);
|
||||||
await errorReporting.start();
|
await errorReporter.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
runner = new JsTaskRunner(config);
|
runner = new JsTaskRunner(config);
|
||||||
|
|
Loading…
Reference in a new issue