diff --git a/packages/@n8n/config/src/configs/logging.config.ts b/packages/@n8n/config/src/configs/logging.config.ts index 791dc6af64..e3f4ab5206 100644 --- a/packages/@n8n/config/src/configs/logging.config.ts +++ b/packages/@n8n/config/src/configs/logging.config.ts @@ -17,6 +17,7 @@ export const LOG_SCOPES = [ 'waiting-executions', 'task-runner', 'insights', + 'workflow-activation', ] as const; export type LogScope = (typeof LOG_SCOPES)[number]; diff --git a/packages/cli/src/__tests__/active-workflow-manager.test.ts b/packages/cli/src/__tests__/active-workflow-manager.test.ts index 6b84a99012..6579030bb5 100644 --- a/packages/cli/src/__tests__/active-workflow-manager.test.ts +++ b/packages/cli/src/__tests__/active-workflow-manager.test.ts @@ -13,6 +13,7 @@ import { Workflow } from 'n8n-workflow'; import { ActiveWorkflowManager } from '@/active-workflow-manager'; import type { WorkflowRepository } from '@/databases/repositories/workflow.repository'; import type { NodeTypes } from '@/node-types'; +import { mockLogger } from '@test/mocking'; describe('ActiveWorkflowManager', () => { let activeWorkflowManager: ActiveWorkflowManager; @@ -23,7 +24,7 @@ describe('ActiveWorkflowManager', () => { beforeEach(() => { jest.clearAllMocks(); activeWorkflowManager = new ActiveWorkflowManager( - mock(), + mockLogger(), mock(), mock(), mock(), @@ -137,12 +138,12 @@ describe('ActiveWorkflowManager', () => { ); workflowRepository.findById.mockResolvedValue(mock({ active: false })); - const result = await activeWorkflowManager.add('some-id', mode); + const added = await activeWorkflowManager.add('some-id', mode); expect(checkSpy).not.toHaveBeenCalled(); expect(addWebhooksSpy).not.toHaveBeenCalled(); expect(addTriggersAndPollersSpy).not.toHaveBeenCalled(); - expect(result).toBe(false); + expect(added).toEqual({ triggersAndPollers: false, webhooks: false }); }, ); }); diff --git a/packages/cli/src/active-workflow-manager.ts b/packages/cli/src/active-workflow-manager.ts index 66bc9ebf9a..92257ad0c7 100644 --- a/packages/cli/src/active-workflow-manager.ts +++ b/packages/cli/src/active-workflow-manager.ts @@ -85,7 +85,9 @@ export class ActiveWorkflowManager { private readonly instanceSettings: InstanceSettings, private readonly publisher: Publisher, private readonly workflowsConfig: WorkflowsConfig, - ) {} + ) { + this.logger = this.logger.scoped(['workflow-activation']); + } async init() { strict( @@ -158,9 +160,7 @@ export class ActiveWorkflowManager { const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData, undefined, true); let path = ''; - if (webhooks.length === 0) return; - - this.logger.debug(`Adding webhooks for workflow "${workflow.name}" (ID ${workflow.id})`); + if (webhooks.length === 0) return false; for (const webhookData of webhooks) { const node = workflow.getNode(webhookData.node) as INode; @@ -227,6 +227,12 @@ export class ActiveWorkflowManager { await this.webhookService.populateCache(); await this.workflowStaticDataService.saveStaticData(workflow); + + this.logger.debug(`Added webhooks for workflow "${workflow.name}" (ID ${workflow.id})`, { + workflowId: workflow.id, + }); + + return true; } /** @@ -434,7 +440,7 @@ export class ActiveWorkflowManager { await Promise.all(activationPromises); } - this.logger.debug('Activated all trigger- and poller-based workflows'); + this.logger.debug('Finished activating all workflows'); } private async activateWorkflow( @@ -445,13 +451,14 @@ export class ActiveWorkflowManager { if (!dbWorkflow) return; try { - const wasActivated = await this.add(dbWorkflow.id, activationMode, dbWorkflow, { + const added = await this.add(dbWorkflow.id, activationMode, dbWorkflow, { shouldPublish: false, }); - if (wasActivated) { + + if (added.webhooks || added.triggersAndPollers) { this.logger.info(` - ${formatWorkflow(dbWorkflow)})`); this.logger.info(' => Started'); - this.logger.debug(`Successfully started workflow ${formatWorkflow(dbWorkflow)}`, { + this.logger.debug(`Activated workflow ${formatWorkflow(dbWorkflow)}`, { workflowName: dbWorkflow.name, workflowId: dbWorkflow.id, }); @@ -517,6 +524,8 @@ export class ActiveWorkflowManager { * Triggers and pollers are registered as active in memory at `ActiveWorkflows`, * but webhooks are registered by being entered in the `webhook_entity` table, * since webhooks do not require continuous execution. + * + * Returns whether this operation added webhooks and/or triggers and pollers. */ async add( workflowId: WorkflowId, @@ -524,13 +533,15 @@ export class ActiveWorkflowManager { existingWorkflow?: WorkflowEntity, { shouldPublish } = { shouldPublish: true }, ) { + const added = { webhooks: false, triggersAndPollers: false }; + if (this.instanceSettings.isMultiMain && shouldPublish) { void this.publisher.publishCommand({ command: 'add-webhooks-triggers-and-pollers', payload: { workflowId }, }); - return; + return added; } let workflow: Workflow; @@ -538,10 +549,6 @@ export class ActiveWorkflowManager { const shouldAddWebhooks = this.shouldAddWebhooks(activationMode); const shouldAddTriggersAndPollers = this.shouldAddTriggersAndPollers(); - const shouldDisplayActivationMessage = - (shouldAddWebhooks || shouldAddTriggersAndPollers) && - ['init', 'leadershipChange'].includes(activationMode); - try { const dbWorkflow = existingWorkflow ?? (await this.workflowRepository.findById(workflowId)); @@ -554,18 +561,10 @@ export class ActiveWorkflowManager { if (['init', 'leadershipChange'].includes(activationMode) && !dbWorkflow.active) { this.logger.debug( `Skipping workflow ${formatWorkflow(dbWorkflow)} as it is no longer active`, - { - workflowId: dbWorkflow.id, - }, + { workflowId: dbWorkflow.id }, ); - return false; - } - if (shouldDisplayActivationMessage) { - this.logger.debug(`Initializing active workflow ${formatWorkflow(dbWorkflow)} (startup)`, { - workflowName: dbWorkflow.name, - workflowId: dbWorkflow.id, - }); + return added; } workflow = new Workflow({ @@ -591,11 +590,16 @@ export class ActiveWorkflowManager { const additionalData = await WorkflowExecuteAdditionalData.getBase(); if (shouldAddWebhooks) { - await this.addWebhooks(workflow, additionalData, 'trigger', activationMode); + added.webhooks = await this.addWebhooks( + workflow, + additionalData, + 'trigger', + activationMode, + ); } if (shouldAddTriggersAndPollers) { - await this.addTriggersAndPollers(dbWorkflow, workflow, { + added.triggersAndPollers = await this.addTriggersAndPollers(dbWorkflow, workflow, { activationMode, executionMode: 'trigger', additionalData, @@ -620,7 +624,7 @@ export class ActiveWorkflowManager { // id of them in the static data. So make sure that data gets persisted. await this.workflowStaticDataService.saveStaticData(workflow); - return shouldDisplayActivationMessage; + return added; } /** @@ -863,24 +867,23 @@ export class ActiveWorkflowManager { activationMode, ); - if (workflow.getTriggerNodes().length !== 0 || workflow.getPollNodes().length !== 0) { - this.logger.debug(`Adding triggers and pollers for workflow ${formatWorkflow(dbWorkflow)}`); - - await this.activeWorkflows.add( - workflow.id, - workflow, - additionalData, - executionMode, - activationMode, - getTriggerFunctions, - getPollFunctions, - ); - - this.logger.debug(`Workflow ${formatWorkflow(dbWorkflow)} activated`, { - workflowId: dbWorkflow.id, - workflowName: dbWorkflow.name, - }); + if (workflow.getTriggerNodes().length === 0 && workflow.getPollNodes().length === 0) { + return false; } + + await this.activeWorkflows.add( + workflow.id, + workflow, + additionalData, + executionMode, + activationMode, + getTriggerFunctions, + getPollFunctions, + ); + + this.logger.debug(`Added triggers and pollers for workflow ${formatWorkflow(dbWorkflow)}`); + + return true; } async removeActivationError(workflowId: WorkflowId) { diff --git a/packages/cli/test/integration/active-workflow-manager.test.ts b/packages/cli/test/integration/active-workflow-manager.test.ts index a1ee102ef1..819ee7ee3c 100644 --- a/packages/cli/test/integration/active-workflow-manager.test.ts +++ b/packages/cli/test/integration/active-workflow-manager.test.ts @@ -1,7 +1,7 @@ import type { WebhookEntity } from '@n8n/db'; import { Container } from '@n8n/di'; import { mock } from 'jest-mock-extended'; -import { InstanceSettings, Logger } from 'n8n-core'; +import { InstanceSettings } from 'n8n-core'; import { FormTrigger } from 'n8n-nodes-base/nodes/Form/FormTrigger.node'; import { ScheduleTrigger } from 'n8n-nodes-base/nodes/Schedule/ScheduleTrigger.node'; import { NodeApiError, Workflow } from 'n8n-workflow'; @@ -32,7 +32,6 @@ import * as utils from './shared/utils/'; import { mockInstance } from '../shared/mocking'; mockInstance(ActiveExecutions); -mockInstance(Logger); mockInstance(Push); mockInstance(SecretsHelper); mockInstance(ExecutionService);