mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix: better error logging in sim console
This commit is contained in:
+172
-15
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user