Improve workflow metrics with faillure reason (#18768)

- split workflow errors into system and user errors
- add workspace id to attributes for debugging
This commit is contained in:
Thomas Trompette
2026-03-20 11:04:40 +01:00
committed by GitHub
parent 3cbd9f2b5d
commit ffb02a6878
9 changed files with 68 additions and 16 deletions
@@ -62,14 +62,14 @@ export class ResumeDelayedWorkflowJob {
if (!step || !isWorkflowDelayAction(step)) {
throw new WorkflowRunException(
'Step not found or is not a delay action',
WorkflowRunExceptionCode.INVALID_INPUT,
WorkflowRunExceptionCode.INVALID_OPERATION,
);
}
if (stepInfo?.status !== StepStatus.PENDING) {
throw new WorkflowRunException(
'Step is not pending',
WorkflowRunExceptionCode.INVALID_INPUT,
WorkflowRunExceptionCode.INVALID_OPERATION,
);
}
@@ -102,6 +102,7 @@ export class ResumeDelayedWorkflowJob {
error instanceof Error
? error.message
: `Error during delay resume: ${String(error)}`,
isSystemError: true,
});
throw error;
@@ -9,6 +9,10 @@ import { HttpTool } from 'src/engine/core-modules/tool/tools/http-tool/http-tool
import { SendEmailTool } from 'src/engine/core-modules/tool/tools/email-tool/send-email-tool';
import { type ToolInput } from 'src/engine/core-modules/tool/types/tool-input.type';
import { type Tool } from 'src/engine/core-modules/tool/types/tool.type';
import {
WorkflowStepExecutorException,
WorkflowStepExecutorExceptionCode,
} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception';
import { type WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
import { type WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
import { type WorkflowSendEmailActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/types/workflow-send-email-action-input.type';
@@ -39,13 +43,19 @@ export class ToolExecutorWorkflowAction implements WorkflowAction {
const step = steps.find((step) => step.id === currentStepId);
if (!step) {
throw new Error('Step not found');
throw new WorkflowStepExecutorException(
'Step not found',
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
);
}
const tool = this.toolsByActionType.get(step.type);
if (!tool) {
throw new Error(`No tool found for workflow action type: ${step.type}`);
throw new WorkflowStepExecutorException(
`No tool found for workflow action type: ${step.type}`,
WorkflowStepExecutorExceptionCode.INVALID_STEP_TYPE,
);
}
let toolInput = step.settings.input;
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module';
import { ToolModule } from 'src/engine/core-modules/tool/tool.module';
import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module';
import { WorkflowActionFactory } from 'src/modules/workflow/workflow-executor/factories/workflow-action.factory';
@@ -36,6 +37,7 @@ import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow
EmptyActionModule,
FeatureFlagModule,
ToolModule,
MetricsModule,
],
providers: [
WorkflowExecutorWorkspaceService,
@@ -6,7 +6,9 @@ import { BILLING_FEATURE_USED } from 'src/engine/core-modules/billing/constants/
import { BILLING_WORKFLOW_EXECUTION_ERROR_MESSAGE } from 'src/engine/core-modules/billing/constants/billing-workflow-execution-error-message.constant';
import { BillingMeterEventName } from 'src/engine/core-modules/billing/enums/billing-meter-event-names';
import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import { WorkflowActionFactory } from 'src/modules/workflow/workflow-executor/factories/workflow-action.factory';
import { shouldExecuteStep } from 'src/modules/workflow/workflow-executor/utils/should-execute-step.util';
@@ -14,7 +16,6 @@ import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
import { WorkflowExecutorWorkspaceService } from 'src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service';
import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service';
@@ -61,6 +62,10 @@ describe('WorkflowExecutorWorkspaceService', () => {
captureExceptions: jest.fn(),
};
const mockMetricsService = {
incrementCounter: jest.fn(),
};
const mockMessageQueueService = {
add: jest.fn(),
};
@@ -97,6 +102,10 @@ describe('WorkflowExecutorWorkspaceService', () => {
provide: `MESSAGE_QUEUE_${MessageQueue.workflowQueue}`,
useValue: mockMessageQueueService,
},
{
provide: MetricsService,
useValue: mockMetricsService,
},
],
}).compile();
@@ -19,6 +19,8 @@ import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handl
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import { WorkflowRunStatus } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity';
import { workflowHasRunningSteps } from 'src/modules/workflow/common/utils/workflow-has-running-steps.util';
@@ -57,6 +59,7 @@ export class WorkflowExecutorWorkspaceService {
private readonly workflowRunWorkspaceService: WorkflowRunWorkspaceService,
private readonly billingService: BillingService,
private readonly exceptionHandlerService: ExceptionHandlerService,
private readonly metricsService: MetricsService,
@InjectMessageQueue(MessageQueue.workflowQueue)
private readonly messageQueueService: MessageQueueService,
) {}
@@ -70,7 +73,7 @@ export class WorkflowExecutorWorkspaceService {
}: WorkflowExecutorInput) {
await Promise.all(
stepIds.map(async (stepIdToExecute) => {
await this.executeFromStep({
return this.executeFromStep({
stepId: stepIdToExecute,
workflowRunId,
workspaceId,
@@ -92,7 +95,7 @@ export class WorkflowExecutorWorkspaceService {
workflowRunId,
workspaceId,
executedStepsCount,
}: WorkflowBranchExecutorInput) {
}: WorkflowBranchExecutorInput): Promise<void> {
const workflowRun =
await this.workflowRunWorkspaceService.getWorkflowRunOrFail({
workflowRunId,
@@ -111,6 +114,7 @@ export class WorkflowExecutorWorkspaceService {
workspaceId,
status: WorkflowRunStatus.FAILED,
error: 'Step not found',
isSystemError: true,
});
return;
@@ -500,6 +504,12 @@ export class WorkflowExecutorWorkspaceService {
this.exceptionHandlerService.captureExceptions([error], {
workspace: { id: workspaceId },
});
await this.metricsService.incrementCounter({
key: MetricsKeys.WorkflowRunSystemError,
eventId: workflowRunId,
debugLog: `[Workflow Run System Error] Workflow run ${workflowRunId} in workspace ${workspaceId} ended with system error`,
});
}
return {