mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-26 05:04:05 -08:00
fix: Report app startup and DB migration errors to Sentry (#5127)
This commit is contained in:
parent
3c109ffab1
commit
a573db2ef7
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue