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 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import { ApiKeyRequest } from '../middleware/apiKey';
|
|||||||
import { mainPool } from '../config/database';
|
import { mainPool } from '../config/database';
|
||||||
import { sqlExecutor } from '../services/SqlExecutor';
|
import { sqlExecutor } from '../services/SqlExecutor';
|
||||||
import { scriptExecutor } from '../services/ScriptExecutor';
|
import { scriptExecutor } from '../services/ScriptExecutor';
|
||||||
import { EndpointParameter, ScriptQuery } from '../types';
|
import { EndpointParameter, ScriptQuery, ScriptExecutionError } from '../types';
|
||||||
|
|
||||||
export const executeDynamicEndpoint = async (req: ApiKeyRequest, res: Response) => {
|
export const executeDynamicEndpoint = async (req: ApiKeyRequest, res: Response) => {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
@@ -202,9 +202,11 @@ export const executeDynamicEndpoint = async (req: ApiKeyRequest, res: Response)
|
|||||||
});
|
});
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
rows: scriptResult,
|
rows: scriptResult.result,
|
||||||
rowCount: 0,
|
rowCount: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
scriptLogs: scriptResult.logs,
|
||||||
|
scriptQueries: scriptResult.queries,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Execute SQL query
|
// Execute SQL query
|
||||||
@@ -241,6 +243,8 @@ export const executeDynamicEndpoint = async (req: ApiKeyRequest, res: Response)
|
|||||||
data: result.rows,
|
data: result.rows,
|
||||||
rowCount: result.rowCount,
|
rowCount: result.rowCount,
|
||||||
executionTime: result.executionTime,
|
executionTime: result.executionTime,
|
||||||
|
...(result.scriptLogs && result.scriptLogs.length > 0 ? { logs: result.scriptLogs } : {}),
|
||||||
|
...(result.scriptQueries && result.scriptQueries.length > 0 ? { queries: result.scriptQueries } : {}),
|
||||||
}
|
}
|
||||||
: result.rows;
|
: result.rows;
|
||||||
|
|
||||||
@@ -267,9 +271,12 @@ export const executeDynamicEndpoint = async (req: ApiKeyRequest, res: Response)
|
|||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Dynamic API execution error:', error);
|
console.error('Dynamic API execution error:', error);
|
||||||
|
|
||||||
const errorResponse = {
|
const isScriptError = error instanceof ScriptExecutionError;
|
||||||
|
const errorResponse: any = {
|
||||||
success: false,
|
success: false,
|
||||||
error: error.message,
|
error: error.message,
|
||||||
|
...(isScriptError && error.logs.length > 0 ? { logs: error.logs } : {}),
|
||||||
|
...(isScriptError && error.queries.length > 0 ? { queries: error.queries } : {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Log error if needed
|
// Log error if needed
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Response } from 'express';
|
|||||||
import { AuthRequest } from '../middleware/auth';
|
import { AuthRequest } from '../middleware/auth';
|
||||||
import { mainPool } from '../config/database';
|
import { mainPool } from '../config/database';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { ExportedEndpoint, ExportedScriptQuery } from '../types';
|
import { ExportedEndpoint, ExportedScriptQuery, ScriptExecutionError } from '../types';
|
||||||
import { encryptEndpointData, decryptEndpointData } from '../services/endpointCrypto';
|
import { encryptEndpointData, decryptEndpointData } from '../services/endpointCrypto';
|
||||||
|
|
||||||
export const getEndpoints = async (req: AuthRequest, res: Response) => {
|
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' });
|
return res.status(400).json({ error: 'Invalid execution_type' });
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
const isScriptError = error instanceof ScriptExecutionError;
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: error.message,
|
error: error.message,
|
||||||
detail: error.detail || undefined,
|
detail: error.detail || undefined,
|
||||||
hint: error.hint || undefined,
|
hint: error.hint || undefined,
|
||||||
logs: [],
|
logs: isScriptError ? error.logs : [],
|
||||||
queries: [],
|
queries: isScriptError ? error.queries : [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as vm from 'vm';
|
import * as vm from 'vm';
|
||||||
import { sqlExecutor } from './SqlExecutor';
|
import { sqlExecutor } from './SqlExecutor';
|
||||||
import { aqlExecutor } from './AqlExecutor';
|
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 { databasePoolManager } from './DatabasePoolManager';
|
||||||
|
|
||||||
interface IsolatedScriptContext {
|
interface IsolatedScriptContext {
|
||||||
@@ -228,7 +228,9 @@ export class IsolatedScriptExecutor {
|
|||||||
}
|
}
|
||||||
timerIds.clear();
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import { sqlExecutor } from './SqlExecutor';
|
import { sqlExecutor } from './SqlExecutor';
|
||||||
import { aqlExecutor } from './AqlExecutor';
|
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 { databasePoolManager } from './DatabasePoolManager';
|
||||||
import { isolatedScriptExecutor } from './IsolatedScriptExecutor';
|
import { isolatedScriptExecutor } from './IsolatedScriptExecutor';
|
||||||
|
|
||||||
@@ -258,7 +258,8 @@ print(json.dumps(result))
|
|||||||
|
|
||||||
python.on('close', (exitCode) => {
|
python.on('close', (exitCode) => {
|
||||||
if (exitCode !== 0) {
|
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 {
|
} else {
|
||||||
try {
|
try {
|
||||||
// Последняя строка вывода - результат, остальные - логи
|
// Последняя строка вывода - результат, остальные - логи
|
||||||
@@ -276,7 +277,7 @@ print(json.dumps(result))
|
|||||||
const result = JSON.parse(resultLine);
|
const result = JSON.parse(resultLine);
|
||||||
resolve({ result, logs, queries });
|
resolve({ result, logs, queries });
|
||||||
} catch (error) {
|
} 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 минут
|
// Таймаут 10 минут
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
python.kill();
|
python.kill();
|
||||||
reject(new Error('Python script execution timeout (10min)'));
|
reject(new ScriptExecutionError('Python script execution timeout (10min)', logs, queries));
|
||||||
}, 600000);
|
}, 600000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,6 +123,18 @@ export interface IsolatedExecutionResult {
|
|||||||
queries: QueryExecution[];
|
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 {
|
export interface SwaggerEndpoint {
|
||||||
tags: string[];
|
tags: string[];
|
||||||
summary: string;
|
summary: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user