Skip to content

Commit

Permalink
Rename to ErrorReporter and improve code
Browse files Browse the repository at this point in the history
  • Loading branch information
tomi committed Nov 12, 2024
1 parent e795d0b commit 4fceebf
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 37 deletions.
4 changes: 2 additions & 2 deletions packages/@n8n/task-runner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@
},
"dependencies": {
"@n8n/config": "workspace:*",
"acorn": "8.14.0",
"acorn-walk": "8.3.4",
"@sentry/integrations": "catalog:",
"@sentry/node": "catalog:",
"acorn": "8.14.0",
"acorn-walk": "8.3.4",
"n8n-core": "workspace:*",
"n8n-workflow": "workspace:*",
"nanoid": "^3.3.6",
Expand Down
31 changes: 31 additions & 0 deletions packages/@n8n/task-runner/src/__tests__/error-reporter.test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { RewriteFrames } from '@sentry/integrations';
import { init, setTag, captureException, close } from '@sentry/node';
import type { ErrorEvent, EventHint } from '@sentry/types';
import * as a from 'assert/strict';
import { createHash } from 'crypto';
import { ApplicationError } from 'n8n-workflow';
Expand All @@ -9,9 +10,12 @@ import type { SentryConfig } from '@/config/sentry-config';
/**
* Handles error reporting using Sentry
*/
export class ErrorReporting {
export class ErrorReporter {
private isInitialized = false;

/** Hashes of error stack traces, to deduplicate error reports. */
private readonly seenErrors = new Set<string>();

private get dsn() {
return this.sentryConfig.sentryDsn;
}
Expand All @@ -37,7 +41,8 @@ export class ErrorReporting {
'OnUnhandledRejection',
'ContextLines',
];
const seenErrors = new Set<string>();

setTag('server_type', 'task_runner');

init({
dsn: this.dsn,
Expand All @@ -46,37 +51,13 @@ export class ErrorReporting {
enableTracing: false,
serverName: this.sentryConfig.deploymentName,
beforeBreadcrumb: () => null,
beforeSend: (event, hint) => this.beforeSend(event, hint),
integrations: (integrations) => [
...integrations.filter(({ name }) => enabledIntegrations.includes(name)),
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;
}

Expand All @@ -87,4 +68,24 @@ export class ErrorReporting {

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;
}
}
16 changes: 8 additions & 8 deletions packages/@n8n/task-runner/src/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { ensureError } from 'n8n-workflow';
import Container from 'typedi';

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';

let runner: JsTaskRunner | undefined;
let isShuttingDown = false;
let errorReporting: ErrorReporting | undefined;
let errorReporter: ErrorReporter | undefined;

function createSignalHandler(signal: string) {
return async function onSignal() {
Expand All @@ -24,9 +24,9 @@ function createSignalHandler(signal: string) {
runner = undefined;
}

if (errorReporting) {
await errorReporting.stop();
errorReporting = undefined;
if (errorReporter) {
await errorReporter.stop();
errorReporter = undefined;
}
} catch (e) {
const error = ensureError(e);
Expand All @@ -42,9 +42,9 @@ void (async function start() {
const config = Container.get(MainConfig);

if (config.sentryConfig.sentryDsn) {
const { ErrorReporting } = await import('@/error-reporting');
errorReporting = new ErrorReporting(config.sentryConfig);
await errorReporting.start();
const { ErrorReporter } = await import('@/error-reporter');
errorReporter = new ErrorReporter(config.sentryConfig);
await errorReporter.start();
}

runner = new JsTaskRunner(config);
Expand Down

0 comments on commit 4fceebf

Please sign in to comment.