mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
c972f3dd50
* Added logging to n8n This commit adds logging to n8n using the Winston library. For now, this commit only allows logging to console (default behavior) or file (need to pass in config via environment variables). Other logging methods can be further implemented using hooks. These were skipped for now as it would require adding more dependencies. Logging level is notice by default, meaning no additional messages would be displayed at the moment. Logging level can be set to info or debug as well to enrich the generated logs. The ILogger interface was added to the workflow project as it would make it available for all other projects but the implementation was done on the cli project. * Lint fixes and logging level naming. Also fixed the way we use the logger as it was not working previously * Improvements to logging framework Using appropriate single quotes Improving the way the logger is declared * Improved naming for Log Types * Removed logger global variable, replacing it by a proxy * Add logging to CLI commands * Remove unused GenericHelpers * Changed back some messages to console instead of logger and added npm shortcuts for worker and webhook * Fix typos * Adding basic file rotation to logs as suggested by @mutdmour * Fixed linting issues * Correcting comment to correctly reflect space usage * Added settings for log files rotation * Correcting config type from String to Number * Changed default file settings to number To reflect previous changes to the type * Changed the way log messages are added to be called statically. Also minor naming improvements * Applying latest corrections sent by @ivov * ⚡ Some logging improvements * Saving logs to a folder inside n8n home instead of root * Fixed broken tests and linting * Changed some log messages to improve formatting * Adding quotes to names on log messages * Added execution and session IDs to logs. Also removed unnecessary line breaks * ⚡ Added file caller to log messages (#1657) This is done using callsites library which already existed in the project as another library's dependency. So in fact it does not add any new dependency. * Adding logs to help debug Salesforce node * ⚡ Add function name to logs and add more logs * ⚡ Improve some error messages * ⚡ Improve some more log messages * ⚡ Rename logging env variables to match others Co-authored-by: dali <servfrdali@yahoo.fr> Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
237 lines
6.9 KiB
TypeScript
237 lines
6.9 KiB
TypeScript
import {
|
|
UserSettings,
|
|
} from 'n8n-core';
|
|
import { Command, flags } from '@oclif/command';
|
|
import * as Redis from 'ioredis';
|
|
|
|
import * as config from '../config';
|
|
import {
|
|
ActiveExecutions,
|
|
ActiveWorkflowRunner,
|
|
CredentialsOverwrites,
|
|
CredentialTypes,
|
|
Db,
|
|
ExternalHooks,
|
|
GenericHelpers,
|
|
LoadNodesAndCredentials,
|
|
NodeTypes,
|
|
TestWebhooks,
|
|
WebhookServer,
|
|
} from '../src';
|
|
import { IDataObject } from 'n8n-workflow';
|
|
|
|
import {
|
|
getLogger,
|
|
} from '../src/Logger';
|
|
|
|
import {
|
|
LoggerProxy,
|
|
} from 'n8n-workflow';
|
|
|
|
let activeWorkflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner | undefined;
|
|
let processExistCode = 0;
|
|
|
|
|
|
export class Webhook extends Command {
|
|
static description = 'Starts n8n webhook process. Intercepts only production URLs.';
|
|
|
|
static examples = [
|
|
`$ n8n webhook`,
|
|
];
|
|
|
|
static flags = {
|
|
help: flags.help({ char: 'h' }),
|
|
};
|
|
|
|
/**
|
|
* Stops the n8n in a graceful way.
|
|
* Make for example sure that all the webhooks from third party services
|
|
* get removed.
|
|
*/
|
|
static async stopProcess() {
|
|
LoggerProxy.info(`\nStopping n8n...`);
|
|
|
|
try {
|
|
const externalHooks = ExternalHooks();
|
|
await externalHooks.run('n8n.stop', []);
|
|
|
|
setTimeout(() => {
|
|
// In case that something goes wrong with shutdown we
|
|
// kill after max. 30 seconds no matter what
|
|
process.exit(processExistCode);
|
|
}, 30000);
|
|
|
|
const removePromises = [];
|
|
if (activeWorkflowRunner !== undefined) {
|
|
removePromises.push(activeWorkflowRunner.removeAll());
|
|
}
|
|
|
|
// Remove all test webhooks
|
|
const testWebhooks = TestWebhooks.getInstance();
|
|
removePromises.push(testWebhooks.removeAll());
|
|
|
|
await Promise.all(removePromises);
|
|
|
|
// Wait for active workflow executions to finish
|
|
const activeExecutionsInstance = ActiveExecutions.getInstance();
|
|
let executingWorkflows = activeExecutionsInstance.getActiveExecutions();
|
|
|
|
let count = 0;
|
|
while (executingWorkflows.length !== 0) {
|
|
if (count++ % 4 === 0) {
|
|
LoggerProxy.info(`Waiting for ${executingWorkflows.length} active executions to finish...`);
|
|
}
|
|
await new Promise((resolve) => {
|
|
setTimeout(resolve, 500);
|
|
});
|
|
executingWorkflows = activeExecutionsInstance.getActiveExecutions();
|
|
}
|
|
|
|
} catch (error) {
|
|
LoggerProxy.error('There was an error shutting down n8n.', error);
|
|
}
|
|
|
|
process.exit(processExistCode);
|
|
}
|
|
|
|
|
|
async run() {
|
|
const logger = getLogger();
|
|
LoggerProxy.init(logger);
|
|
|
|
// Make sure that n8n shuts down gracefully if possible
|
|
process.on('SIGTERM', Webhook.stopProcess);
|
|
process.on('SIGINT', Webhook.stopProcess);
|
|
|
|
const { flags } = this.parse(Webhook);
|
|
|
|
// Wrap that the process does not close but we can still use async
|
|
await (async () => {
|
|
if (config.get('executions.mode') !== 'queue') {
|
|
/**
|
|
* It is technically possible to run without queues but
|
|
* there are 2 known bugs when running in this mode:
|
|
* - Executions list will be problematic as the main process
|
|
* is not aware of current executions in the webhook processes
|
|
* and therefore will display all current executions as error
|
|
* as it is unable to determine if it is still running or crashed
|
|
* - You cannot stop currently executing jobs from webhook processes
|
|
* when running without queues as the main process cannot talk to
|
|
* the wehbook processes to communicate workflow execution interruption.
|
|
*/
|
|
|
|
this.error('Webhook processes can only run with execution mode as queue.');
|
|
}
|
|
|
|
try {
|
|
// Start directly with the init of the database to improve startup time
|
|
const startDbInitPromise = Db.init().catch(error => {
|
|
logger.error(`There was an error initializing DB: "${error.message}"`);
|
|
|
|
processExistCode = 1;
|
|
// @ts-ignore
|
|
process.emit('SIGINT');
|
|
process.exit(1);
|
|
});
|
|
|
|
// Make sure the settings exist
|
|
const userSettings = await UserSettings.prepareUserSettings();
|
|
|
|
// Load all node and credential types
|
|
const loadNodesAndCredentials = LoadNodesAndCredentials();
|
|
await loadNodesAndCredentials.init();
|
|
|
|
// Load the credentials overwrites if any exist
|
|
const credentialsOverwrites = CredentialsOverwrites();
|
|
await credentialsOverwrites.init();
|
|
|
|
// Load all external hooks
|
|
const externalHooks = ExternalHooks();
|
|
await externalHooks.init();
|
|
|
|
// Add the found types to an instance other parts of the application can use
|
|
const nodeTypes = NodeTypes();
|
|
await nodeTypes.init(loadNodesAndCredentials.nodeTypes);
|
|
const credentialTypes = CredentialTypes();
|
|
await credentialTypes.init(loadNodesAndCredentials.credentialTypes);
|
|
|
|
// Wait till the database is ready
|
|
await startDbInitPromise;
|
|
|
|
if (config.get('executions.mode') === 'queue') {
|
|
const redisHost = config.get('queue.bull.redis.host');
|
|
const redisPassword = config.get('queue.bull.redis.password');
|
|
const redisPort = config.get('queue.bull.redis.port');
|
|
const redisDB = config.get('queue.bull.redis.db');
|
|
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
|
|
let lastTimer = 0, cumulativeTimeout = 0;
|
|
|
|
const settings = {
|
|
retryStrategy: (times: number): number | null => {
|
|
const now = Date.now();
|
|
if (now - lastTimer > 30000) {
|
|
// Means we had no timeout at all or last timeout was temporary and we recovered
|
|
lastTimer = now;
|
|
cumulativeTimeout = 0;
|
|
} else {
|
|
cumulativeTimeout += now - lastTimer;
|
|
lastTimer = now;
|
|
if (cumulativeTimeout > redisConnectionTimeoutLimit) {
|
|
logger.error('Unable to connect to Redis after ' + redisConnectionTimeoutLimit + ". Exiting process.");
|
|
process.exit(1);
|
|
}
|
|
}
|
|
return 500;
|
|
},
|
|
} as IDataObject;
|
|
|
|
if (redisHost) {
|
|
settings.host = redisHost;
|
|
}
|
|
if (redisPassword) {
|
|
settings.password = redisPassword;
|
|
}
|
|
if (redisPort) {
|
|
settings.port = redisPort;
|
|
}
|
|
if (redisDB) {
|
|
settings.db = redisDB;
|
|
}
|
|
|
|
// This connection is going to be our heartbeat
|
|
// IORedis automatically pings redis and tries to reconnect
|
|
// We will be using the retryStrategy above
|
|
// to control how and when to exit.
|
|
const redis = new Redis(settings);
|
|
|
|
redis.on('error', (error) => {
|
|
if (error.toString().includes('ECONNREFUSED') === true) {
|
|
logger.warn('Redis unavailable - trying to reconnect...');
|
|
} else {
|
|
logger.warn('Error with Redis: ', error);
|
|
}
|
|
});
|
|
}
|
|
|
|
await WebhookServer.start();
|
|
|
|
// Start to get active workflows and run their triggers
|
|
activeWorkflowRunner = ActiveWorkflowRunner.getInstance();
|
|
await activeWorkflowRunner.initWebhooks();
|
|
|
|
const editorUrl = GenericHelpers.getBaseUrl();
|
|
console.info('Webhook listener waiting for requests.');
|
|
|
|
} catch (error) {
|
|
console.error('Exiting due to error. See log message for details.');
|
|
logger.error(`Webhook process cannot continue. "${error.message}"`);
|
|
|
|
processExistCode = 1;
|
|
// @ts-ignore
|
|
process.emit('SIGINT');
|
|
process.exit(1);
|
|
}
|
|
})();
|
|
}
|
|
}
|