fix(core): Fix task runner logging to browser console (#15111)

This commit is contained in:
Iván Ovejero
2025-05-06 09:42:46 +02:00
committed by GitHub
parent 07e6c7e13f
commit e86edf536f
3 changed files with 52 additions and 5 deletions

View File

@@ -1,7 +1,14 @@
import { isObject } from 'lodash';
import set from 'lodash/set';
import { DateTime, Duration, Interval } from 'luxon';
import { getAdditionalKeys } from 'n8n-core';
import { WorkflowDataProxy, Workflow, ObservableObject, Expression } from 'n8n-workflow';
import {
WorkflowDataProxy,
Workflow,
ObservableObject,
Expression,
jsonStringify,
} from 'n8n-workflow';
import type {
CodeExecutionMode,
IWorkflowExecuteAdditionalData,
@@ -19,7 +26,6 @@ import type {
IWorkflowDataProxyData,
} from 'n8n-workflow';
import * as a from 'node:assert';
import { inspect } from 'node:util';
import { type Context, createContext, runInContext } from 'node:vm';
import type { MainConfig } from '@/config/main-config';
@@ -498,7 +504,11 @@ export class JsTaskRunner extends TaskRunner {
// Send log output back to the main process. It will take care of forwarding
// it to the UI or printing to console.
log: (...args: unknown[]) => {
const formattedLogArgs = args.map((arg) => inspect(arg));
const formattedLogArgs = args.map((arg) => {
if (isObject(arg) && '__isExecutionContext' in arg) return '[[ExecutionContext]]';
if (typeof arg === 'string') return `'${arg}'`;
return jsonStringify(arg, { replaceCircularRefs: true });
});
void this.makeRpcCall(taskId, 'logNodeOutput', formattedLogArgs);
},
};
@@ -521,7 +531,7 @@ export class JsTaskRunner extends TaskRunner {
additionalProperties: Record<string, unknown> = {},
): Context {
return createContext({
[inspect.custom]: () => '[[ExecutionContext]]',
__isExecutionContext: true,
require: this.requireResolver,
module: {},
console: this.buildCustomConsole(taskId),

View File

@@ -226,4 +226,37 @@ describe('ExecuteContext', () => {
]);
});
});
describe('logNodeOutput', () => {
it('when in manual mode, should parse JSON', () => {
const json = '{"key": "value", "nested": {"foo": "bar"}}';
const expectedParsedObject = { key: 'value', nested: { foo: 'bar' } };
const numberArg = 42;
const stringArg = 'hello world!';
const manualModeContext = new ExecuteContext(
workflow,
node,
additionalData,
'manual',
runExecutionData,
runIndex,
connectionInputData,
inputData,
executeData,
[closeFn],
abortSignal,
);
const sendMessageSpy = jest.spyOn(manualModeContext, 'sendMessageToUI');
manualModeContext.logNodeOutput(json, numberArg, stringArg);
expect(sendMessageSpy.mock.calls[0][0]).toEqual(expectedParsedObject);
expect(sendMessageSpy.mock.calls[0][1]).toBe(numberArg);
expect(sendMessageSpy.mock.calls[0][2]).toBe(stringArg);
sendMessageSpy.mockRestore();
});
});
});

View File

@@ -20,6 +20,7 @@ import {
ApplicationError,
createDeferredPromise,
createEnvProviderState,
jsonParse,
NodeConnectionTypes,
} from 'n8n-workflow';
@@ -183,7 +184,10 @@ export class ExecuteContext extends BaseExecuteContext implements IExecuteFuncti
logNodeOutput(...args: unknown[]): void {
if (this.mode === 'manual') {
this.sendMessageToUI(...args);
const parsedLogArgs = args.map((arg) =>
typeof arg === 'string' ? jsonParse(arg, { fallbackValue: arg }) : arg,
);
this.sendMessageToUI(...parsedLogArgs);
return;
}