mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
refactor(core): Support multiple log scopes (#11318)
This commit is contained in:
parent
c863abd083
commit
43f31b86aa
|
@ -1,14 +1,16 @@
|
||||||
import { Config, Env, Nested } from '../decorators';
|
import { Config, Env, Nested } from '../decorators';
|
||||||
import { StringArray } from '../utils';
|
import { StringArray } from '../utils';
|
||||||
|
|
||||||
/**
|
/** Scopes (areas of functionality) to filter logs by. */
|
||||||
* Scopes (areas of functionality) to filter logs by.
|
export const LOG_SCOPES = [
|
||||||
*
|
'concurrency',
|
||||||
* `executions` -> execution lifecycle
|
'license',
|
||||||
* `license` -> license SDK
|
'multi-main-setup',
|
||||||
* `scaling` -> scaling mode
|
'pubsub',
|
||||||
*/
|
'redis',
|
||||||
export const LOG_SCOPES = ['executions', 'license', 'scaling'] as const;
|
'scaling',
|
||||||
|
'waiting-executions',
|
||||||
|
] as const;
|
||||||
|
|
||||||
export type LogScope = (typeof LOG_SCOPES)[number];
|
export type LogScope = (typeof LOG_SCOPES)[number];
|
||||||
|
|
||||||
|
@ -59,14 +61,19 @@ export class LoggingConfig {
|
||||||
/**
|
/**
|
||||||
* Scopes to filter logs by. Nothing is filtered by default.
|
* Scopes to filter logs by. Nothing is filtered by default.
|
||||||
*
|
*
|
||||||
* Currently supported log scopes:
|
* Supported log scopes:
|
||||||
* - `executions`
|
*
|
||||||
|
* - `concurrency`
|
||||||
* - `license`
|
* - `license`
|
||||||
|
* - `multi-main-setup`
|
||||||
|
* - `pubsub`
|
||||||
|
* - `redis`
|
||||||
* - `scaling`
|
* - `scaling`
|
||||||
|
* - `waiting-executions`
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* `N8N_LOG_SCOPES=license`
|
* `N8N_LOG_SCOPES=license`
|
||||||
* `N8N_LOG_SCOPES=license,executions`
|
* `N8N_LOG_SCOPES=license,waiting-executions`
|
||||||
*/
|
*/
|
||||||
@Env('N8N_LOG_SCOPES')
|
@Env('N8N_LOG_SCOPES')
|
||||||
scopes: StringArray<LogScope> = [];
|
scopes: StringArray<LogScope> = [];
|
||||||
|
|
|
@ -170,7 +170,7 @@ export class Start extends BaseCommand {
|
||||||
|
|
||||||
this.logger.info('Initializing n8n process');
|
this.logger.info('Initializing n8n process');
|
||||||
if (config.getEnv('executions.mode') === 'queue') {
|
if (config.getEnv('executions.mode') === 'queue') {
|
||||||
const scopedLogger = this.logger.withScope('scaling');
|
const scopedLogger = this.logger.scoped('scaling');
|
||||||
scopedLogger.debug('Starting main instance in scaling mode');
|
scopedLogger.debug('Starting main instance in scaling mode');
|
||||||
scopedLogger.debug(`Host ID: ${this.instanceSettings.hostId}`);
|
scopedLogger.debug(`Host ID: ${this.instanceSettings.hostId}`);
|
||||||
}
|
}
|
||||||
|
@ -263,7 +263,7 @@ export class Start extends BaseCommand {
|
||||||
await subscriber.subscribe('n8n.commands');
|
await subscriber.subscribe('n8n.commands');
|
||||||
await subscriber.subscribe('n8n.worker-response');
|
await subscriber.subscribe('n8n.worker-response');
|
||||||
|
|
||||||
this.logger.withScope('scaling').debug('Pubsub setup completed');
|
this.logger.scoped(['scaling', 'pubsub']).debug('Pubsub setup completed');
|
||||||
|
|
||||||
if (!orchestrationService.isMultiMainSetupEnabled) return;
|
if (!orchestrationService.isMultiMainSetupEnabled) return;
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ export class Worker extends BaseCommand {
|
||||||
|
|
||||||
super(argv, cmdConfig);
|
super(argv, cmdConfig);
|
||||||
|
|
||||||
this.logger = Container.get(Logger).withScope('scaling');
|
this.logger = Container.get(Logger).scoped('scaling');
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
|
@ -151,7 +151,7 @@ export class Worker extends BaseCommand {
|
||||||
Container.get(PubSubHandler).init();
|
Container.get(PubSubHandler).init();
|
||||||
await Container.get(Subscriber).subscribe('n8n.commands');
|
await Container.get(Subscriber).subscribe('n8n.commands');
|
||||||
|
|
||||||
this.logger.withScope('scaling').debug('Pubsub setup ready');
|
this.logger.scoped(['scaling', 'pubsub']).debug('Pubsub setup completed');
|
||||||
}
|
}
|
||||||
|
|
||||||
async setConcurrency() {
|
async setConcurrency() {
|
||||||
|
|
|
@ -33,7 +33,7 @@ export class ConcurrencyControlService {
|
||||||
private readonly telemetry: Telemetry,
|
private readonly telemetry: Telemetry,
|
||||||
private readonly eventService: EventService,
|
private readonly eventService: EventService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.logger.withScope('executions');
|
this.logger = this.logger.scoped('concurrency');
|
||||||
|
|
||||||
this.productionLimit = config.getEnv('executions.concurrency.productionLimit');
|
this.productionLimit = config.getEnv('executions.concurrency.productionLimit');
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ export class License {
|
||||||
private readonly licenseMetricsService: LicenseMetricsService,
|
private readonly licenseMetricsService: LicenseMetricsService,
|
||||||
private readonly globalConfig: GlobalConfig,
|
private readonly globalConfig: GlobalConfig,
|
||||||
) {
|
) {
|
||||||
this.logger = this.logger.withScope('license');
|
this.logger = this.logger.scoped('license');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -58,9 +58,11 @@ export class Logger {
|
||||||
this.internalLogger = internalLogger;
|
this.internalLogger = internalLogger;
|
||||||
}
|
}
|
||||||
|
|
||||||
withScope(scope: LogScope) {
|
/** Create a logger that injects the given scopes into its log metadata. */
|
||||||
|
scoped(scopes: LogScope | LogScope[]) {
|
||||||
|
scopes = Array.isArray(scopes) ? scopes : [scopes];
|
||||||
const scopedLogger = new Logger(this.globalConfig, this.instanceSettings);
|
const scopedLogger = new Logger(this.globalConfig, this.instanceSettings);
|
||||||
const childLogger = this.internalLogger.child({ scope });
|
const childLogger = this.internalLogger.child({ scopes });
|
||||||
|
|
||||||
scopedLogger.setInternalLogger(childLogger);
|
scopedLogger.setInternalLogger(childLogger);
|
||||||
|
|
||||||
|
@ -106,11 +108,14 @@ export class Logger {
|
||||||
|
|
||||||
private scopeFilter() {
|
private scopeFilter() {
|
||||||
return winston.format((info: TransformableInfo & { metadata: LogMetadata }) => {
|
return winston.format((info: TransformableInfo & { metadata: LogMetadata }) => {
|
||||||
const shouldIncludeScope = info.metadata.scope && this.scopes.has(info.metadata.scope);
|
if (!this.isScopingEnabled) return info;
|
||||||
|
|
||||||
if (this.isScopingEnabled && !shouldIncludeScope) return false;
|
const { scopes } = info.metadata;
|
||||||
|
|
||||||
return info;
|
const shouldIncludeScope =
|
||||||
|
scopes && scopes?.length > 0 && scopes.some((s) => this.scopes.has(s));
|
||||||
|
|
||||||
|
return shouldIncludeScope ? info : false;
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ export type LogLevel = (typeof LOG_LEVELS)[number];
|
||||||
|
|
||||||
export type LogMetadata = {
|
export type LogMetadata = {
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
scope?: LogScope;
|
scopes?: LogScope[];
|
||||||
file?: string;
|
file?: string;
|
||||||
function?: string;
|
function?: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -40,7 +40,7 @@ export class JobProcessor {
|
||||||
private readonly nodeTypes: NodeTypes,
|
private readonly nodeTypes: NodeTypes,
|
||||||
private readonly instanceSettings: InstanceSettings,
|
private readonly instanceSettings: InstanceSettings,
|
||||||
) {
|
) {
|
||||||
this.logger = this.logger.withScope('scaling');
|
this.logger = this.logger.scoped('scaling');
|
||||||
}
|
}
|
||||||
|
|
||||||
async processJob(job: Job): Promise<JobResult> {
|
async processJob(job: Job): Promise<JobResult> {
|
||||||
|
|
|
@ -36,7 +36,7 @@ export class MultiMainSetup extends TypedEmitter<MultiMainEvents> {
|
||||||
private readonly globalConfig: GlobalConfig,
|
private readonly globalConfig: GlobalConfig,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.logger = this.logger.withScope('scaling');
|
this.logger = this.logger.scoped(['scaling', 'multi-main-setup']);
|
||||||
}
|
}
|
||||||
|
|
||||||
private leaderKey: string;
|
private leaderKey: string;
|
||||||
|
|
|
@ -26,7 +26,7 @@ export class Publisher {
|
||||||
// @TODO: Once this class is only ever initialized in scaling mode, throw in the next line instead.
|
// @TODO: Once this class is only ever initialized in scaling mode, throw in the next line instead.
|
||||||
if (config.getEnv('executions.mode') !== 'queue') return;
|
if (config.getEnv('executions.mode') !== 'queue') return;
|
||||||
|
|
||||||
this.logger = this.logger.withScope('scaling');
|
this.logger = this.logger.scoped(['scaling', 'pubsub']);
|
||||||
|
|
||||||
this.client = this.redisClientService.createClient({ type: 'publisher(n8n)' });
|
this.client = this.redisClientService.createClient({ type: 'publisher(n8n)' });
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ export class Subscriber {
|
||||||
// @TODO: Once this class is only ever initialized in scaling mode, throw in the next line instead.
|
// @TODO: Once this class is only ever initialized in scaling mode, throw in the next line instead.
|
||||||
if (config.getEnv('executions.mode') !== 'queue') return;
|
if (config.getEnv('executions.mode') !== 'queue') return;
|
||||||
|
|
||||||
this.logger = this.logger.withScope('scaling');
|
this.logger = this.logger.scoped(['scaling', 'pubsub']);
|
||||||
|
|
||||||
this.client = this.redisClientService.createClient({ type: 'subscriber(n8n)' });
|
this.client = this.redisClientService.createClient({ type: 'subscriber(n8n)' });
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ export class ScalingService {
|
||||||
private readonly orchestrationService: OrchestrationService,
|
private readonly orchestrationService: OrchestrationService,
|
||||||
private readonly eventService: EventService,
|
private readonly eventService: EventService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.logger.withScope('scaling');
|
this.logger = this.logger.scoped('scaling');
|
||||||
}
|
}
|
||||||
|
|
||||||
// #region Lifecycle
|
// #region Lifecycle
|
||||||
|
|
|
@ -58,7 +58,7 @@ export class WorkerServer {
|
||||||
) {
|
) {
|
||||||
assert(this.instanceSettings.instanceType === 'worker');
|
assert(this.instanceSettings.instanceType === 'worker');
|
||||||
|
|
||||||
this.logger = this.logger.withScope('scaling');
|
this.logger = this.logger.scoped('scaling');
|
||||||
|
|
||||||
this.app = express();
|
this.app = express();
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,9 @@ export class RedisClientService extends TypedEmitter<RedisEventMap> {
|
||||||
private readonly globalConfig: GlobalConfig,
|
private readonly globalConfig: GlobalConfig,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.logger = this.logger.scoped(['redis', 'scaling']);
|
||||||
|
|
||||||
this.registerListeners();
|
this.registerListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,9 +102,11 @@ export class RedisClientService extends TypedEmitter<RedisEventMap> {
|
||||||
options.host = host;
|
options.host = host;
|
||||||
options.port = port;
|
options.port = port;
|
||||||
|
|
||||||
this.logger.debug('[Redis] Initializing regular client', { type, host, port });
|
const client = new ioRedis(options);
|
||||||
|
|
||||||
return new ioRedis(options);
|
this.logger.debug(`Started Redis client ${type}`, { type, host, port });
|
||||||
|
|
||||||
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createClusterClient({
|
private createClusterClient({
|
||||||
|
@ -115,12 +120,14 @@ export class RedisClientService extends TypedEmitter<RedisEventMap> {
|
||||||
|
|
||||||
const clusterNodes = this.clusterNodes();
|
const clusterNodes = this.clusterNodes();
|
||||||
|
|
||||||
this.logger.debug('[Redis] Initializing cluster client', { type, clusterNodes });
|
const clusterClient = new ioRedis.Cluster(clusterNodes, {
|
||||||
|
|
||||||
return new ioRedis.Cluster(clusterNodes, {
|
|
||||||
redisOptions: options,
|
redisOptions: options,
|
||||||
clusterRetryStrategy: this.retryStrategy(),
|
clusterRetryStrategy: this.retryStrategy(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.logger.debug(`Started Redis cluster client ${type}`, { type, clusterNodes });
|
||||||
|
|
||||||
|
return clusterClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getOptions({ extraOptions }: { extraOptions?: RedisOptions }) {
|
private getOptions({ extraOptions }: { extraOptions?: RedisOptions }) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ export class WaitTracker {
|
||||||
private readonly orchestrationService: OrchestrationService,
|
private readonly orchestrationService: OrchestrationService,
|
||||||
private readonly instanceSettings: InstanceSettings,
|
private readonly instanceSettings: InstanceSettings,
|
||||||
) {
|
) {
|
||||||
this.logger = this.logger.withScope('executions');
|
this.logger = this.logger.scoped('waiting-executions');
|
||||||
}
|
}
|
||||||
|
|
||||||
has(executionId: string) {
|
has(executionId: string) {
|
||||||
|
|
|
@ -25,5 +25,4 @@ export const mockEntityManager = (entityClass: Class) => {
|
||||||
return entityManager;
|
return entityManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockLogger = () =>
|
export const mockLogger = () => mock<Logger>({ scoped: jest.fn().mockReturnValue(mock<Logger>()) });
|
||||||
mock<Logger>({ withScope: jest.fn().mockReturnValue(mock<Logger>()) });
|
|
||||||
|
|
Loading…
Reference in a new issue