Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(API): Set up error tracking using Sentry #4394

Merged
merged 24 commits into from
Nov 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
42682ef
feat(cli): Setup error tracking using Sentry
netroy Oct 20, 2022
625a35d
Merge remote-tracking branch 'origin/master' into N8N-4727-setup-sentry
netroy Oct 28, 2022
94b9566
make error reporting available in the workflows package
netroy Oct 28, 2022
0e428e9
Merge remote-tracking branch 'origin/master' into N8N-4727-setup-sentry
netroy Oct 31, 2022
9c2c66f
address some of the PR comments
netroy Oct 31, 2022
a3d5f12
create a ErrorReporterProxy like LoggerProxy
netroy Nov 2, 2022
d827184
Merge remote-tracking branch 'origin/master' into N8N-4727-setup-sentry
netroy Nov 2, 2022
edfc6b1
remove the `captureError` helper. use ErrorReporterProxy directly
netroy Nov 2, 2022
399ef72
Merge remote-tracking branch 'origin/master' into N8N-4727-setup-sentry
netroy Nov 2, 2022
626ea22
fix linting issues
netroy Nov 2, 2022
aeff61d
remove ErrorReporterProxy warnings in tests
netroy Nov 2, 2022
c9ea923
Merge remote-tracking branch 'origin/master' into N8N-4727-setup-sentry
netroy Nov 2, 2022
ad0ceea
Merge remote-tracking branch 'origin/master' into N8N-4727-setup-sentry
netroy Nov 2, 2022
51207f3
check for NODE_ENV === 'production' instead
netroy Nov 2, 2022
fe076bc
IErrorReporter -> ErrorReporter
netroy Nov 2, 2022
da2cf67
ErrorReporterProxy.getInstance() -> ErrorReporter
netroy Nov 2, 2022
77a0dbd
allow capturing stacks in warnings as well
netroy Nov 2, 2022
1f6d27c
Merge remote-tracking branch 'origin/master' into N8N-4727-setup-sentry
netroy Nov 3, 2022
4dd816c
make n8n debugging consistent with `npm start`
netroy Nov 3, 2022
4644fc3
Merge remote-tracking branch 'origin/master' into N8N-4727-setup-sentry
netroy Nov 3, 2022
b611741
IReportingOptions -> ReportingOptions
netroy Nov 3, 2022
7f70ffc
use consistent signature for `error` and `warn`
netroy Nov 4, 2022
857b7bd
use Logger instead of console.log
netroy Nov 4, 2022
19d5b3a
Merge remote-tracking branch 'origin/master' into N8N-4727-setup-sentry
netroy Nov 4, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
{
"name": "Launch n8n with debug",
"program": "${workspaceFolder}/packages/cli/bin/n8n",
"cwd": "${workspaceFolder}/packages/cli/bin",
"request": "launch",
"skipFiles": ["<node_internals>/**"],
"type": "node",
Expand Down
1 change: 1 addition & 0 deletions docker/images/n8n-debian/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docker/images/n8n-rhel7/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 3 additions & 2 deletions docker/images/n8n/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48,207 changes: 27,569 additions & 20,638 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions packages/cli/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,15 @@ export const schema = {
env: 'N8N_DIAGNOSTICS_POSTHOG_DISABLE_RECORDING',
},
},
sentry: {
dsn: {
doc: 'Data source name for error tracking on Sentry',
format: String,
default:
'https://1f954e089a054b8e943ae4f4042b2bff@o1420875.ingest.sentry.io/4504016528408576',
env: 'N8N_SENTRY_DSN',
},
},
frontend: {
doc: 'Diagnostics config for frontend.',
format: String,
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@
"@oclif/core": "^1.9.3",
"@oclif/errors": "^1.2.2",
"@rudderstack/rudder-sdk-node": "1.0.6",
"@sentry/node": "^7.17.3",
"@sentry/integrations": "^7.17.3",
"axios": "^0.21.1",
"basic-auth": "^2.0.1",
"bcryptjs": "^2.4.3",
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/ActiveWorkflowRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
WorkflowActivationError,
WorkflowExecuteMode,
LoggerProxy as Logger,
ErrorReporterProxy as ErrorReporter,
} from 'n8n-workflow';

import express from 'express';
Expand Down Expand Up @@ -121,6 +122,7 @@ export class ActiveWorkflowRunner {
});
console.log(` => Started`);
} catch (error) {
ErrorReporter.error(error);
console.log(
` => ERROR: Workflow could not be activated on first try, keep on trying`,
);
Expand Down Expand Up @@ -881,6 +883,7 @@ export class ActiveWorkflowRunner {
try {
await this.add(workflowId, activationMode, workflowData);
} catch (error) {
ErrorReporter.error(error);
let lastTimeout = this.queuedWorkflowActivations[workflowId].lastTimeout;
if (lastTimeout < WORKFLOW_REACTIVATE_MAX_TIMEOUT) {
lastTimeout = Math.min(lastTimeout * 2, WORKFLOW_REACTIVATE_MAX_TIMEOUT);
Expand Down Expand Up @@ -948,6 +951,7 @@ export class ActiveWorkflowRunner {
try {
await this.removeWorkflowWebhooks(workflowId);
} catch (error) {
ErrorReporter.error(error);
console.error(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`Could not remove webhooks of workflow "${workflowId}" because of error: "${error.message}"`,
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/CommunityNodes/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const executeCommand = async (
command: string,
options?: { doNotHandleError?: boolean },
): Promise<string> => {
const downloadFolder = UserSettings.getUserN8nFolderDowloadedNodesPath();
const downloadFolder = UserSettings.getUserN8nFolderDownloadedNodesPath();

const execOptions = {
cwd: downloadFolder,
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/CredentialsHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
WorkflowExecuteMode,
ITaskDataConnections,
LoggerProxy as Logger,
ErrorReporterProxy as ErrorReporter,
IHttpRequestHelper,
} from 'n8n-workflow';

Expand Down Expand Up @@ -672,6 +673,7 @@ export class CredentialsHelper extends ICredentialsHelper {
credentialsDecrypted,
);
} catch (error) {
ErrorReporter.error(error);
// Do not fail any requests to allow custom error messages and
// make logic easier
if (error.cause?.response) {
Expand Down
41 changes: 41 additions & 0 deletions packages/cli/src/ErrorReporting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as Sentry from '@sentry/node';
import { RewriteFrames } from '@sentry/integrations';
import type { Application } from 'express';
import config from '../config';
import { ErrorReporterProxy } from 'n8n-workflow';

let initialized = false;

export const initErrorHandling = (app?: Application) => {
if (initialized) return;

if (!config.getEnv('diagnostics.enabled')) {
initialized = true;
return;
}

const dsn = config.getEnv('diagnostics.config.sentry.dsn');
const { N8N_VERSION: release, ENVIRONMENT: environment } = process.env;

Sentry.init({
dsn,
release,
environment,
integrations: (integrations) => {
integrations.push(new RewriteFrames({ root: process.cwd() }));
return integrations;
},
});

if (app) {
const { requestHandler, errorHandler } = Sentry.Handlers;
app.use(requestHandler());
app.use(errorHandler());
}

ErrorReporterProxy.init({
report: (error, options) => Sentry.captureException(error, options),
});

initialized = true;
};
17 changes: 11 additions & 6 deletions packages/cli/src/LoadNodesAndCredentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
IVersionedNodeType,
LoggerProxy,
jsonParse,
ErrorReporterProxy as ErrorReporter,
} from 'n8n-workflow';

import {
Expand Down Expand Up @@ -125,19 +126,23 @@ class LoadNodesAndCredentialsClass {
const nodePackages = [];
try {
// Read downloaded nodes and credentials
const downloadedNodesFolder = UserSettings.getUserN8nFolderDowloadedNodesPath();
const downloadedNodesFolder = UserSettings.getUserN8nFolderDownloadedNodesPath();
const downloadedNodesFolderModules = path.join(downloadedNodesFolder, 'node_modules');
await fsAccess(downloadedNodesFolderModules);
const downloadedPackages = await this.getN8nNodePackages(downloadedNodesFolderModules);
nodePackages.push(...downloadedPackages);
// eslint-disable-next-line no-empty
} catch (error) {}
} catch (error) {
// Folder does not exist so ignore and return
return;
netroy marked this conversation as resolved.
Show resolved Hide resolved
}

for (const packagePath of nodePackages) {
try {
await this.loadDataFromPackage(packagePath);
// eslint-disable-next-line no-empty
} catch (error) {}
} catch (error) {
ErrorReporter.error(error);
}
}
}

Expand Down Expand Up @@ -231,7 +236,7 @@ class LoadNodesAndCredentialsClass {
}

async loadNpmModule(packageName: string, version?: string): Promise<InstalledPackages> {
const downloadFolder = UserSettings.getUserN8nFolderDowloadedNodesPath();
const downloadFolder = UserSettings.getUserN8nFolderDownloadedNodesPath();
const command = `npm install ${packageName}${version ? `@${version}` : ''}`;

await executeCommand(command);
Expand Down Expand Up @@ -285,7 +290,7 @@ class LoadNodesAndCredentialsClass {
packageName: string,
installedPackage: InstalledPackages,
): Promise<InstalledPackages> {
const downloadFolder = UserSettings.getUserN8nFolderDowloadedNodesPath();
const downloadFolder = UserSettings.getUserN8nFolderDownloadedNodesPath();

const command = `npm i ${packageName}@latest`;

Expand Down
9 changes: 7 additions & 2 deletions packages/cli/src/ResponseHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Request, Response } from 'express';
import { parse, stringify } from 'flatted';
import { ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';

// eslint-disable-next-line import/no-cycle
import {
Expand Down Expand Up @@ -154,8 +155,12 @@ export function send<T, R extends Request, S extends Response>(

sendSuccessResponse(res, data, raw);
} catch (error) {
if (error instanceof Error && isUniqueConstraintError(error)) {
error.message = 'There is already an entry with this name';
if (error instanceof Error) {
ErrorReporter.error(error);

if (isUniqueConstraintError(error)) {
error.message = 'There is already an entry with this name';
}
ivov marked this conversation as resolved.
Show resolved Hide resolved
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
Expand Down
6 changes: 6 additions & 0 deletions packages/cli/src/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import {
jsonParse,
WebhookHttpMethod,
WorkflowExecuteMode,
ErrorReporterProxy as ErrorReporter,
} from 'n8n-workflow';

import basicAuth from 'basic-auth';
Expand All @@ -82,6 +83,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';

Expand Down Expand Up @@ -153,6 +155,7 @@ import glob from 'fast-glob';
import { ResponseError } from './ResponseHelper';

import { toHttpNodeParameters } from './CurlConverterHelper';
import { initErrorHandling } from './ErrorReporting';

require('body-parser-xml')(bodyParser);

Expand Down Expand Up @@ -256,6 +259,8 @@ class App {
this.presetCredentialsLoaded = false;
this.endpointPresetCredentials = config.getEnv('credentials.overwrite.endpoint');

initErrorHandling(this.app);

const urlBaseWebhook = WebhookHelpers.getWebhookBaseUrl();
const telemetrySettings: ITelemetrySettings = {
enabled: config.getEnv('diagnostics.enabled'),
Expand Down Expand Up @@ -742,6 +747,7 @@ class App {
// DB ping
await connection.query('SELECT 1');
} catch (err) {
ErrorReporter.error(err);
LoggerProxy.error('No Database connection!', err);
const error = new ResponseHelper.ResponseError('No Database connection!', undefined, 503);
return ResponseHelper.sendErrorResponse(res, error);
Expand Down
28 changes: 11 additions & 17 deletions packages/cli/src/UserManagement/email/NodeMailer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { createTransport, Transporter } from 'nodemailer';
import { LoggerProxy as Logger } from 'n8n-workflow';
import { ErrorReporterProxy as ErrorReporter, LoggerProxy as Logger } from 'n8n-workflow';
import * as config from '../../../config';
import { MailData, SendEmailResult, UserManagementMailerImplementation } from './Interfaces';

Expand All @@ -24,22 +24,15 @@ export class NodeMailer implements UserManagementMailerImplementation {
const user = config.getEnv('userManagement.emails.smtp.auth.user');
const pass = config.getEnv('userManagement.emails.smtp.auth.pass');

return new Promise((resolve, reject) => {
this.transport.verify((error: Error) => {
if (!error) {
resolve();
return;
}

const message = [];

if (!host) message.push('SMTP host not defined (N8N_SMTP_HOST).');
if (!user) message.push('SMTP user not defined (N8N_SMTP_USER).');
if (!pass) message.push('SMTP pass not defined (N8N_SMTP_PASS).');

reject(new Error(message.length ? message.join(' ') : error.message));
});
});
try {
await this.transport.verify();
} catch (error) {
const message: string[] = [];
if (!host) message.push('SMTP host not defined (N8N_SMTP_HOST).');
if (!user) message.push('SMTP user not defined (N8N_SMTP_USER).');
if (!pass) message.push('SMTP pass not defined (N8N_SMTP_PASS).');
throw message.length ? new Error(message.join(' '), { cause: error }) : error;
}
}

async sendMail(mailData: MailData): Promise<SendEmailResult> {
Expand All @@ -62,6 +55,7 @@ export class NodeMailer implements UserManagementMailerImplementation {
`Email sent successfully to the following recipients: ${mailData.emailRecipients.toString()}`,
);
} catch (error) {
ErrorReporter.error(error);
Logger.error('Failed to send email', { recipients: mailData.emailRecipients, error });
return {
success: false,
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/UserManagement/routes/users.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable no-restricted-syntax */
/* eslint-disable import/no-cycle */
import { Response } from 'express';
import { LoggerProxy as Logger } from 'n8n-workflow';
import { ErrorReporterProxy as ErrorReporter, LoggerProxy as Logger } from 'n8n-workflow';
import { In } from 'typeorm';
import validator from 'validator';

Expand Down Expand Up @@ -159,6 +159,7 @@ export function usersNamespace(this: N8nApp): void {
public_api: false,
});
} catch (error) {
ErrorReporter.error(error);
Logger.error('Failed to create user shells', { userShells: createUsers });
throw new ResponseHelper.ResponseError('An error occurred during user creation');
}
Expand Down
12 changes: 7 additions & 5 deletions packages/cli/src/WaitTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
/* 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 {
ErrorReporterProxy as ErrorReporter,
LoggerProxy as Logger,
WorkflowOperationError,
} from 'n8n-workflow';
import { FindManyOptions, LessThanOrEqual, ObjectLiteral } from 'typeorm';

import { DateUtils } from 'typeorm/util/DateUtils';
Expand All @@ -20,8 +23,6 @@ import {
IExecutionsStopData,
IWorkflowExecutionDataProcess,
ResponseHelper,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
WorkflowCredentials,
WorkflowRunner,
} from '.';
import { getWorkflowOwner } from './UserManagement/UserManagementHelper';
Expand Down Expand Up @@ -173,7 +174,8 @@ export class WaitTrackerClass {
// Start the execution again
const workflowRunner = new WorkflowRunner();
await workflowRunner.run(data, false, false, executionId);
})().catch((error) => {
})().catch((error: Error) => {
ErrorReporter.error(error);
Logger.error(
`There was a problem starting the waiting execution with id "${executionId}": "${error.message}"`,
{ executionId },
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/WebhookHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
IWebhookResponseData,
IWorkflowDataProxyAdditionalKeys,
IWorkflowExecuteAdditionalData,
ErrorReporterProxy as ErrorReporter,
LoggerProxy as Logger,
NodeHelpers,
Workflow,
Expand Down Expand Up @@ -434,6 +435,7 @@ export async function executeWebhook(
didSendResponse = true;
})
.catch(async (error) => {
ErrorReporter.error(error);
Logger.error(
`Error with Webhook-Response for execution "${executionId}": "${error.message}"`,
{ executionId, workflowId: workflow.id },
Expand Down
Loading