mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-28 05:59:42 -08:00
fix: Remove duplicate error reporting for endpoints (no-changelog) (#11959)
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
parent
f258cebe53
commit
fb5cf4beea
|
@ -94,7 +94,7 @@
|
||||||
"@n8n/permissions": "workspace:*",
|
"@n8n/permissions": "workspace:*",
|
||||||
"@n8n/task-runner": "workspace:*",
|
"@n8n/task-runner": "workspace:*",
|
||||||
"@n8n/typeorm": "0.3.20-12",
|
"@n8n/typeorm": "0.3.20-12",
|
||||||
"@n8n_io/ai-assistant-sdk": "1.10.3",
|
"@n8n_io/ai-assistant-sdk": "1.12.0",
|
||||||
"@n8n_io/license-sdk": "2.13.1",
|
"@n8n_io/license-sdk": "2.13.1",
|
||||||
"@oclif/core": "4.0.7",
|
"@oclif/core": "4.0.7",
|
||||||
"@rudderstack/rudder-sdk-node": "2.0.9",
|
"@rudderstack/rudder-sdk-node": "2.0.9",
|
||||||
|
|
61
packages/cli/src/__tests__/error-reporting.test.ts
Normal file
61
packages/cli/src/__tests__/error-reporting.test.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import { GlobalConfig } from '@n8n/config';
|
||||||
|
import type { ClientOptions, ErrorEvent } from '@sentry/types';
|
||||||
|
import { strict as assert } from 'node:assert';
|
||||||
|
import { Container } from 'typedi';
|
||||||
|
|
||||||
|
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
||||||
|
|
||||||
|
const init = jest.fn();
|
||||||
|
|
||||||
|
jest.mock('@sentry/integrations');
|
||||||
|
jest.mock('@sentry/node', () => ({
|
||||||
|
init,
|
||||||
|
setTag: jest.fn(),
|
||||||
|
captureException: jest.fn(),
|
||||||
|
Integrations: {},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.spyOn(process, 'on');
|
||||||
|
|
||||||
|
describe('initErrorHandling', () => {
|
||||||
|
let beforeSend: ClientOptions['beforeSend'];
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
Container.get(GlobalConfig).sentry.backendDsn = 'backend-dsn';
|
||||||
|
const errorReporting = require('@/error-reporting');
|
||||||
|
await errorReporting.initErrorHandling();
|
||||||
|
const options = (init.mock.calls[0] as [ClientOptions])[0];
|
||||||
|
beforeSend = options.beforeSend;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores errors with level warning', async () => {
|
||||||
|
const originalException = new InternalServerError('test');
|
||||||
|
originalException.level = 'warning';
|
||||||
|
|
||||||
|
const event = {} as ErrorEvent;
|
||||||
|
|
||||||
|
assert(beforeSend);
|
||||||
|
expect(await beforeSend(event, { originalException })).toEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps events with a cause with error level', async () => {
|
||||||
|
const cause = new Error('cause-error');
|
||||||
|
|
||||||
|
const originalException = new InternalServerError('test', cause);
|
||||||
|
const event = {} as ErrorEvent;
|
||||||
|
|
||||||
|
assert(beforeSend);
|
||||||
|
expect(await beforeSend(event, { originalException })).toEqual(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores events with error cause with warning level', async () => {
|
||||||
|
const cause: Error & { level?: 'warning' } = new Error('cause-error');
|
||||||
|
cause.level = 'warning';
|
||||||
|
|
||||||
|
const originalException = new InternalServerError('test', cause);
|
||||||
|
const event = {} as ErrorEvent;
|
||||||
|
|
||||||
|
assert(beforeSend);
|
||||||
|
expect(await beforeSend(event, { originalException })).toEqual(null);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,6 +1,5 @@
|
||||||
import type { AiAssistantSDK } from '@n8n_io/ai-assistant-sdk';
|
import type { AiAssistantSDK } from '@n8n_io/ai-assistant-sdk';
|
||||||
import type { Response } from 'express';
|
import type { Response } from 'express';
|
||||||
import { ErrorReporterProxy } from 'n8n-workflow';
|
|
||||||
import { strict as assert } from 'node:assert';
|
import { strict as assert } from 'node:assert';
|
||||||
import { WritableStream } from 'node:stream/web';
|
import { WritableStream } from 'node:stream/web';
|
||||||
|
|
||||||
|
@ -33,8 +32,7 @@ export class AiController {
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
assert(e instanceof Error);
|
assert(e instanceof Error);
|
||||||
ErrorReporterProxy.error(e);
|
throw new InternalServerError(e.message, e);
|
||||||
throw new InternalServerError(`Something went wrong: ${e.message}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,8 +44,7 @@ export class AiController {
|
||||||
return await this.aiService.applySuggestion(req.body, req.user);
|
return await this.aiService.applySuggestion(req.body, req.user);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
assert(e instanceof Error);
|
assert(e instanceof Error);
|
||||||
ErrorReporterProxy.error(e);
|
throw new InternalServerError(e.message, e);
|
||||||
throw new InternalServerError(`Something went wrong: ${e.message}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,8 +54,7 @@ export class AiController {
|
||||||
return await this.aiService.askAi(req.body, req.user);
|
return await this.aiService.askAi(req.body, req.user);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
assert(e instanceof Error);
|
assert(e instanceof Error);
|
||||||
ErrorReporterProxy.error(e);
|
throw new InternalServerError(e.message, e);
|
||||||
throw new InternalServerError(`Something went wrong: ${e.message}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -201,7 +201,7 @@ export class CommunityPackagesController {
|
||||||
error instanceof Error ? error.message : UNKNOWN_FAILURE_REASON,
|
error instanceof Error ? error.message : UNKNOWN_FAILURE_REASON,
|
||||||
].join(':');
|
].join(':');
|
||||||
|
|
||||||
throw new InternalServerError(message);
|
throw new InternalServerError(message, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// broadcast to connected frontends that node list has been updated
|
// broadcast to connected frontends that node list has been updated
|
||||||
|
@ -283,7 +283,7 @@ export class CommunityPackagesController {
|
||||||
error instanceof Error ? error.message : UNKNOWN_FAILURE_REASON,
|
error instanceof Error ? error.message : UNKNOWN_FAILURE_REASON,
|
||||||
].join(':');
|
].join(':');
|
||||||
|
|
||||||
throw new InternalServerError(message);
|
throw new InternalServerError(message, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,7 +120,7 @@ export class PasswordResetController {
|
||||||
publicApi: false,
|
publicApi: false,
|
||||||
});
|
});
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
throw new InternalServerError(`Please contact your administrator: ${error.message}`);
|
throw new InternalServerError(`Please contact your administrator: ${error.message}`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ export class TranslationController {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||||
return require(NODE_HEADERS_PATH);
|
return require(NODE_HEADERS_PATH);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new InternalServerError('Failed to load headers file');
|
throw new InternalServerError('Failed to load headers file', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,17 @@ export const initErrorHandling = async () => {
|
||||||
if (tags) event.tags = { ...event.tags, ...tags };
|
if (tags) event.tags = { ...event.tags, ...tags };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
originalException instanceof Error &&
|
||||||
|
'cause' in originalException &&
|
||||||
|
originalException.cause instanceof Error &&
|
||||||
|
'level' in originalException.cause &&
|
||||||
|
originalException.cause.level === 'warning'
|
||||||
|
) {
|
||||||
|
// handle underlying errors propagating from dependencies like ai-assistant-sdk
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (originalException instanceof Error && originalException.stack) {
|
if (originalException instanceof Error && originalException.stack) {
|
||||||
const eventHash = createHash('sha1').update(originalException.stack).digest('base64');
|
const eventHash = createHash('sha1').update(originalException.stack).digest('base64');
|
||||||
if (seenErrors.has(eventHash)) return null;
|
if (seenErrors.has(eventHash)) return null;
|
||||||
|
|
|
@ -16,8 +16,9 @@ export abstract class ResponseError extends ApplicationError {
|
||||||
readonly errorCode: number = httpStatusCode,
|
readonly errorCode: number = httpStatusCode,
|
||||||
// The error hint the response
|
// The error hint the response
|
||||||
readonly hint: string | undefined = undefined,
|
readonly hint: string | undefined = undefined,
|
||||||
|
cause?: unknown,
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message, { cause });
|
||||||
this.name = 'ResponseError';
|
this.name = 'ResponseError';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ResponseError } from './abstract/response.error';
|
import { ResponseError } from './abstract/response.error';
|
||||||
|
|
||||||
export class InternalServerError extends ResponseError {
|
export class InternalServerError extends ResponseError {
|
||||||
constructor(message: string, errorCode = 500) {
|
constructor(message: string, cause?: unknown) {
|
||||||
super(message, 500, errorCode);
|
super(message, 500, 500, undefined, cause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -251,7 +251,7 @@ export class ExecutionService {
|
||||||
requestFilters = requestFiltersRaw as IGetExecutionsQueryFilter;
|
requestFilters = requestFiltersRaw as IGetExecutionsQueryFilter;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new InternalServerError('Parameter "filter" contained invalid JSON string.');
|
throw new InternalServerError('Parameter "filter" contained invalid JSON string.', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { IUserSettings } from 'n8n-workflow';
|
import type { IUserSettings } from 'n8n-workflow';
|
||||||
import { ApplicationError, ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';
|
import { ApplicationError } from 'n8n-workflow';
|
||||||
import { Service } from 'typedi';
|
import { Service } from 'typedi';
|
||||||
|
|
||||||
import type { User, AssignableRole } from '@/databases/entities/user';
|
import type { User, AssignableRole } from '@/databases/entities/user';
|
||||||
|
@ -213,9 +213,8 @@ export class UserService {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ErrorReporter.error(error);
|
|
||||||
this.logger.error('Failed to create user shells', { userShells: createdUsers });
|
this.logger.error('Failed to create user shells', { userShells: createdUsers });
|
||||||
throw new InternalServerError('An error occurred during user creation');
|
throw new InternalServerError('An error occurred during user creation', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingUsersToInvite.forEach(({ email, id }) => createdUsers.set(email, id));
|
pendingUsersToInvite.forEach(({ email, id }) => createdUsers.set(email, id));
|
||||||
|
|
|
@ -125,7 +125,7 @@ export class UserManagementMailer {
|
||||||
|
|
||||||
const error = toError(e);
|
const error = toError(e);
|
||||||
|
|
||||||
throw new InternalServerError(`Please contact your administrator: ${error.message}`);
|
throw new InternalServerError(`Please contact your administrator: ${error.message}`, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,7 +180,7 @@ export class UserManagementMailer {
|
||||||
|
|
||||||
const error = toError(e);
|
const error = toError(e);
|
||||||
|
|
||||||
throw new InternalServerError(`Please contact your administrator: ${error.message}`);
|
throw new InternalServerError(`Please contact your administrator: ${error.message}`, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -762,7 +762,7 @@ export async function executeWebhook(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const internalServerError = new InternalServerError(e.message);
|
const internalServerError = new InternalServerError(e.message, e);
|
||||||
if (e instanceof ExecutionCancelledError) internalServerError.level = 'warning';
|
if (e instanceof ExecutionCancelledError) internalServerError.level = 'warning';
|
||||||
throw internalServerError;
|
throw internalServerError;
|
||||||
});
|
});
|
||||||
|
|
|
@ -776,8 +776,8 @@ importers:
|
||||||
specifier: 0.3.20-12
|
specifier: 0.3.20-12
|
||||||
version: 0.3.20-12(@sentry/node@7.87.0)(ioredis@5.3.2)(mssql@10.0.2)(mysql2@3.11.0)(pg@8.12.0)(redis@4.6.14)(sqlite3@5.1.7)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.7.2))
|
version: 0.3.20-12(@sentry/node@7.87.0)(ioredis@5.3.2)(mssql@10.0.2)(mysql2@3.11.0)(pg@8.12.0)(redis@4.6.14)(sqlite3@5.1.7)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.7.2))
|
||||||
'@n8n_io/ai-assistant-sdk':
|
'@n8n_io/ai-assistant-sdk':
|
||||||
specifier: 1.10.3
|
specifier: 1.12.0
|
||||||
version: 1.10.3
|
version: 1.12.0
|
||||||
'@n8n_io/license-sdk':
|
'@n8n_io/license-sdk':
|
||||||
specifier: 2.13.1
|
specifier: 2.13.1
|
||||||
version: 2.13.1
|
version: 2.13.1
|
||||||
|
@ -3795,8 +3795,8 @@ packages:
|
||||||
engines: {node: '>=18.10', pnpm: '>=9.6'}
|
engines: {node: '>=18.10', pnpm: '>=9.6'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@n8n_io/ai-assistant-sdk@1.10.3':
|
'@n8n_io/ai-assistant-sdk@1.12.0':
|
||||||
resolution: {integrity: sha512-oHuYryPoWa+KcIf7ihrlXSVpi3XhVPmZX4tUX4TMvgAeipu7yhf7ZSXQFeBUx6zD1oa2zOXIs4b0NsXELrZWqg==}
|
resolution: {integrity: sha512-ddIGzUn8icxWwl49PLSpl/Gfb0bCIGpqvWtZWqC3GIBeb51Nul6E4e3cIyDYOFlZmWWr/BDKsN0wskm2s/jkdg==}
|
||||||
engines: {node: '>=20.15', pnpm: '>=8.14'}
|
engines: {node: '>=20.15', pnpm: '>=8.14'}
|
||||||
|
|
||||||
'@n8n_io/license-sdk@2.13.1':
|
'@n8n_io/license-sdk@2.13.1':
|
||||||
|
@ -15023,7 +15023,7 @@ snapshots:
|
||||||
acorn: 8.12.1
|
acorn: 8.12.1
|
||||||
acorn-walk: 8.3.4
|
acorn-walk: 8.3.4
|
||||||
|
|
||||||
'@n8n_io/ai-assistant-sdk@1.10.3': {}
|
'@n8n_io/ai-assistant-sdk@1.12.0': {}
|
||||||
|
|
||||||
'@n8n_io/license-sdk@2.13.1':
|
'@n8n_io/license-sdk@2.13.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
Loading…
Reference in a new issue