From d9ecab0672d21ae42e75203d1f4d0f97fb677d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Wed, 16 Oct 2024 11:34:02 +0200 Subject: [PATCH] convert error reporting to a DI service --- packages/cli/src/commands/base-command.ts | 4 +- packages/cli/src/error-reporting.ts | 197 +++++++++++----------- 2 files changed, 105 insertions(+), 96 deletions(-) diff --git a/packages/cli/src/commands/base-command.ts b/packages/cli/src/commands/base-command.ts index 303b3cae3e..9bd2ef1c23 100644 --- a/packages/cli/src/commands/base-command.ts +++ b/packages/cli/src/commands/base-command.ts @@ -21,7 +21,7 @@ import { LICENSE_FEATURES, inDevelopment, inTest } from '@/constants'; import * as CrashJournal from '@/crash-journal'; import * as Db from '@/db'; import { getDataDeduplicationService } from '@/deduplication'; -import { initErrorHandling } from '@/error-reporting'; +import { ErrorReporting } from '@/error-reporting'; import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { TelemetryEventRelay } from '@/events/relays/telemetry.event-relay'; import { initExpressionEvaluator } from '@/expression-evaluator'; @@ -61,7 +61,7 @@ export abstract class BaseCommand extends Command { protected needsCommunityPackages = false; async init(): Promise { - await initErrorHandling(); + await Container.get(ErrorReporting).init(); initExpressionEvaluator(); process.once('SIGTERM', this.onTerminationSignal('SIGTERM')); diff --git a/packages/cli/src/error-reporting.ts b/packages/cli/src/error-reporting.ts index d90229130f..ba8e945d41 100644 --- a/packages/cli/src/error-reporting.ts +++ b/packages/cli/src/error-reporting.ts @@ -5,102 +5,111 @@ import { AxiosError } from 'axios'; import { createHash } from 'crypto'; import { InstanceSettings } from 'n8n-core'; import { ErrorReporterProxy, ApplicationError } from 'n8n-workflow'; -import Container from 'typedi'; +import { Service } from 'typedi'; -let initialized = false; +@Service() +export class ErrorReporting { + private initialized = false; -export const initErrorHandling = async () => { - if (initialized) return; + constructor( + private readonly globalConfig: GlobalConfig, + private readonly instanceSettings: InstanceSettings, + ) {} - process.on('uncaughtException', (error) => { - ErrorReporterProxy.error(error); - }); + async init() { + if (this.initialized) return; - const dsn = Container.get(GlobalConfig).sentry.backendDsn; - if (!dsn) { - initialized = true; - return; + process.on('uncaughtException', (error) => { + ErrorReporterProxy.error(error); + }); + + const dsn = this.globalConfig.sentry.backendDsn; + if (!dsn) { + this.initialized = true; + return; + } + + // Collect longer stacktraces + Error.stackTraceLimit = 50; + + const { + N8N_VERSION: release, + ENVIRONMENT: environment, + DEPLOYMENT_NAME: serverName, + } = process.env; + + const { init, captureException, setTag } = await import('@sentry/node'); + const { RewriteFrames } = await import('@sentry/integrations'); + const { Integrations } = await import('@sentry/node'); + + const enabledIntegrations = [ + 'InboundFilters', + 'FunctionToString', + 'LinkedErrors', + 'OnUnhandledRejection', + 'ContextLines', + ]; + const seenErrors = new Set(); + + init({ + dsn, + release, + environment, + enableTracing: false, + serverName, + beforeBreadcrumb: () => null, + integrations: (integrations) => [ + ...integrations.filter(({ name }) => enabledIntegrations.includes(name)), + new RewriteFrames({ root: process.cwd() }), + new Integrations.RequestData({ + include: { + cookies: false, + data: false, + headers: false, + query_string: false, + url: true, + user: false, + }, + }), + ], + beforeSend(event, { originalException }) { + if (!originalException) return null; + + if (originalException instanceof AxiosError) return null; + + if ( + originalException instanceof QueryFailedError && + ['SQLITE_FULL', 'SQLITE_IOERR'].some((errMsg) => + originalException.message.includes(errMsg), + ) + ) { + 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 (seenErrors.has(eventHash)) return null; + seenErrors.add(eventHash); + } + + return event; + }, + }); + + setTag('server_type', this.instanceSettings.instanceType); + + ErrorReporterProxy.init({ + report: (error, options) => captureException(error, options), + }); + + this.initialized = true; } - - // Collect longer stacktraces - Error.stackTraceLimit = 50; - - const { - N8N_VERSION: release, - ENVIRONMENT: environment, - DEPLOYMENT_NAME: serverName, - } = process.env; - - const { init, captureException, setTag } = await import('@sentry/node'); - - const { RewriteFrames } = await import('@sentry/integrations'); - const { Integrations } = await import('@sentry/node'); - - const enabledIntegrations = [ - 'InboundFilters', - 'FunctionToString', - 'LinkedErrors', - 'OnUnhandledRejection', - 'ContextLines', - ]; - const seenErrors = new Set(); - - init({ - dsn, - release, - environment, - enableTracing: false, - serverName, - beforeBreadcrumb: () => null, - integrations: (integrations) => [ - ...integrations.filter(({ name }) => enabledIntegrations.includes(name)), - new RewriteFrames({ root: process.cwd() }), - new Integrations.RequestData({ - include: { - cookies: false, - data: false, - headers: false, - query_string: false, - url: true, - user: false, - }, - }), - ], - beforeSend(event, { originalException }) { - if (!originalException) return null; - - if (originalException instanceof AxiosError) return null; - - if ( - originalException instanceof QueryFailedError && - ['SQLITE_FULL', 'SQLITE_IOERR'].some((errMsg) => originalException.message.includes(errMsg)) - ) { - 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 (seenErrors.has(eventHash)) return null; - seenErrors.add(eventHash); - } - - return event; - }, - }); - - setTag('server_type', Container.get(InstanceSettings).instanceType); - - ErrorReporterProxy.init({ - report: (error, options) => captureException(error, options), - }); - - initialized = true; -}; +}