From 49f262d8aea83e7771ba3eff94d273994c561b69 Mon Sep 17 00:00:00 2001 From: eshmeshek Date: Mon, 2 Mar 2026 02:52:51 +0300 Subject: [PATCH] Fix script execution logs being lost - Add ScriptExecutionError class that preserves captured logs/queries - IsolatedScriptExecutor: throw ScriptExecutionError with accumulated logs instead of plain Error on script failure - ScriptExecutor (Python): same fix for Python execution errors - testEndpoint: return captured logs/queries on script errors - dynamicApiController: correctly extract scriptResult.result instead of stuffing entire IsolatedExecutionResult into rows; include logs in detailed_response output Co-Authored-By: Claude Opus 4.6 --- backend/src/controllers/dynamicApiController.ts | 13 ++++++++++--- backend/src/controllers/endpointController.ts | 7 ++++--- backend/src/services/IsolatedScriptExecutor.ts | 6 ++++-- backend/src/services/ScriptExecutor.ts | 9 +++++---- backend/src/types/index.ts | 12 ++++++++++++ 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/backend/src/controllers/dynamicApiController.ts b/backend/src/controllers/dynamicApiController.ts index a50253e..d937715 100644 --- a/backend/src/controllers/dynamicApiController.ts +++ b/backend/src/controllers/dynamicApiController.ts @@ -3,7 +3,7 @@ import { ApiKeyRequest } from '../middleware/apiKey'; import { mainPool } from '../config/database'; import { sqlExecutor } from '../services/SqlExecutor'; import { scriptExecutor } from '../services/ScriptExecutor'; -import { EndpointParameter, ScriptQuery } from '../types'; +import { EndpointParameter, ScriptQuery, ScriptExecutionError } from '../types'; export const executeDynamicEndpoint = async (req: ApiKeyRequest, res: Response) => { const startTime = Date.now(); @@ -202,9 +202,11 @@ export const executeDynamicEndpoint = async (req: ApiKeyRequest, res: Response) }); result = { - rows: scriptResult, + rows: scriptResult.result, rowCount: 0, executionTime: 0, + scriptLogs: scriptResult.logs, + scriptQueries: scriptResult.queries, }; } else { // Execute SQL query @@ -241,6 +243,8 @@ export const executeDynamicEndpoint = async (req: ApiKeyRequest, res: Response) data: result.rows, rowCount: result.rowCount, executionTime: result.executionTime, + ...(result.scriptLogs && result.scriptLogs.length > 0 ? { logs: result.scriptLogs } : {}), + ...(result.scriptQueries && result.scriptQueries.length > 0 ? { queries: result.scriptQueries } : {}), } : result.rows; @@ -267,9 +271,12 @@ export const executeDynamicEndpoint = async (req: ApiKeyRequest, res: Response) } catch (error: any) { console.error('Dynamic API execution error:', error); - const errorResponse = { + const isScriptError = error instanceof ScriptExecutionError; + const errorResponse: any = { success: false, error: error.message, + ...(isScriptError && error.logs.length > 0 ? { logs: error.logs } : {}), + ...(isScriptError && error.queries.length > 0 ? { queries: error.queries } : {}), }; // Log error if needed diff --git a/backend/src/controllers/endpointController.ts b/backend/src/controllers/endpointController.ts index dbdc0ea..a3ef0cc 100644 --- a/backend/src/controllers/endpointController.ts +++ b/backend/src/controllers/endpointController.ts @@ -2,7 +2,7 @@ import { Response } from 'express'; import { AuthRequest } from '../middleware/auth'; import { mainPool } from '../config/database'; import { v4 as uuidv4 } from 'uuid'; -import { ExportedEndpoint, ExportedScriptQuery } from '../types'; +import { ExportedEndpoint, ExportedScriptQuery, ScriptExecutionError } from '../types'; import { encryptEndpointData, decryptEndpointData } from '../services/endpointCrypto'; export const getEndpoints = async (req: AuthRequest, res: Response) => { @@ -388,13 +388,14 @@ export const testEndpoint = async (req: AuthRequest, res: Response) => { return res.status(400).json({ error: 'Invalid execution_type' }); } } catch (error: any) { + const isScriptError = error instanceof ScriptExecutionError; res.status(400).json({ success: false, error: error.message, detail: error.detail || undefined, hint: error.hint || undefined, - logs: [], - queries: [], + logs: isScriptError ? error.logs : [], + queries: isScriptError ? error.queries : [], }); } }; diff --git a/backend/src/services/IsolatedScriptExecutor.ts b/backend/src/services/IsolatedScriptExecutor.ts index e16cc70..dba9c53 100644 --- a/backend/src/services/IsolatedScriptExecutor.ts +++ b/backend/src/services/IsolatedScriptExecutor.ts @@ -1,7 +1,7 @@ import * as vm from 'vm'; import { sqlExecutor } from './SqlExecutor'; import { aqlExecutor } from './AqlExecutor'; -import { ScriptQuery, EndpointParameter, LogEntry, QueryExecution, IsolatedExecutionResult } from '../types'; +import { ScriptQuery, EndpointParameter, LogEntry, QueryExecution, IsolatedExecutionResult, ScriptExecutionError } from '../types'; import { databasePoolManager } from './DatabasePoolManager'; interface IsolatedScriptContext { @@ -228,7 +228,9 @@ export class IsolatedScriptExecutor { } timerIds.clear(); - throw new Error(`JavaScript execution error: ${error.message}`); + // Preserve captured logs and queries in the error + logs.push({ type: 'error', message: error.message, timestamp: Date.now() }); + throw new ScriptExecutionError(`JavaScript execution error: ${error.message}`, logs, queries); } } diff --git a/backend/src/services/ScriptExecutor.ts b/backend/src/services/ScriptExecutor.ts index 7eb3a40..88938cb 100644 --- a/backend/src/services/ScriptExecutor.ts +++ b/backend/src/services/ScriptExecutor.ts @@ -1,7 +1,7 @@ import { spawn } from 'child_process'; import { sqlExecutor } from './SqlExecutor'; import { aqlExecutor } from './AqlExecutor'; -import { ScriptQuery, EndpointParameter, LogEntry, QueryExecution, IsolatedExecutionResult } from '../types'; +import { ScriptQuery, EndpointParameter, LogEntry, QueryExecution, IsolatedExecutionResult, ScriptExecutionError } from '../types'; import { databasePoolManager } from './DatabasePoolManager'; import { isolatedScriptExecutor } from './IsolatedScriptExecutor'; @@ -258,7 +258,8 @@ print(json.dumps(result)) python.on('close', (exitCode) => { if (exitCode !== 0) { - reject(new Error(`Python execution error: ${errorOutput}`)); + logs.push({ type: 'error', message: errorOutput, timestamp: Date.now() }); + reject(new ScriptExecutionError(`Python execution error: ${errorOutput}`, logs, queries)); } else { try { // Последняя строка вывода - результат, остальные - логи @@ -276,7 +277,7 @@ print(json.dumps(result)) const result = JSON.parse(resultLine); resolve({ result, logs, queries }); } catch (error) { - reject(new Error(`Failed to parse Python output: ${output}`)); + reject(new ScriptExecutionError(`Failed to parse Python output: ${output}`, logs, queries)); } } }); @@ -284,7 +285,7 @@ print(json.dumps(result)) // Таймаут 10 минут setTimeout(() => { python.kill(); - reject(new Error('Python script execution timeout (10min)')); + reject(new ScriptExecutionError('Python script execution timeout (10min)', logs, queries)); }, 600000); }); } diff --git a/backend/src/types/index.ts b/backend/src/types/index.ts index 999d5a1..71b64c3 100644 --- a/backend/src/types/index.ts +++ b/backend/src/types/index.ts @@ -123,6 +123,18 @@ export interface IsolatedExecutionResult { queries: QueryExecution[]; } +export class ScriptExecutionError extends Error { + logs: LogEntry[]; + queries: QueryExecution[]; + + constructor(message: string, logs: LogEntry[], queries: QueryExecution[]) { + super(message); + this.name = 'ScriptExecutionError'; + this.logs = logs; + this.queries = queries; + } +} + export interface SwaggerEndpoint { tags: string[]; summary: string;