fix: better error logging in sim console

This commit is contained in:
Emir Karabeg
2025-03-20 18:45:45 -07:00
parent 01e1054f58
commit 73f224de33
4 changed files with 473 additions and 37 deletions
+172 -15
View File
@@ -793,15 +793,123 @@ export class ApiBlockHandler implements BlockHandler {
throw new Error(`Tool not found: ${block.config.tool}`)
}
const result = await executeTool(block.config.tool, {
...inputs,
_context: { workflowId: context.workflowId },
})
if (!result.success) {
throw new Error(result.error || `API request failed with no error message`)
// Early return with empty success response if URL is not provided or empty
if (tool.name && tool.name.includes('HTTP') && (!inputs.url || inputs.url.trim() === '')) {
return { response: { content: '', success: true } }
}
return { response: result.output }
// Pre-validate common HTTP request issues to provide better error messages
if (tool.name && tool.name.includes('HTTP') && inputs.url) {
// Check for missing protocol
if (!inputs.url.match(/^https?:\/\//i)) {
throw new Error(
`Invalid URL: "${inputs.url}" - URL must include protocol (try "https://${inputs.url}")`
);
}
// Detect other common URL issues
try {
new URL(inputs.url);
} catch (e: any) {
throw new Error(`Invalid URL format: "${inputs.url}" - ${e.message}`);
}
}
try {
const result = await executeTool(block.config.tool, {
...inputs,
_context: { workflowId: context.workflowId },
})
if (!result.success) {
const errorDetails = [];
// Add request details to error message
if (inputs.url) errorDetails.push(`URL: ${inputs.url}`);
if (inputs.method) errorDetails.push(`Method: ${inputs.method}`);
// Add response details
if (result.error) errorDetails.push(`Error: ${result.error}`);
if (result.output?.status) errorDetails.push(`Status: ${result.output.status}`);
if (result.output?.statusText) errorDetails.push(`Status text: ${result.output.statusText}`);
// Add specific suggestions for common error codes
let suggestion = '';
if (result.output?.status === 403) {
suggestion = ' - This may be due to CORS restrictions or authorization issues';
} else if (result.output?.status === 404) {
suggestion = ' - The requested resource was not found';
} else if (result.output?.status === 429) {
suggestion = ' - Too many requests, you may need to implement rate limiting';
} else if (result.output?.status >= 500) {
suggestion = ' - Server error, the target server is experiencing issues';
} else if (result.error && result.error.includes('CORS')) {
suggestion = ' - CORS policy prevented the request, try using a proxy or server-side request';
} else if (result.error && result.error.includes('Failed to fetch')) {
suggestion = ' - Network error, check if the URL is accessible and if you have internet connectivity';
}
const errorMessage = errorDetails.length > 0
? `HTTP Request failed: ${errorDetails.join(' | ')}${suggestion}`
: `API request to ${tool.name || block.config.tool} failed with no error message`;
// Create a detailed error object with formatted message
const error = new Error(errorMessage);
// Add additional properties for debugging
Object.assign(error, {
toolId: block.config.tool,
toolName: tool.name || 'Unknown tool',
blockId: block.id,
blockName: block.metadata?.name || 'Unnamed Block',
output: result.output || {},
status: result.output?.status || null,
request: {
url: inputs.url,
method: inputs.method || 'GET',
},
timestamp: new Date().toISOString()
});
throw error;
}
return { response: result.output }
} catch (error: any) {
// Ensure we have a meaningful error message
if (!error.message || error.message === "undefined (undefined)") {
// Construct a detailed error message with available information
let errorMessage = `API request to ${tool.name || block.config.tool} failed`;
// Add details if available
if (inputs.url) errorMessage += `: ${inputs.url}`;
if (error.status) errorMessage += ` (Status: ${error.status})`;
if (error.statusText) errorMessage += ` - ${error.statusText}`;
// If we still have no details, give a generic but helpful message
if (errorMessage === `API request to ${tool.name || block.config.tool} failed`) {
errorMessage += ` - ${block.metadata?.name || 'Unknown error'}`;
}
error.message = errorMessage;
}
// Add additional context to the error
if (typeof error === 'object' && error !== null) {
if (!error.toolId) error.toolId = block.config.tool;
if (!error.blockName) error.blockName = block.metadata?.name || 'Unnamed Block';
// Add request details if missing
if (inputs && !error.request) {
error.request = {
url: inputs.url,
method: inputs.method || 'GET'
};
}
}
throw error;
}
}
}
@@ -860,14 +968,63 @@ export class GenericBlockHandler implements BlockHandler {
throw new Error(`Tool not found: ${block.config.tool}`)
}
const result = await executeTool(block.config.tool, {
...inputs,
_context: { workflowId: context.workflowId },
})
if (!result.success) {
throw new Error(result.error || `Block execution failed with no error message`)
}
try {
const result = await executeTool(block.config.tool, {
...inputs,
_context: { workflowId: context.workflowId },
})
if (!result.success) {
const errorDetails = [];
if (result.error) errorDetails.push(result.error);
const errorMessage = errorDetails.length > 0
? errorDetails.join(' - ')
: `Block execution of ${tool.name || block.config.tool} failed with no error message`;
// Create a detailed error object with formatted message
const error = new Error(errorMessage);
// Add additional properties for debugging
Object.assign(error, {
toolId: block.config.tool,
toolName: tool.name || 'Unknown tool',
blockId: block.id,
blockName: block.metadata?.name || 'Unnamed Block',
output: result.output || {},
timestamp: new Date().toISOString()
});
throw error;
}
return { response: result.output }
return { response: result.output }
} catch (error: any) {
// Ensure we have a meaningful error message
if (!error.message || error.message === "undefined (undefined)") {
// Construct a detailed error message with available information
let errorMessage = `Block execution of ${tool.name || block.config.tool} failed`;
// Add block name if available
if (block.metadata?.name) {
errorMessage += `: ${block.metadata.name}`;
}
// Add status code if available
if (error.status) {
errorMessage += ` (Status: ${error.status})`;
}
error.message = errorMessage;
}
// Add additional context to the error
if (typeof error === 'object' && error !== null) {
if (!error.toolId) error.toolId = block.config.tool;
if (!error.blockName) error.blockName = block.metadata?.name || 'Unnamed Block';
}
throw error;
}
}
}
+134 -5
View File
@@ -114,12 +114,12 @@ export class Executor {
logs: context.blockLogs,
}
} catch (error: any) {
logger.error('Workflow execution failed:', { error })
logger.error('Workflow execution failed:', this.sanitizeError(error))
return {
success: false,
output: finalOutput,
error: error.message || 'Workflow execution failed',
error: this.extractErrorMessage(error),
logs: context.blockLogs,
}
} finally {
@@ -442,7 +442,7 @@ export class Executor {
return output
} catch (error: any) {
blockLog.success = false
blockLog.error = error.message
blockLog.error = error.message || `Error executing ${block.metadata?.id || 'unknown'} block: ${String(error)}`
blockLog.endedAt = new Date().toISOString()
blockLog.durationMs =
new Date(blockLog.endedAt).getTime() - new Date(blockLog.startedAt).getTime()
@@ -450,7 +450,7 @@ export class Executor {
context.blockLogs.push(blockLog)
addConsole({
output: {},
error: error.message,
error: error.message || `Error executing ${block.metadata?.id || 'unknown'} block: ${String(error)}`,
durationMs: blockLog.durationMs,
startedAt: blockLog.startedAt,
endedAt: blockLog.endedAt,
@@ -460,7 +460,22 @@ export class Executor {
blockType: block.metadata?.id || 'unknown',
})
throw error
// Create a proper error message that is never undefined
let errorMessage = error.message;
// Handle the specific "undefined (undefined)" case
if (!errorMessage || errorMessage === "undefined (undefined)") {
errorMessage = `Error executing ${block.metadata?.id || 'unknown'} block: ${block.metadata?.name || 'Unnamed Block'}`;
// Try to get more details if possible
if (error && typeof error === 'object') {
if (error.code) errorMessage += ` (code: ${error.code})`;
if (error.status) errorMessage += ` (status: ${error.status})`;
if (error.type) errorMessage += ` (type: ${error.type})`;
}
}
throw new Error(errorMessage);
}
}
@@ -590,4 +605,118 @@ export class Executor {
success: false,
}
}
/**
* Extracts a meaningful error message from any error object structure.
* Handles nested error objects, undefined messages, and various error formats.
*
* @param error - The error object to extract a message from
* @returns A meaningful error message string
*/
private extractErrorMessage(error: any): string {
if (!error) return 'Unknown error occurred';
// Handle Error instances
if (error instanceof Error) {
return error.message || `Error: ${String(error)}`;
}
// Handle string errors
if (typeof error === 'string') {
return error;
}
// Handle object errors with nested structure
if (typeof error === 'object') {
// Case: { error: { message: "msg" } }
if (error.error && typeof error.error === 'object' && error.error.message) {
return error.error.message;
}
// Case: { error: "msg" }
if (error.error && typeof error.error === 'string') {
return error.error;
}
// Case: { message: "msg" }
if (error.message) {
return error.message;
}
// Add specific handling for HTTP errors
if (error.status || error.request) {
let message = 'API request failed';
// Add URL information if available
if (error.request && error.request.url) {
message += `: ${error.request.url}`;
}
// Add status code if available
if (error.status) {
message += ` (Status: ${error.status})`;
}
return message;
}
// Last resort: try to stringify the object
try {
return `Error details: ${JSON.stringify(error)}`;
} catch {
return 'Error occurred but details could not be displayed';
}
}
return 'Unknown error occurred';
}
/**
* Sanitizes an error object for logging purposes.
* Ensures the error is in a format that won't cause "undefined" to appear in logs.
*
* @param error - The error object to sanitize
* @returns A sanitized version of the error for logging
*/
private sanitizeError(error: any): any {
if (!error) return { message: 'No error details available' };
// Handle Error instances
if (error instanceof Error) {
return {
message: error.message || 'Error without message',
stack: error.stack
};
}
// Handle string errors
if (typeof error === 'string') {
return { message: error };
}
// Handle object errors with nested structure
if (typeof error === 'object') {
// If error has a nested error object with undefined message, fix it
if (error.error && typeof error.error === 'object') {
if (!error.error.message) {
error.error.message = 'No specific error message provided';
}
}
// If no message property exists at root level, add one
if (!error.message) {
if (error.error && typeof error.error === 'string') {
error.message = error.error;
} else if (error.status) {
error.message = `API request failed with status ${error.status}`;
} else {
error.message = 'Error occurred during workflow execution';
}
}
return error;
}
return { message: `Unexpected error type: ${typeof error}` };
}
}
+111 -14
View File
@@ -452,14 +452,66 @@ export async function executeTool(
logger.error(`Looking for custom tool with identifier: ${identifier}`)
}
// Process the error to ensure we have a useful message
let errorMessage = 'Unknown error occurred';
let errorDetails = {};
if (error instanceof Error) {
errorMessage = error.message || `Error executing tool ${toolId}`;
} else if (typeof error === 'string') {
errorMessage = error;
} else if (error && typeof error === 'object') {
// Handle API response errors
if (error.response) {
const response = error.response;
errorMessage = `API Error: ${response.statusText || response.status || 'Unknown status'}`;
// Try to extract more details from the response
if (response.data) {
if (typeof response.data === 'string') {
errorMessage = `${errorMessage} - ${response.data}`;
} else if (response.data.message) {
errorMessage = `${errorMessage} - ${response.data.message}`;
} else if (response.data.error) {
errorMessage = `${errorMessage} - ${typeof response.data.error === 'string' ?
response.data.error : JSON.stringify(response.data.error)}`;
}
}
// Include useful debugging information
errorDetails = {
status: response.status,
statusText: response.statusText,
data: response.data
};
}
// Handle fetch or other network errors
else if (error.message) {
// Don't pass along "undefined (undefined)" messages
if (error.message === "undefined (undefined)") {
errorMessage = `Error executing tool ${toolId}`;
// Add status if available
if (error.status) {
errorMessage += ` (Status: ${error.status})`;
}
} else {
errorMessage = error.message;
}
if (error.cause) {
errorMessage = `${errorMessage} (${error.cause})`;
}
}
}
// Add timing data even for errors
const endTime = new Date()
const endTimeISO = endTime.toISOString()
const duration = endTime.getTime() - startTime.getTime()
return {
success: false,
output: {},
error: error.message || 'Unknown error',
output: errorDetails,
error: errorMessage,
timing: {
startTime: startTimeISO,
endTime: endTimeISO,
@@ -700,21 +752,66 @@ async function handleProxyRequest(
}
const proxyUrl = new URL('/api/proxy', baseUrl).toString()
const response = await fetch(proxyUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ toolId, params }),
})
try {
const response = await fetch(proxyUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ toolId, params }),
});
const result = await response.json()
if (!response.ok) {
const errorText = await response.text();
let errorMessage = `HTTP error ${response.status}: ${response.statusText}`;
let errorDetails = { status: response.status, statusText: response.statusText };
try {
// Try to parse as JSON for more details
const errorJson = JSON.parse(errorText);
if (errorJson.error) {
errorMessage = typeof errorJson.error === 'string' ?
errorJson.error :
`API Error: ${response.status} ${response.statusText}`;
}
errorDetails = { ...errorDetails, ...errorJson };
} catch {
// If not JSON, use the raw text
if (errorText && errorText !== "undefined (undefined)") {
errorMessage = `${errorMessage} - ${errorText}`;
}
}
return {
success: false,
output: errorDetails,
error: errorMessage,
};
}
if (!result.success) {
const result = await response.json();
if (!result.success) {
return {
success: false,
output: result.output || {},
error: result.error || `API request to ${toolId} failed with no error message`,
};
}
return result;
} catch (error: any) {
// Handle network or other fetch errors
logger.error(`Error in proxy request for tool ${toolId}:`, { error });
let errorMessage = error instanceof Error ?
error.message :
typeof error === 'string' ?
error :
`Unknown error in API request to ${toolId}`;
return {
success: false,
output: {},
error: result.error,
}
output: { originalError: error },
error: errorMessage,
};
}
return result
}
+56 -3
View File
@@ -348,9 +348,42 @@ export function useWorkflowExecution() {
// Send the entire execution result to our API to be processed server-side
await persistLogs(executionId, result)
} catch (error: any) {
logger.error('Workflow Execution Error:', { error })
logger.error('Workflow Execution Error:', error)
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
// Properly extract error message ensuring it's never undefined
let errorMessage = 'Unknown error'
if (error instanceof Error) {
errorMessage = error.message || `Error: ${String(error)}`
} else if (typeof error === 'string') {
errorMessage = error
} else if (error && typeof error === 'object') {
// Fix the "undefined (undefined)" pattern specifically
if (error.message === 'undefined (undefined)' ||
(error.error && typeof error.error === 'object' && error.error.message === 'undefined (undefined)')) {
errorMessage = 'API request failed - no specific error details available';
}
// Try to extract error details from potential API or execution errors
else if (error.message) {
errorMessage = error.message
} else if (error.error && typeof error.error === 'string') {
errorMessage = error.error
} else if (error.error && typeof error.error === 'object' && error.error.message) {
errorMessage = error.error.message
} else {
// Last resort: stringify the whole object
try {
errorMessage = `Error details: ${JSON.stringify(error)}`
} catch {
errorMessage = 'Error occurred but details could not be displayed'
}
}
}
// Ensure errorMessage is never "undefined (undefined)"
if (errorMessage === 'undefined (undefined)') {
errorMessage = 'API request failed - no specific error details available';
}
// Set error result and show notification immediately
const errorResult = {
@@ -361,7 +394,27 @@ export function useWorkflowExecution() {
}
setExecutionResult(errorResult)
addNotification('error', `Workflow execution failed: ${errorMessage}`, activeWorkflowId)
// Create a more user-friendly notification message
let notificationMessage = `Workflow execution failed`;
// Add URL for HTTP errors
if (error && error.request && error.request.url) {
// Don't show empty URL errors
if (error.request.url && error.request.url.trim() !== '') {
notificationMessage += `: Request to ${error.request.url} failed`;
// Add status if available
if (error.status) {
notificationMessage += ` (Status: ${error.status})`;
}
}
} else {
// Regular errors
notificationMessage += `: ${errorMessage}`;
}
addNotification('error', notificationMessage, activeWorkflowId)
// Also send the error result to the API
await persistLogs(executionId, errorResult)