mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-31 08:46:58 +02:00
fix(core): Preserve underlying cause when logging webhook execution failures (#31120)
This commit is contained in:
parent
383928ea3f
commit
b2f4c2c6e4
|
|
@ -1,7 +1,9 @@
|
|||
import { Logger } from '@n8n/backend-common';
|
||||
import { mockInstance } from '@n8n/backend-test-utils';
|
||||
import { type Response } from 'express';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { isWebhookHtmlSandboxingDisabled, getHtmlSandboxCSP } from 'n8n-core';
|
||||
import { randomString } from 'n8n-workflow';
|
||||
import { OperationalError, randomString } from 'n8n-workflow';
|
||||
import type { IHttpRequestMethods } from 'n8n-workflow';
|
||||
|
||||
import { ResponseError } from '@/errors/response-errors/abstract/response.error';
|
||||
|
|
@ -20,6 +22,7 @@ jest.mock('n8n-core', () => ({
|
|||
}));
|
||||
|
||||
describe('WebhookRequestHandler', () => {
|
||||
const logger = mockInstance(Logger);
|
||||
const webhookManager = mock<Required<IWebhookManager>>();
|
||||
const handler = createWebhookHandlerFor(webhookManager) as (
|
||||
req: WebhookRequest | WebhookOptionsRequest,
|
||||
|
|
@ -209,6 +212,31 @@ describe('WebhookRequestHandler', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should log the underlying error cause when execution fails', async () => {
|
||||
const req = mock<WebhookRequest>({
|
||||
path: '/webhook/abc',
|
||||
method: 'GET',
|
||||
params: { path: 'abc' },
|
||||
});
|
||||
|
||||
const res = mock<Response>();
|
||||
res.status.mockReturnValue(res);
|
||||
|
||||
const rootCause = new Error('SQLITE_BUSY: database is locked');
|
||||
const wrapper = new OperationalError('There was a problem executing the workflow', {
|
||||
cause: rootCause,
|
||||
});
|
||||
|
||||
webhookManager.executeWebhook.mockRejectedValueOnce(wrapper);
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
'Error in handling webhook request GET /webhook/abc: There was a problem executing the workflow',
|
||||
expect.objectContaining({ error: wrapper }),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not throw when legacy response headers contain invalid names', async () => {
|
||||
const req = mock<WebhookRequest>({
|
||||
path: '/',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import type { Project } from '@n8n/db';
|
|||
import { ExecutionRepository } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import type express from 'express';
|
||||
import merge from 'lodash/merge';
|
||||
import { BinaryDataService, ErrorReporter, WAITING_TOKEN_QUERY_PARAM } from 'n8n-core';
|
||||
import type {
|
||||
IBinaryData,
|
||||
|
|
@ -50,20 +51,13 @@ import {
|
|||
} from 'n8n-workflow';
|
||||
import { finished } from 'stream/promises';
|
||||
|
||||
import { WebhookService } from './webhook.service';
|
||||
import {
|
||||
WebhookResponseHeaders,
|
||||
type WebhookNodeResponseHeaders,
|
||||
} from './webhook-response-headers';
|
||||
import type { IWebhookResponseCallbackData, WebhookRequest } from './webhook.types';
|
||||
|
||||
import { ActiveExecutions } from '@/active-executions';
|
||||
import { AuthService } from '@/auth/auth.service';
|
||||
import { MCP_TRIGGER_NODE_TYPE } from '@/constants';
|
||||
import { EventService } from '@/events/event.service';
|
||||
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||
import { UnprocessableRequestError } from '@/errors/response-errors/unprocessable.error';
|
||||
import { EventService } from '@/events/event.service';
|
||||
import { parseBody } from '@/middlewares';
|
||||
import { OwnershipService } from '@/services/ownership.service';
|
||||
import { WorkflowStatisticsService } from '@/services/workflow-statistics.service';
|
||||
|
|
@ -77,7 +71,13 @@ import { createStaticResponse, createStreamResponse } from '@/webhooks/webhook-r
|
|||
import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data';
|
||||
import * as WorkflowHelpers from '@/workflow-helpers';
|
||||
import { WorkflowRunner } from '@/workflow-runner';
|
||||
import merge from 'lodash/merge';
|
||||
|
||||
import {
|
||||
WebhookResponseHeaders,
|
||||
type WebhookNodeResponseHeaders,
|
||||
} from './webhook-response-headers';
|
||||
import { WebhookService } from './webhook.service';
|
||||
import type { IWebhookResponseCallbackData, WebhookRequest } from './webhook.types';
|
||||
|
||||
// Type guards for MCP queue mode data validation
|
||||
interface McpToolCallPayload {
|
||||
|
|
@ -944,6 +944,8 @@ export async function executeWebhook(
|
|||
return runData;
|
||||
})
|
||||
.catch((e) => {
|
||||
Container.get(ErrorReporter).error(e, { executionId });
|
||||
|
||||
if (!didSendResponse) {
|
||||
responseCallback(
|
||||
new OperationalError('There was a problem executing the workflow', {
|
||||
|
|
@ -960,12 +962,13 @@ export async function executeWebhook(
|
|||
}
|
||||
return executionId;
|
||||
} catch (e) {
|
||||
const error =
|
||||
e instanceof UnprocessableRequestError
|
||||
? e
|
||||
: new OperationalError('There was a problem executing the workflow', {
|
||||
cause: e,
|
||||
});
|
||||
let error: Error;
|
||||
if (e instanceof UnprocessableRequestError) {
|
||||
error = e;
|
||||
} else {
|
||||
Container.get(ErrorReporter).error(e, { executionId });
|
||||
error = new OperationalError('There was a problem executing the workflow', { cause: e });
|
||||
}
|
||||
if (didSendResponse) throw error;
|
||||
responseCallback(error, {});
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ class WebhookRequestHandler {
|
|||
} else {
|
||||
logger.error(
|
||||
`Error in handling webhook request ${req.method} ${req.path}: ${error.message}`,
|
||||
{ stacktrace: error.stack },
|
||||
{ error },
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user