From b7d32d4bae702690cfc64925aefc52af16e31811 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: Thu, 20 Oct 2022 17:33:48 +0200 Subject: [PATCH] feat(cli): Setup error tracking using Sentry --- docker/images/n8n-debian/Dockerfile | 1 + docker/images/n8n-rhel7/Dockerfile | 1 + docker/images/n8n/Dockerfile | 5 +- package-lock.json | 193 ++++++++++++++++-- packages/cli/config/schema.ts | 9 + packages/cli/package.json | 2 + packages/cli/src/ActiveWorkflowRunner.ts | 4 + packages/cli/src/CredentialsHelper.ts | 2 + packages/cli/src/ErrorHandling.ts | 36 ++++ packages/cli/src/LoadNodesAndCredentials.ts | 9 +- packages/cli/src/Server.ts | 5 + .../src/UserManagement/email/NodeMailer.ts | 2 + .../cli/src/UserManagement/routes/users.ts | 2 + packages/cli/src/WaitTracker.ts | 7 +- packages/cli/src/WebhookHelpers.ts | 2 + packages/cli/src/WebhookServer.ts | 3 + .../cli/src/WorkflowExecuteAdditionalData.ts | 7 + packages/cli/src/WorkflowHelpers.ts | 3 + packages/cli/src/WorkflowRunner.ts | 9 + packages/cli/src/WorkflowRunnerProcess.ts | 7 + 20 files changed, 289 insertions(+), 20 deletions(-) create mode 100644 packages/cli/src/ErrorHandling.ts diff --git a/docker/images/n8n-debian/Dockerfile b/docker/images/n8n-debian/Dockerfile index c77016585b80d..68904101bdc44 100644 --- a/docker/images/n8n-debian/Dockerfile +++ b/docker/images/n8n-debian/Dockerfile @@ -4,6 +4,7 @@ ARG N8N_VERSION RUN if [ -z "$N8N_VERSION" ] ; then echo "The N8N_VERSION argument is missing!" ; exit 1; fi +ENV N8N_VERSION=${N8N_VERSION} RUN \ apt-get update && \ apt-get -y install graphicsmagick gosu git diff --git a/docker/images/n8n-rhel7/Dockerfile b/docker/images/n8n-rhel7/Dockerfile index fea681ac9445d..72ab1938ce99f 100644 --- a/docker/images/n8n-rhel7/Dockerfile +++ b/docker/images/n8n-rhel7/Dockerfile @@ -4,6 +4,7 @@ ARG N8N_VERSION RUN if [ -z "$N8N_VERSION" ] ; then echo "The N8N_VERSION argument is missing!" ; exit 1; fi +ENV N8N_VERSION=${N8N_VERSION} RUN \ yum install -y gcc-c++ make diff --git a/docker/images/n8n/Dockerfile b/docker/images/n8n/Dockerfile index 4a3d368ff5952..c85ef12a66b97 100644 --- a/docker/images/n8n/Dockerfile +++ b/docker/images/n8n/Dockerfile @@ -4,15 +4,16 @@ FROM n8nio/base:${NODE_VERSION} ARG N8N_VERSION RUN if [ -z "$N8N_VERSION" ] ; then echo "The N8N_VERSION argument is missing!" ; exit 1; fi +ENV N8N_VERSION=${N8N_VERSION} ENV NODE_ENV=production RUN set -eux; \ apkArch="$(apk --print-arch)"; \ case "$apkArch" in \ - 'armv7') apk --no-cache add --virtual build-dependencies python3 build-base;; \ + 'armv7') apk --no-cache add --virtual build-dependencies python3 build-base;; \ esac && \ npm install -g --omit=dev n8n@${N8N_VERSION} && \ case "$apkArch" in \ - 'armv7') apk del build-dependencies;; \ + 'armv7') apk del build-dependencies;; \ esac && \ find /usr/local/lib/node_modules/n8n -type f -name "*.ts" -o -name "*.js.map" -o -name "*.vue" | xargs rm && \ rm -rf /root/.npm diff --git a/package-lock.json b/package-lock.json index 8bcb0eafe067f..663501c9ea55b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5788,6 +5788,70 @@ "url": "https://ko-fi.com/killymxi" } }, + "node_modules/@sentry/core": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.16.0.tgz", + "integrity": "sha512-vq6H1b/IPTvzDD9coQ3wIudvSjkAYuUlXb1dv69dRlq4v3st9dcKBps1Zf0lQ1i4TVlDLoe1iGMmNFglMF1Q5w==", + "dependencies": { + "@sentry/types": "7.16.0", + "@sentry/utils": "7.16.0", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/integrations": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.16.0.tgz", + "integrity": "sha512-PJRmFUHOKMf1APOlwxGB7Z6LHzJP4xL6OLSoiwnruRuHaGvdyILA53fr/A6wRMSPyTxJDJpNkcN36cA56mLxYA==", + "dependencies": { + "@sentry/types": "7.16.0", + "@sentry/utils": "7.16.0", + "localforage": "^1.8.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/node": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.16.0.tgz", + "integrity": "sha512-OC0TO6UTetv8IsX3zNhdeui7YVIQCnhkbfi+CMrB6YsHaMP2A9qH5gNyu/hKbaY9+4xci7e4rxyRmI65aKS9ow==", + "dependencies": { + "@sentry/core": "7.16.0", + "@sentry/types": "7.16.0", + "@sentry/utils": "7.16.0", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.16.0.tgz", + "integrity": "sha512-i6D+OK6d0l/k+VQvRp/Pt21WkDEgVBUIZq+sOkEZJczbcfexVdXKeXXoYTD2vYuFq8Yy28fzlsZaKI+NoH94yQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/utils": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.16.0.tgz", + "integrity": "sha512-3Zh1txg7IRp4kZAdG27YF7K6lD1IZyuAo9KjoPg1Xzqa4DOZyASJuEkbf+rK2a9T4HrtVHHXJUsNbKg8WM3VHg==", + "dependencies": { + "@sentry/types": "7.16.0", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@servie/events": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@servie/events/-/events-1.0.0.tgz", @@ -21353,6 +21417,11 @@ "node": ">=0.10.0" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "node_modules/immutable": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", @@ -26808,6 +26877,14 @@ "resolved": "https://registry.npmjs.org/libqp/-/libqp-1.1.0.tgz", "integrity": "sha512-4Rgfa0hZpG++t1Vi2IiqXG9Ad1ig4QTmtuZF946QJP4bPqOYC78ixUXgz5TW/wE7lNaNKlplSYTxQ+fR2KZ0EA==" }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/liftoff": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", @@ -26935,6 +27012,14 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "dependencies": { + "lie": "3.1.1" + } + }, "node_modules/localtunnel": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/localtunnel/-/localtunnel-2.0.2.tgz", @@ -27226,6 +27311,11 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, + "node_modules/lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==" + }, "node_modules/lru-cache": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", @@ -41943,6 +42033,8 @@ "@oclif/core": "^1.9.3", "@oclif/errors": "^1.2.2", "@rudderstack/rudder-sdk-node": "1.0.6", + "@sentry/integrations": "^7.16.0", + "@sentry/node": "^7.16.0", "axios": "^0.21.1", "basic-auth": "^2.0.1", "bcryptjs": "^2.4.3", @@ -46646,7 +46738,7 @@ "@oclif/errors": "^1.3.6", "@oclif/parser": "^3.8.8", "debug": "^4.3.4", - "globby": "^11.1.0", + "globby": "^11.0.2", "is-wsl": "^2.1.1", "tslib": "^2.3.1" }, @@ -46672,10 +46764,10 @@ "clean-stack": "^3.0.1", "cli-progress": "^3.10.0", "debug": "^4.3.4", - "ejs": "^3.1.6", + "ejs": "^3.1.8", "fs-extra": "^9.1.0", "get-package-type": "^0.1.0", - "globby": "^11.1.0", + "globby": "^11.0.2", "hyperlinker": "^1.0.0", "indent-string": "^4.0.0", "is-wsl": "^2.2.0", @@ -47028,7 +47120,7 @@ "@oclif/errors": "^1.3.3", "@oclif/parser": "^3.8.0", "debug": "^4.1.1", - "globby": "^11.0.1", + "globby": "^11.0.2", "is-wsl": "^2.1.1", "tslib": "^2.0.0" } @@ -47196,6 +47288,55 @@ "selderee": "^0.6.0" } }, + "@sentry/core": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.16.0.tgz", + "integrity": "sha512-vq6H1b/IPTvzDD9coQ3wIudvSjkAYuUlXb1dv69dRlq4v3st9dcKBps1Zf0lQ1i4TVlDLoe1iGMmNFglMF1Q5w==", + "requires": { + "@sentry/types": "7.16.0", + "@sentry/utils": "7.16.0", + "tslib": "^1.9.3" + } + }, + "@sentry/integrations": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.16.0.tgz", + "integrity": "sha512-PJRmFUHOKMf1APOlwxGB7Z6LHzJP4xL6OLSoiwnruRuHaGvdyILA53fr/A6wRMSPyTxJDJpNkcN36cA56mLxYA==", + "requires": { + "@sentry/types": "7.16.0", + "@sentry/utils": "7.16.0", + "localforage": "^1.8.1", + "tslib": "^1.9.3" + } + }, + "@sentry/node": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.16.0.tgz", + "integrity": "sha512-OC0TO6UTetv8IsX3zNhdeui7YVIQCnhkbfi+CMrB6YsHaMP2A9qH5gNyu/hKbaY9+4xci7e4rxyRmI65aKS9ow==", + "requires": { + "@sentry/core": "7.16.0", + "@sentry/types": "7.16.0", + "@sentry/utils": "7.16.0", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^1.9.3" + } + }, + "@sentry/types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.16.0.tgz", + "integrity": "sha512-i6D+OK6d0l/k+VQvRp/Pt21WkDEgVBUIZq+sOkEZJczbcfexVdXKeXXoYTD2vYuFq8Yy28fzlsZaKI+NoH94yQ==" + }, + "@sentry/utils": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.16.0.tgz", + "integrity": "sha512-3Zh1txg7IRp4kZAdG27YF7K6lD1IZyuAo9KjoPg1Xzqa4DOZyASJuEkbf+rK2a9T4HrtVHHXJUsNbKg8WM3VHg==", + "requires": { + "@sentry/types": "7.16.0", + "tslib": "^1.9.3" + } + }, "@servie/events": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@servie/events/-/events-1.0.0.tgz", @@ -47534,7 +47675,7 @@ "css-loader": "^3.6.0", "file-loader": "^6.2.0", "find-up": "^5.0.0", - "fork-ts-checker-webpack-plugin": "^4.1.6", + "fork-ts-checker-webpack-plugin": "^6.0.4", "glob": "^7.1.6", "glob-promise": "^3.4.0", "global": "^4.4.0", @@ -50091,7 +50232,7 @@ "@typescript-eslint/types": "5.40.0", "@typescript-eslint/visitor-keys": "5.40.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "globby": "^11.0.2", "is-glob": "^4.0.3", "semver": "^7.3.7", "tsutils": "^3.21.0" @@ -51409,7 +51550,7 @@ "integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==", "dev": true, "requires": { - "browserslist": "^4.12.0", + "browserslist": "^4.21.3", "caniuse-lite": "^1.0.30001109", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", @@ -54206,7 +54347,7 @@ "integrity": "sha512-ovcyhs2DEBUIE0MGEKHP4olCUW/XYte3Vroyxuh38rD1wAO4dHohsovUC4eAOuzFxE6b+RXvBU3UZ9o0YhUTkA==", "dev": true, "requires": { - "browserslist": "^4.21.4" + "browserslist": "^4.21.3" } }, "core-js-pure": { @@ -54280,7 +54421,7 @@ "requires": { "arrify": "^2.0.1", "cp-file": "^7.0.0", - "globby": "^9.2.0", + "globby": "^11.0.2", "has-glob": "^1.0.0", "junk": "^3.1.0", "nested-error-stacks": "^2.1.0", @@ -55883,7 +56024,7 @@ "functional-red-black-tree": "^1.0.1", "glob-parent": "^6.0.1", "globals": "^13.15.0", - "globby": "^11.1.0", + "globby": "^11.0.2", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", @@ -59474,6 +59615,11 @@ } } }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "immutable": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", @@ -63686,6 +63832,14 @@ "resolved": "https://registry.npmjs.org/libqp/-/libqp-1.1.0.tgz", "integrity": "sha512-4Rgfa0hZpG++t1Vi2IiqXG9Ad1ig4QTmtuZF946QJP4bPqOYC78ixUXgz5TW/wE7lNaNKlplSYTxQ+fR2KZ0EA==" }, + "lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "requires": { + "immediate": "~3.0.5" + } + }, "liftoff": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", @@ -63794,6 +63948,14 @@ "integrity": "sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==", "dev": true }, + "localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "requires": { + "lie": "3.1.1" + } + }, "localtunnel": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/localtunnel/-/localtunnel-2.0.2.tgz", @@ -64060,6 +64222,11 @@ } } }, + "lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==" + }, "lru-cache": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", @@ -65314,6 +65481,8 @@ "@oclif/dev-cli": "^1.22.2", "@oclif/errors": "^1.2.2", "@rudderstack/rudder-sdk-node": "1.0.6", + "@sentry/integrations": "^7.16.0", + "@sentry/node": "^7.16.0", "@types/basic-auth": "^1.1.2", "@types/bcryptjs": "^2.4.2", "@types/bull": "^3.3.10", @@ -68649,7 +68818,7 @@ "fs-extra": "^6.0.1", "get-stream": "^5.1.0", "glob": "^7.1.2", - "globby": "^10.0.1", + "globby": "^11.0.2", "http-call": "^5.1.2", "load-json-file": "^6.2.0", "pkg-dir": "^4.2.0", @@ -74342,7 +74511,7 @@ "consola": "^2.15.3", "dotenv": "^16.0.0", "dotenv-expand": "^8.0.2", - "ejs": "^3.1.6", + "ejs": "^3.1.8", "fast-glob": "^3.2.11", "fs-extra": "^10.0.1", "html-minifier-terser": "^6.1.0", diff --git a/packages/cli/config/schema.ts b/packages/cli/config/schema.ts index 045f676a22b67..106a9f60d7839 100644 --- a/packages/cli/config/schema.ts +++ b/packages/cli/config/schema.ts @@ -942,6 +942,15 @@ export const schema = { env: 'N8N_DIAGNOSTICS_POSTHOG_DISABLE_RECORDING', }, }, + sentry: { + dsn: { + doc: 'DSN for error tracking on SentryS', + format: String, + default: + 'https://1f954e089a054b8e943ae4f4042b2bff@o1420875.ingest.sentry.io/4504016528408576', + env: 'N8N_SENTRY_DSN', + }, + }, frontend: { doc: 'Diagnostics config for frontend.', format: String, diff --git a/packages/cli/package.json b/packages/cli/package.json index 678a08ffaa97c..a6332e8896e04 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -104,6 +104,8 @@ "@oclif/core": "^1.9.3", "@oclif/errors": "^1.2.2", "@rudderstack/rudder-sdk-node": "1.0.6", + "@sentry/node": "^7.16.0", + "@sentry/integrations": "^7.16.0", "axios": "^0.21.1", "basic-auth": "^2.0.1", "bcryptjs": "^2.4.3", diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts index dfaea975f43a9..050f7ee6b5a24 100644 --- a/packages/cli/src/ActiveWorkflowRunner.ts +++ b/packages/cli/src/ActiveWorkflowRunner.ts @@ -59,6 +59,7 @@ import { whereClause } from './WorkflowHelpers'; import { WorkflowEntity } from './databases/entities/WorkflowEntity'; import * as ActiveExecutions from './ActiveExecutions'; import { createErrorExecution } from './GenericHelpers'; +import { captureError } from './ErrorHandling'; import { WORKFLOW_REACTIVATE_INITIAL_TIMEOUT, WORKFLOW_REACTIVATE_MAX_TIMEOUT } from './constants'; const activeExecutions = ActiveExecutions.getInstance(); @@ -121,6 +122,7 @@ export class ActiveWorkflowRunner { }); console.log(` => Started`); } catch (error) { + captureError(error); console.log( ` => ERROR: Workflow could not be activated on first try, keep on trying`, ); @@ -881,6 +883,7 @@ export class ActiveWorkflowRunner { try { await this.add(workflowId, activationMode, workflowData); } catch (error) { + captureError(error); let lastTimeout = this.queuedWorkflowActivations[workflowId].lastTimeout; if (lastTimeout < WORKFLOW_REACTIVATE_MAX_TIMEOUT) { lastTimeout = Math.min(lastTimeout * 2, WORKFLOW_REACTIVATE_MAX_TIMEOUT); @@ -948,6 +951,7 @@ export class ActiveWorkflowRunner { try { await this.removeWorkflowWebhooks(workflowId); } catch (error) { + captureError(error); console.error( // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Could not remove webhooks of workflow "${workflowId}" because of error: "${error.message}"`, diff --git a/packages/cli/src/CredentialsHelper.ts b/packages/cli/src/CredentialsHelper.ts index e0e51938f52f9..0e0ef421f1a5c 100644 --- a/packages/cli/src/CredentialsHelper.ts +++ b/packages/cli/src/CredentialsHelper.ts @@ -9,6 +9,7 @@ import { Credentials, NodeExecuteFunctions } from 'n8n-core'; // eslint-disable-next-line import/no-extraneous-dependencies import { get } from 'lodash'; +import { captureError } from './ErrorHandling'; import { ICredentialDataDecryptedObject, @@ -672,6 +673,7 @@ export class CredentialsHelper extends ICredentialsHelper { credentialsDecrypted, ); } catch (error) { + captureError(error); // Do not fail any requests to allow custom error messages and // make logic easier if (error.cause?.response) { diff --git a/packages/cli/src/ErrorHandling.ts b/packages/cli/src/ErrorHandling.ts new file mode 100644 index 0000000000000..2e155893d103c --- /dev/null +++ b/packages/cli/src/ErrorHandling.ts @@ -0,0 +1,36 @@ +import * as Sentry from '@sentry/node'; +import { RewriteFrames } from '@sentry/integrations'; +import type { Application } from 'express'; +import config from '../config'; + +let initialized = false; + +export const initErrorHandling = (app?: Application) => { + if (initialized) return; + + const dsn = config.getEnv('diagnostics.config.sentry.dsn'); + const { N8N_VERSION: release, ENVIRONMENT: environment } = process.env; + + Sentry.init({ + dsn, + release, + environment, + integrations: (defaults) => { + const integrations = defaults.filter( + (i) => !['Console', 'Modules', 'Context', 'LinkedErrors'].includes(i.name), + ); + integrations.push(new RewriteFrames({ root: process.cwd() })); + return integrations; + }, + }); + + if (app) { + const { requestHandler, errorHandler } = Sentry.Handlers; + app.use(requestHandler()); + app.use(errorHandler()); + } + + initialized = true; +}; + +export const captureError = Sentry.captureException; diff --git a/packages/cli/src/LoadNodesAndCredentials.ts b/packages/cli/src/LoadNodesAndCredentials.ts index 29ae116441ffd..dc2730a683e93 100644 --- a/packages/cli/src/LoadNodesAndCredentials.ts +++ b/packages/cli/src/LoadNodesAndCredentials.ts @@ -45,6 +45,7 @@ import { persistInstalledPackageData, removePackageFromDatabase, } from './CommunityNodes/packageModel'; +import { captureError } from './ErrorHandling'; const CUSTOM_NODES_CATEGORY = 'Custom Nodes'; @@ -131,13 +132,17 @@ class LoadNodesAndCredentialsClass { const downloadedPackages = await this.getN8nNodePackages(downloadedNodesFolderModules); nodePackages.push(...downloadedPackages); // eslint-disable-next-line no-empty - } catch (error) {} + } catch (error) { + captureError(error); + } for (const packagePath of nodePackages) { try { await this.loadDataFromPackage(packagePath); // eslint-disable-next-line no-empty - } catch (error) {} + } catch (error) { + captureError(error); + } } } diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 514eddde859ac..63cfe2e4dc477 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -82,6 +82,7 @@ import parseUrl from 'parseurl'; import promClient, { Registry } from 'prom-client'; import history from 'connect-history-api-fallback'; import bodyParser from 'body-parser'; + import config from '../config'; import * as Queue from './Queue'; @@ -153,6 +154,7 @@ import glob from 'fast-glob'; import { ResponseError } from './ResponseHelper'; import { toHttpNodeParameters } from './CurlConverterHelper'; +import { initErrorHandling, captureError } from './ErrorHandling'; require('body-parser-xml')(bodyParser); @@ -271,6 +273,8 @@ class App { } telemetrySettings.config = { key, url }; + + initErrorHandling(this.app); } // Define it here to avoid calling the function multiple times @@ -747,6 +751,7 @@ class App { // DB ping await connection.query('SELECT 1'); } catch (err) { + captureError(err); LoggerProxy.error('No Database connection!', err); const error = new ResponseHelper.ResponseError('No Database connection!', undefined, 503); return ResponseHelper.sendErrorResponse(res, error); diff --git a/packages/cli/src/UserManagement/email/NodeMailer.ts b/packages/cli/src/UserManagement/email/NodeMailer.ts index c73f2803f2383..ddf07a0cf5c59 100644 --- a/packages/cli/src/UserManagement/email/NodeMailer.ts +++ b/packages/cli/src/UserManagement/email/NodeMailer.ts @@ -3,6 +3,7 @@ import { createTransport, Transporter } from 'nodemailer'; import { LoggerProxy as Logger } from 'n8n-workflow'; import * as config from '../../../config'; import { MailData, SendEmailResult, UserManagementMailerImplementation } from './Interfaces'; +import { captureError } from './ErrorHandling'; export class NodeMailer implements UserManagementMailerImplementation { private transport: Transporter; @@ -62,6 +63,7 @@ export class NodeMailer implements UserManagementMailerImplementation { `Email sent successfully to the following recipients: ${mailData.emailRecipients.toString()}`, ); } catch (error) { + captureError(error); Logger.error('Failed to send email', { recipients: mailData.emailRecipients, error }); return { success: false, diff --git a/packages/cli/src/UserManagement/routes/users.ts b/packages/cli/src/UserManagement/routes/users.ts index 56b8e685d2371..c8638754710e8 100644 --- a/packages/cli/src/UserManagement/routes/users.ts +++ b/packages/cli/src/UserManagement/routes/users.ts @@ -23,6 +23,7 @@ import { import * as config from '../../../config'; import { issueCookie } from '../auth/jwt'; +import { captureError } from '../../ErrorHandling'; export function usersNamespace(this: N8nApp): void { /** @@ -159,6 +160,7 @@ export function usersNamespace(this: N8nApp): void { public_api: false, }); } catch (error) { + captureError(error); Logger.error('Failed to create user shells', { userShells: createUsers }); throw new ResponseHelper.ResponseError('An error occurred during user creation'); } diff --git a/packages/cli/src/WaitTracker.ts b/packages/cli/src/WaitTracker.ts index 51804aae6a030..9db74a0c80a79 100644 --- a/packages/cli/src/WaitTracker.ts +++ b/packages/cli/src/WaitTracker.ts @@ -6,8 +6,7 @@ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-floating-promises */ // eslint-disable-next-line @typescript-eslint/no-unused-vars -import { IRun, LoggerProxy as Logger, WorkflowOperationError } from 'n8n-workflow'; - +import { LoggerProxy as Logger, WorkflowOperationError } from 'n8n-workflow'; import { FindManyOptions, LessThanOrEqual, ObjectLiteral } from 'typeorm'; import { DateUtils } from 'typeorm/util/DateUtils'; @@ -20,11 +19,10 @@ import { IExecutionsStopData, IWorkflowExecutionDataProcess, ResponseHelper, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - WorkflowCredentials, WorkflowRunner, } from '.'; import { getWorkflowOwner } from './UserManagement/UserManagementHelper'; +import { captureError } from './ErrorHandling'; export class WaitTrackerClass { activeExecutionsInstance: ActiveExecutions.ActiveExecutions; @@ -174,6 +172,7 @@ export class WaitTrackerClass { const workflowRunner = new WorkflowRunner(); await workflowRunner.run(data, false, false, executionId); })().catch((error) => { + captureError(error); Logger.error( `There was a problem starting the waiting execution with id "${executionId}": "${error.message}"`, { executionId }, diff --git a/packages/cli/src/WebhookHelpers.ts b/packages/cli/src/WebhookHelpers.ts index d50b86243f3d5..e93abe40556b7 100644 --- a/packages/cli/src/WebhookHelpers.ts +++ b/packages/cli/src/WebhookHelpers.ts @@ -58,6 +58,7 @@ import * as ActiveExecutions from './ActiveExecutions'; import { User } from './databases/entities/User'; import { WorkflowEntity } from './databases/entities/WorkflowEntity'; import { getWorkflowOwner } from './UserManagement/UserManagementHelper'; +import { captureError } from './ErrorHandling'; const activeExecutions = ActiveExecutions.getInstance(); @@ -434,6 +435,7 @@ export async function executeWebhook( didSendResponse = true; }) .catch(async (error) => { + captureError(error); Logger.error( `Error with Webhook-Response for execution "${executionId}": "${error.message}"`, { executionId, workflowId: workflow.id }, diff --git a/packages/cli/src/WebhookServer.ts b/packages/cli/src/WebhookServer.ts index d73e62bcad1d4..ab3b533e0983d 100644 --- a/packages/cli/src/WebhookServer.ts +++ b/packages/cli/src/WebhookServer.ts @@ -33,6 +33,7 @@ import { import config from '../config'; // eslint-disable-next-line import/no-cycle import { WEBHOOK_METHODS } from './WebhookHelpers'; +import { initErrorHandling } from './ErrorHandling'; // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-call require('body-parser-xml')(bodyParser); @@ -217,6 +218,8 @@ class App { this.presetCredentialsLoaded = false; this.endpointPresetCredentials = config.getEnv('credentials.overwrite.endpoint'); + + initErrorHandling(this.app); } /** diff --git a/packages/cli/src/WorkflowExecuteAdditionalData.ts b/packages/cli/src/WorkflowExecuteAdditionalData.ts index 3439e289e2eaa..f62f5ff26d759 100644 --- a/packages/cli/src/WorkflowExecuteAdditionalData.ts +++ b/packages/cli/src/WorkflowExecuteAdditionalData.ts @@ -69,6 +69,7 @@ import { import { whereClause } from './WorkflowHelpers'; import { IWorkflowErrorData } from './Interfaces'; import { findSubworkflowStart } from './utils'; +import { captureError } from './ErrorHandling'; const ERROR_TRIGGER_TYPE = config.getEnv('nodes.errorTriggerType'); @@ -163,6 +164,7 @@ export function executeErrorWorkflow( ); }) .catch((error) => { + captureError(error); Logger.error( `Could not execute ErrorWorkflow for execution ID ${this.executionId} because of error querying the workflow owner`, { @@ -221,6 +223,7 @@ function pruneExecutionData(this: WorkflowHooks): void { .catch((error) => { throttling = false; + captureError(error); Logger.error( `Failed pruning execution data from database for execution ID ${this.executionId} (hookFunctionsSave)`, { @@ -451,6 +454,7 @@ export function hookFunctionsPreExecute(parentProcessMode?: string): IWorkflowEx flattenedExecutionData as IExecutionFlattedDb, ); } catch (err) { + captureError(err); // TODO: Improve in the future! // Errors here might happen because of database access // For busy machines, we may get "Database is locked" errors. @@ -511,6 +515,7 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks { newStaticData, ); } catch (e) { + captureError(e); Logger.error( `There was a problem saving the workflow with id "${this.workflowData.id}" to save changed staticData: "${e.message}" (hookFunctionsSave)`, { executionId: this.executionId, workflowId: this.workflowData.id }, @@ -629,6 +634,7 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks { ); } } catch (error) { + captureError(error); Logger.error(`Failed saving execution data to DB on execution ID ${this.executionId}`, { executionId: this.executionId, workflowId: this.workflowData.id, @@ -676,6 +682,7 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks { newStaticData, ); } catch (e) { + captureError(e); Logger.error( `There was a problem saving the workflow with id "${this.workflowData.id}" to save changed staticData: "${e.message}" (workflowExecuteAfter)`, { sessionId: this.sessionId, workflowId: this.workflowData.id }, diff --git a/packages/cli/src/WorkflowHelpers.ts b/packages/cli/src/WorkflowHelpers.ts index 37bc881d05ffa..eeecd386a6631 100644 --- a/packages/cli/src/WorkflowHelpers.ts +++ b/packages/cli/src/WorkflowHelpers.ts @@ -44,6 +44,7 @@ import config from '../config'; import { WorkflowEntity } from './databases/entities/WorkflowEntity'; import { User } from './databases/entities/User'; import { getWorkflowOwner } from './UserManagement/UserManagementHelper'; +import { captureError } from './ErrorHandling'; const ERROR_TRIGGER_TYPE = config.getEnv('nodes.errorTriggerType'); @@ -232,6 +233,7 @@ export async function executeErrorWorkflow( const workflowRunner = new WorkflowRunner(); await workflowRunner.run(runData); } catch (error) { + captureError(error); Logger.error( `Calling Error Workflow for "${workflowErrorData.workflow.id}": "${error.message}"`, { workflowId: workflowErrorData.workflow.id }, @@ -408,6 +410,7 @@ export async function saveStaticData(workflow: Workflow): Promise { await saveStaticDataById(workflow.id!, workflow.staticData); workflow.staticData.__dataChanged = false; } catch (e) { + captureError(e); Logger.error( `There was a problem saving the workflow with id "${workflow.id}" to save changed staticData: "${e.message}"`, { workflowId: workflow.id }, diff --git a/packages/cli/src/WorkflowRunner.ts b/packages/cli/src/WorkflowRunner.ts index a5e6d2bdf8ede..8ee83651914d6 100644 --- a/packages/cli/src/WorkflowRunner.ts +++ b/packages/cli/src/WorkflowRunner.ts @@ -56,6 +56,7 @@ import * as Queue from './Queue'; import { InternalHooksManager } from './InternalHooksManager'; import { checkPermissionsForExecution } from './UserManagement/UserManagementHelper'; import { generateFailedExecutionFromError } from './WorkflowHelpers'; +import { initErrorHandling, captureError } from './ErrorHandling'; export class WorkflowRunner { activeExecutions: ActiveExecutions.ActiveExecutions; @@ -76,6 +77,8 @@ export class WorkflowRunner { if (executionsMode === 'queue') { this.jobQueue = Queue.getInstance().getBullObjectInstance(); } + + initErrorHandling(); } /** @@ -98,6 +101,8 @@ export class WorkflowRunner { executionId: string, hooks?: WorkflowHooks, ) { + captureError(error); + const fullRunData: IRun = { data: { resultData: { @@ -170,6 +175,7 @@ export class WorkflowRunner { }) .catch((error) => { console.error('There was a problem running internal hook "onWorkflowPostExecute"', error); + captureError(error); }); if (externalHooks.exists('workflow.postExecute')) { @@ -183,6 +189,7 @@ export class WorkflowRunner { }) .catch((error) => { console.error('There was a problem running hook "workflow.postExecute"', error); + captureError(error); }); } @@ -263,6 +270,7 @@ export class WorkflowRunner { try { await checkPermissionsForExecution(workflow, data.userId); } catch (error) { + captureError(error); // Create a failed execution with the data for the node // save it and abort execution const failedExecution = generateFailedExecutionFromError( @@ -503,6 +511,7 @@ export class WorkflowRunner { clearWatchdogInterval(); } } catch (error) { + captureError(error); // We use "getWorkflowHooksWorkerExecuter" as "getWorkflowHooksWorkerMain" does not contain the // "workflowExecuteAfter" which we require. const hooks = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter( diff --git a/packages/cli/src/WorkflowRunnerProcess.ts b/packages/cli/src/WorkflowRunnerProcess.ts index 8f0ff4a9b0af9..fa87ad537c017 100644 --- a/packages/cli/src/WorkflowRunnerProcess.ts +++ b/packages/cli/src/WorkflowRunnerProcess.ts @@ -52,6 +52,7 @@ import { InternalHooksManager } from './InternalHooksManager'; import { checkPermissionsForExecution } from './UserManagement/UserManagementHelper'; import { loadClassInIsolation } from './CommunityNodes/helpers'; import { generateFailedExecutionFromError } from './WorkflowHelpers'; +import { captureError, initErrorHandling } from './ErrorHandling'; export class WorkflowRunnerProcess { data: IWorkflowExecutionDataProcessWithExecution | undefined; @@ -79,6 +80,10 @@ export class WorkflowRunnerProcess { }, 30000); } + constructor() { + initErrorHandling(); + } + async runWorkflow(inputData: IWorkflowExecutionDataProcessWithExecution): Promise { process.on('SIGTERM', WorkflowRunnerProcess.stopProcess); process.on('SIGINT', WorkflowRunnerProcess.stopProcess); @@ -265,6 +270,7 @@ export class WorkflowRunnerProcess { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment await sendToParentProcess('sendMessageToUI', { source, message }); } catch (error) { + captureError(error); this.logger.error( // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access `There was a problem sending UI data to parent process: "${error.message}"`, @@ -402,6 +408,7 @@ export class WorkflowRunnerProcess { parameters, }); } catch (error) { + captureError(error); this.logger.error(`There was a problem sending hook: "${hook}"`, { parameters, error }); } }