diff --git a/packages/cli/src/ErrorReporting.ts b/packages/cli/src/ErrorReporting.ts index b214408b66..cc193a4900 100644 --- a/packages/cli/src/ErrorReporting.ts +++ b/packages/cli/src/ErrorReporting.ts @@ -1,6 +1,6 @@ import { createHash } from 'crypto'; import config from '@/config'; -import { ErrorReporterProxy, ExecutionBaseError } from 'n8n-workflow'; +import { ErrorReporterProxy, ExecutionBaseError, ReportableError } from 'n8n-workflow'; let initialized = false; @@ -41,10 +41,19 @@ export const initErrorHandling = async () => { addGlobalEventProcessor((event, { originalException }) => { if (originalException instanceof ExecutionBaseError && originalException.severity === 'warning') return null; + + if (originalException instanceof ReportableError) { + const { level, extra } = originalException; + if (level === 'warning') return null; + event.level = level; + if (extra) event.extra = { ...event.extra, ...extra }; + } + if (!event.exception) return null; const eventHash = createHash('sha1').update(JSON.stringify(event.exception)).digest('base64'); if (seenErrors.has(eventHash)) return null; seenErrors.add(eventHash); + return event; }); diff --git a/packages/core/src/errors.ts b/packages/core/src/errors.ts deleted file mode 100644 index a8fea81380..0000000000 --- a/packages/core/src/errors.ts +++ /dev/null @@ -1,13 +0,0 @@ -export class FileNotFoundError extends Error { - constructor(readonly filePath: string) { - super(`File not found: ${filePath}`); - } -} - -export class BinaryFileNotFoundError extends FileNotFoundError {} - -export class InvalidPathError extends Error { - constructor(readonly filePath: string) { - super(`Invalid path detected: ${filePath}`); - } -} diff --git a/packages/core/src/errors/filesystem.errors.ts b/packages/core/src/errors/filesystem.errors.ts new file mode 100644 index 0000000000..f7928a333f --- /dev/null +++ b/packages/core/src/errors/filesystem.errors.ts @@ -0,0 +1,21 @@ +import { ReportableError } from 'n8n-workflow'; + +abstract class FileSystemError extends ReportableError { + constructor(message: string, filePath: string) { + super(message, { extra: { filePath } }); + } +} + +export class FileNotFoundError extends FileSystemError { + constructor(filePath: string) { + super('File not found', filePath); + } +} + +export class BinaryFileNotFoundError extends FileNotFoundError {} + +export class InvalidPathError extends FileSystemError { + constructor(filePath: string) { + super('Invalid path detected', filePath); + } +} diff --git a/packages/core/src/errors/index.ts b/packages/core/src/errors/index.ts new file mode 100644 index 0000000000..ef2f8401c2 --- /dev/null +++ b/packages/core/src/errors/index.ts @@ -0,0 +1 @@ +export { BinaryFileNotFoundError, FileNotFoundError, InvalidPathError } from './filesystem.errors'; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 74e15f0f4a..b934894f4d 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,5 +1,6 @@ import * as NodeExecuteFunctions from './NodeExecuteFunctions'; +export * from './errors'; export * from './ActiveWorkflows'; export * from './BinaryData/BinaryData.service'; export * from './BinaryData/types'; diff --git a/packages/workflow/src/ErrorReporterProxy.ts b/packages/workflow/src/ErrorReporterProxy.ts index 6f7251d737..092060e40a 100644 --- a/packages/workflow/src/ErrorReporterProxy.ts +++ b/packages/workflow/src/ErrorReporterProxy.ts @@ -1,11 +1,5 @@ -import type { Primitives } from './utils'; import * as Logger from './LoggerProxy'; - -export interface ReportingOptions { - level?: 'warning' | 'error' | 'fatal'; - tags?: Record; - extra?: Record; -} +import { ReportableError, type ReportingOptions } from './errors/reportable.error'; interface ErrorReporter { report: (error: Error | string, options?: ReportingOptions) => void; @@ -16,7 +10,8 @@ const instance: ErrorReporter = { if (error instanceof Error) { let e = error; do { - Logger.error(`${e.constructor.name}: ${e.message}`); + const meta = e instanceof ReportableError ? e.extra : undefined; + Logger.error(`${e.constructor.name}: ${e.message}`, meta); e = e.cause as Error; } while (e); } diff --git a/packages/workflow/src/errors/index.ts b/packages/workflow/src/errors/index.ts new file mode 100644 index 0000000000..7cb82861d6 --- /dev/null +++ b/packages/workflow/src/errors/index.ts @@ -0,0 +1 @@ +export { ReportableError } from './reportable.error'; diff --git a/packages/workflow/src/errors/reportable.error.ts b/packages/workflow/src/errors/reportable.error.ts new file mode 100644 index 0000000000..b3d0fe5c20 --- /dev/null +++ b/packages/workflow/src/errors/reportable.error.ts @@ -0,0 +1,24 @@ +import type { Event } from '@sentry/node'; + +type Level = 'warning' | 'error' | 'fatal'; + +export type ReportingOptions = { + level?: Level; +} & Pick; + +export type ReportableErrorOptions = Partial & ReportingOptions; + +export class ReportableError extends Error { + readonly level: Level; + + readonly tags?: Event['tags']; + + readonly extra?: Event['extra']; + + constructor(message: string, { level, tags, extra, ...rest }: ReportableErrorOptions) { + super(message, rest); + this.level = level ?? 'error'; + this.tags = tags; + this.extra = extra; + } +} diff --git a/packages/workflow/src/index.ts b/packages/workflow/src/index.ts index e8458ab105..9ac6bb4ddd 100644 --- a/packages/workflow/src/index.ts +++ b/packages/workflow/src/index.ts @@ -5,6 +5,7 @@ import * as NodeHelpers from './NodeHelpers'; import * as ObservableObject from './ObservableObject'; import * as TelemetryHelpers from './TelemetryHelpers'; +export * from './errors'; export * from './Authentication'; export * from './Constants'; export * from './Cron';