fix: Report app startup and DB migration errors to Sentry (#5127)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2023-01-11 18:28:35 +01:00 committed by GitHub
parent 3c109ffab1
commit a573db2ef7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 74 additions and 79 deletions

View file

@ -1,9 +1,5 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/await-thenable */ /* eslint-disable @typescript-eslint/await-thenable */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/unbound-method */ /* eslint-disable @typescript-eslint/unbound-method */
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
import path from 'path'; import path from 'path';
@ -17,7 +13,7 @@ import replaceStream from 'replacestream';
import { promisify } from 'util'; import { promisify } from 'util';
import glob from 'fast-glob'; import glob from 'fast-glob';
import { LoggerProxy, sleep } from 'n8n-workflow'; import { LoggerProxy, ErrorReporterProxy as ErrorReporter, sleep } from 'n8n-workflow';
import { createHash } from 'crypto'; import { createHash } from 'crypto';
import config from '@/config'; import config from '@/config';
@ -49,7 +45,20 @@ const open = require('open');
const pipeline = promisify(stream.pipeline); const pipeline = promisify(stream.pipeline);
let activeWorkflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner | undefined; let activeWorkflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner | undefined;
let processExitCode = 0;
const exitWithCrash = async (message: string, error: unknown) => {
ErrorReporter.error(new Error(message, { cause: error }), { level: 'fatal' });
await sleep(2000);
process.exit(1);
};
const exitSuccessFully = async () => {
try {
await CrashJournal.cleanup();
} finally {
process.exit();
}
};
export class Start extends Command { export class Start extends Command {
static description = 'Starts n8n. Makes Web-UI available and starts active workflows'; static description = 'Starts n8n. Makes Web-UI available and starts active workflows';
@ -101,12 +110,6 @@ export class Start extends Command {
static async stopProcess() { static async stopProcess() {
getLogger().info('\nStopping n8n...'); getLogger().info('\nStopping n8n...');
const exit = () => {
CrashJournal.cleanup().finally(() => {
process.exit(processExitCode);
});
};
try { try {
// Stop with trying to activate workflows that could not be activated // Stop with trying to activate workflows that could not be activated
activeWorkflowRunner?.removeAllQueuedWorkflowActivations(); activeWorkflowRunner?.removeAllQueuedWorkflowActivations();
@ -114,11 +117,11 @@ export class Start extends Command {
const externalHooks = ExternalHooks(); const externalHooks = ExternalHooks();
await externalHooks.run('n8n.stop', []); await externalHooks.run('n8n.stop', []);
setTimeout(() => { setTimeout(async () => {
// In case that something goes wrong with shutdown we // In case that something goes wrong with shutdown we
// kill after max. 30 seconds no matter what // kill after max. 30 seconds no matter what
console.log('process exited after 30s'); console.log('process exited after 30s');
exit(); await exitSuccessFully();
}, 30000); }, 30000);
await InternalHooksManager.getInstance().onN8nStop(); await InternalHooksManager.getInstance().onN8nStop();
@ -159,10 +162,10 @@ export class Start extends Command {
//finally shut down Event Bus //finally shut down Event Bus
await eventBus.close(); await eventBus.close();
} catch (error) { } catch (error) {
console.error('There was an error shutting down n8n.', error); await exitWithCrash('There was an error shutting down n8n.', error);
} }
exit(); await exitSuccessFully();
} }
static async generateStaticAssets() { static async generateStaticAssets() {
@ -236,12 +239,9 @@ export class Start extends Command {
try { try {
// Start directly with the init of the database to improve startup time // Start directly with the init of the database to improve startup time
const startDbInitPromise = Db.init().catch((error: Error) => { const startDbInitPromise = Db.init().catch(async (error: Error) =>
logger.error(`There was an error initializing DB: "${error.message}"`); exitWithCrash('There was an error initializing DB', error),
);
processExitCode = 1;
process.emit('exit', processExitCode);
});
// Make sure the settings exist // Make sure the settings exist
const userSettings = await UserSettings.prepareUserSettings(); const userSettings = await UserSettings.prepareUserSettings();
@ -456,9 +456,7 @@ export class Start extends Command {
}); });
} }
} catch (error) { } catch (error) {
console.error('There was an error', error); await exitWithCrash('There was an error', error);
processExitCode = 1;
process.emit('exit', processExitCode);
} }
} }
} }

View file

@ -1,13 +1,8 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/unbound-method */ /* eslint-disable @typescript-eslint/unbound-method */
import { BinaryDataManager, UserSettings } from 'n8n-core'; import { BinaryDataManager, UserSettings } from 'n8n-core';
import { Command, flags } from '@oclif/command'; import { Command, flags } from '@oclif/command';
import { LoggerProxy, sleep } from 'n8n-workflow'; import { LoggerProxy, ErrorReporterProxy as ErrorReporter, sleep } from 'n8n-workflow';
import config from '@/config'; import config from '@/config';
import * as ActiveExecutions from '@/ActiveExecutions'; import * as ActiveExecutions from '@/ActiveExecutions';
import { CredentialsOverwrites } from '@/CredentialsOverwrites'; import { CredentialsOverwrites } from '@/CredentialsOverwrites';
@ -22,7 +17,19 @@ import { getLogger } from '@/Logger';
import { initErrorHandling } from '@/ErrorReporting'; import { initErrorHandling } from '@/ErrorReporting';
import * as CrashJournal from '@/CrashJournal'; import * as CrashJournal from '@/CrashJournal';
let processExitCode = 0; const exitWithCrash = async (message: string, error: unknown) => {
ErrorReporter.error(new Error(message, { cause: error }), { level: 'fatal' });
await sleep(2000);
process.exit(1);
};
const exitSuccessFully = async () => {
try {
await CrashJournal.cleanup();
} finally {
process.exit();
}
};
export class Webhook extends Command { export class Webhook extends Command {
static description = 'Starts n8n webhook process. Intercepts only production URLs.'; static description = 'Starts n8n webhook process. Intercepts only production URLs.';
@ -42,20 +49,14 @@ export class Webhook extends Command {
static async stopProcess() { static async stopProcess() {
LoggerProxy.info('\nStopping n8n...'); LoggerProxy.info('\nStopping n8n...');
const exit = () => {
CrashJournal.cleanup().finally(() => {
process.exit(processExitCode);
});
};
try { try {
const externalHooks = ExternalHooks(); const externalHooks = ExternalHooks();
await externalHooks.run('n8n.stop', []); await externalHooks.run('n8n.stop', []);
setTimeout(() => { setTimeout(async () => {
// In case that something goes wrong with shutdown we // In case that something goes wrong with shutdown we
// kill after max. 30 seconds no matter what // kill after max. 30 seconds no matter what
exit(); await exitSuccessFully();
}, 30000); }, 30000);
// Wait for active workflow executions to finish // Wait for active workflow executions to finish
@ -74,10 +75,10 @@ export class Webhook extends Command {
executingWorkflows = activeExecutionsInstance.getActiveExecutions(); executingWorkflows = activeExecutionsInstance.getActiveExecutions();
} }
} catch (error) { } catch (error) {
LoggerProxy.error('There was an error shutting down n8n.', error); await exitWithCrash('There was an error shutting down n8n.', error);
} }
exit(); await exitSuccessFully();
} }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
@ -110,13 +111,9 @@ export class Webhook extends Command {
try { try {
// Start directly with the init of the database to improve startup time // Start directly with the init of the database to improve startup time
const startDbInitPromise = Db.init().catch((error) => { const startDbInitPromise = Db.init().catch(async (error: Error) =>
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access exitWithCrash('There was an error initializing DB', error),
logger.error(`There was an error initializing DB: "${error.message}"`); );
processExitCode = 1;
process.emit('exit', processExitCode);
});
// Make sure the settings exist // Make sure the settings exist
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
@ -151,9 +148,7 @@ export class Webhook extends Command {
console.info('Webhook listener waiting for requests.'); console.info('Webhook listener waiting for requests.');
} catch (error) { } catch (error) {
console.error('Exiting due to error.', error); await exitWithCrash('Exiting due to error.', error);
processExitCode = 1;
process.emit('exit', processExitCode);
} }
} }
} }

View file

@ -1,12 +1,8 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-shadow */ /* eslint-disable @typescript-eslint/no-shadow */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/unbound-method */ /* eslint-disable @typescript-eslint/unbound-method */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unused-vars */
import express from 'express'; import express from 'express';
import http from 'http'; import http from 'http';
import PCancelable from 'p-cancelable'; import PCancelable from 'p-cancelable';
@ -20,6 +16,7 @@ import {
IRun, IRun,
Workflow, Workflow,
LoggerProxy, LoggerProxy,
ErrorReporterProxy as ErrorReporter,
sleep, sleep,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -29,7 +26,6 @@ import { CredentialsOverwrites } from '@/CredentialsOverwrites';
import { CredentialTypes } from '@/CredentialTypes'; import { CredentialTypes } from '@/CredentialTypes';
import * as Db from '@/Db'; import * as Db from '@/Db';
import { ExternalHooks } from '@/ExternalHooks'; import { ExternalHooks } from '@/ExternalHooks';
import * as GenericHelpers from '@/GenericHelpers';
import { NodeTypes } from '@/NodeTypes'; import { NodeTypes } from '@/NodeTypes';
import * as ResponseHelper from '@/ResponseHelper'; import * as ResponseHelper from '@/ResponseHelper';
import * as WebhookHelpers from '@/WebhookHelpers'; import * as WebhookHelpers from '@/WebhookHelpers';
@ -41,9 +37,25 @@ import { PermissionChecker } from '@/UserManagement/PermissionChecker';
import config from '@/config'; import config from '@/config';
import * as Queue from '@/Queue'; import * as Queue from '@/Queue';
import * as CrashJournal from '@/CrashJournal';
import { getWorkflowOwner } from '@/UserManagement/UserManagementHelper'; import { getWorkflowOwner } from '@/UserManagement/UserManagementHelper';
import { generateFailedExecutionFromError } from '@/WorkflowHelpers'; import { generateFailedExecutionFromError } from '@/WorkflowHelpers';
import { N8N_VERSION } from '@/constants'; import { N8N_VERSION } from '@/constants';
import { initErrorHandling } from '@/ErrorReporting';
const exitWithCrash = async (message: string, error: unknown) => {
ErrorReporter.error(new Error(message, { cause: error }), { level: 'fatal' });
await sleep(2000);
process.exit(1);
};
const exitSuccessFully = async () => {
try {
await CrashJournal.cleanup();
} finally {
process.exit();
}
};
export class Worker extends Command { export class Worker extends Command {
static description = '\nStarts a n8n worker'; static description = '\nStarts a n8n worker';
@ -64,9 +76,6 @@ export class Worker extends Command {
static jobQueue: Queue.JobQueue; static jobQueue: Queue.JobQueue;
static processExitCode = 0;
// static activeExecutions = ActiveExecutions.getInstance();
/** /**
* Stop n8n in a graceful way. * Stop n8n in a graceful way.
* Make for example sure that all the webhooks from third party services * Make for example sure that all the webhooks from third party services
@ -87,10 +96,10 @@ export class Worker extends Command {
const stopTime = new Date().getTime() + maxStopTime; const stopTime = new Date().getTime() + maxStopTime;
setTimeout(() => { setTimeout(async () => {
// In case that something goes wrong with shutdown we // In case that something goes wrong with shutdown we
// kill after max. 30 seconds no matter what // kill after max. 30 seconds no matter what
process.exit(Worker.processExitCode); await exitSuccessFully();
}, maxStopTime); }, maxStopTime);
// Wait for active workflow executions to finish // Wait for active workflow executions to finish
@ -108,10 +117,10 @@ export class Worker extends Command {
await sleep(500); await sleep(500);
} }
} catch (error) { } catch (error) {
LoggerProxy.error('There was an error shutting down n8n.', error); await exitWithCrash('There was an error shutting down n8n.', error);
} }
process.exit(Worker.processExitCode); await exitSuccessFully();
} }
async runJob(job: Queue.Job, nodeTypes: INodeTypes): Promise<Queue.JobResponse> { async runJob(job: Queue.Job, nodeTypes: INodeTypes): Promise<Queue.JobResponse> {
@ -261,20 +270,18 @@ export class Worker extends Command {
process.once('SIGTERM', Worker.stopProcess); process.once('SIGTERM', Worker.stopProcess);
process.once('SIGINT', Worker.stopProcess); process.once('SIGINT', Worker.stopProcess);
await initErrorHandling();
await CrashJournal.init();
// Wrap that the process does not close but we can still use async // Wrap that the process does not close but we can still use async
await (async () => { await (async () => {
try { try {
const { flags } = this.parse(Worker); const { flags } = this.parse(Worker);
// Start directly with the init of the database to improve startup time // Start directly with the init of the database to improve startup time
const startDbInitPromise = Db.init().catch((error) => { const startDbInitPromise = Db.init().catch(async (error: Error) =>
logger.error(`There was an error initializing DB: "${error.message}"`); exitWithCrash('There was an error initializing DB', error),
);
Worker.processExitCode = 1;
// @ts-ignore
process.emit('SIGINT');
process.exit(1);
});
// Make sure the settings exist // Make sure the settings exist
await UserSettings.prepareUserSettings(); await UserSettings.prepareUserSettings();
@ -428,12 +435,7 @@ export class Worker extends Command {
}); });
} }
} catch (error) { } catch (error) {
logger.error(`Worker process cannot continue. "${error.message}"`); await exitWithCrash('Worker process cannot continue.', error);
Worker.processExitCode = 1;
// @ts-ignore
process.emit('SIGINT');
process.exit(1);
} }
})(); })();
} }

View file

@ -2,7 +2,7 @@ import type { Primitives } from './utils';
import * as Logger from './LoggerProxy'; import * as Logger from './LoggerProxy';
export interface ReportingOptions { export interface ReportingOptions {
level?: 'warning' | 'error'; level?: 'warning' | 'error' | 'fatal';
tags?: Record<string, Primitives>; tags?: Record<string, Primitives>;
extra?: Record<string, unknown>; extra?: Record<string, unknown>;
} }