Skip to content

Commit 73f224d

Browse files
committed
fix: better error logging in sim console
1 parent 01e1054 commit 73f224d

File tree

4 files changed

+473
-37
lines changed

4 files changed

+473
-37
lines changed

sim/app/executor/handlers.ts

Lines changed: 172 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -793,15 +793,123 @@ export class ApiBlockHandler implements BlockHandler {
793793
throw new Error(`Tool not found: ${block.config.tool}`)
794794
}
795795

796-
const result = await executeTool(block.config.tool, {
797-
...inputs,
798-
_context: { workflowId: context.workflowId },
799-
})
800-
if (!result.success) {
801-
throw new Error(result.error || `API request failed with no error message`)
796+
// Early return with empty success response if URL is not provided or empty
797+
if (tool.name && tool.name.includes('HTTP') && (!inputs.url || inputs.url.trim() === '')) {
798+
return { response: { content: '', success: true } }
802799
}
803800

804-
return { response: result.output }
801+
// Pre-validate common HTTP request issues to provide better error messages
802+
if (tool.name && tool.name.includes('HTTP') && inputs.url) {
803+
// Check for missing protocol
804+
if (!inputs.url.match(/^https?:\/\//i)) {
805+
throw new Error(
806+
`Invalid URL: "${inputs.url}" - URL must include protocol (try "https://${inputs.url}")`
807+
);
808+
}
809+
810+
// Detect other common URL issues
811+
try {
812+
new URL(inputs.url);
813+
} catch (e: any) {
814+
throw new Error(`Invalid URL format: "${inputs.url}" - ${e.message}`);
815+
}
816+
}
817+
818+
try {
819+
const result = await executeTool(block.config.tool, {
820+
...inputs,
821+
_context: { workflowId: context.workflowId },
822+
})
823+
824+
if (!result.success) {
825+
const errorDetails = [];
826+
827+
// Add request details to error message
828+
if (inputs.url) errorDetails.push(`URL: ${inputs.url}`);
829+
if (inputs.method) errorDetails.push(`Method: ${inputs.method}`);
830+
831+
// Add response details
832+
if (result.error) errorDetails.push(`Error: ${result.error}`);
833+
if (result.output?.status) errorDetails.push(`Status: ${result.output.status}`);
834+
if (result.output?.statusText) errorDetails.push(`Status text: ${result.output.statusText}`);
835+
836+
// Add specific suggestions for common error codes
837+
let suggestion = '';
838+
if (result.output?.status === 403) {
839+
suggestion = ' - This may be due to CORS restrictions or authorization issues';
840+
} else if (result.output?.status === 404) {
841+
suggestion = ' - The requested resource was not found';
842+
} else if (result.output?.status === 429) {
843+
suggestion = ' - Too many requests, you may need to implement rate limiting';
844+
} else if (result.output?.status >= 500) {
845+
suggestion = ' - Server error, the target server is experiencing issues';
846+
} else if (result.error && result.error.includes('CORS')) {
847+
suggestion = ' - CORS policy prevented the request, try using a proxy or server-side request';
848+
} else if (result.error && result.error.includes('Failed to fetch')) {
849+
suggestion = ' - Network error, check if the URL is accessible and if you have internet connectivity';
850+
}
851+
852+
const errorMessage = errorDetails.length > 0
853+
? `HTTP Request failed: ${errorDetails.join(' | ')}${suggestion}`
854+
: `API request to ${tool.name || block.config.tool} failed with no error message`;
855+
856+
// Create a detailed error object with formatted message
857+
const error = new Error(errorMessage);
858+
859+
// Add additional properties for debugging
860+
Object.assign(error, {
861+
toolId: block.config.tool,
862+
toolName: tool.name || 'Unknown tool',
863+
blockId: block.id,
864+
blockName: block.metadata?.name || 'Unnamed Block',
865+
output: result.output || {},
866+
status: result.output?.status || null,
867+
request: {
868+
url: inputs.url,
869+
method: inputs.method || 'GET',
870+
},
871+
timestamp: new Date().toISOString()
872+
});
873+
874+
throw error;
875+
}
876+
877+
return { response: result.output }
878+
} catch (error: any) {
879+
// Ensure we have a meaningful error message
880+
if (!error.message || error.message === "undefined (undefined)") {
881+
// Construct a detailed error message with available information
882+
let errorMessage = `API request to ${tool.name || block.config.tool} failed`;
883+
884+
// Add details if available
885+
if (inputs.url) errorMessage += `: ${inputs.url}`;
886+
if (error.status) errorMessage += ` (Status: ${error.status})`;
887+
if (error.statusText) errorMessage += ` - ${error.statusText}`;
888+
889+
// If we still have no details, give a generic but helpful message
890+
if (errorMessage === `API request to ${tool.name || block.config.tool} failed`) {
891+
errorMessage += ` - ${block.metadata?.name || 'Unknown error'}`;
892+
}
893+
894+
error.message = errorMessage;
895+
}
896+
897+
// Add additional context to the error
898+
if (typeof error === 'object' && error !== null) {
899+
if (!error.toolId) error.toolId = block.config.tool;
900+
if (!error.blockName) error.blockName = block.metadata?.name || 'Unnamed Block';
901+
902+
// Add request details if missing
903+
if (inputs && !error.request) {
904+
error.request = {
905+
url: inputs.url,
906+
method: inputs.method || 'GET'
907+
};
908+
}
909+
}
910+
911+
throw error;
912+
}
805913
}
806914
}
807915

@@ -860,14 +968,63 @@ export class GenericBlockHandler implements BlockHandler {
860968
throw new Error(`Tool not found: ${block.config.tool}`)
861969
}
862970

863-
const result = await executeTool(block.config.tool, {
864-
...inputs,
865-
_context: { workflowId: context.workflowId },
866-
})
867-
if (!result.success) {
868-
throw new Error(result.error || `Block execution failed with no error message`)
869-
}
971+
try {
972+
const result = await executeTool(block.config.tool, {
973+
...inputs,
974+
_context: { workflowId: context.workflowId },
975+
})
976+
977+
if (!result.success) {
978+
const errorDetails = [];
979+
if (result.error) errorDetails.push(result.error);
980+
981+
const errorMessage = errorDetails.length > 0
982+
? errorDetails.join(' - ')
983+
: `Block execution of ${tool.name || block.config.tool} failed with no error message`;
984+
985+
// Create a detailed error object with formatted message
986+
const error = new Error(errorMessage);
987+
988+
// Add additional properties for debugging
989+
Object.assign(error, {
990+
toolId: block.config.tool,
991+
toolName: tool.name || 'Unknown tool',
992+
blockId: block.id,
993+
blockName: block.metadata?.name || 'Unnamed Block',
994+
output: result.output || {},
995+
timestamp: new Date().toISOString()
996+
});
997+
998+
throw error;
999+
}
8701000

871-
return { response: result.output }
1001+
return { response: result.output }
1002+
} catch (error: any) {
1003+
// Ensure we have a meaningful error message
1004+
if (!error.message || error.message === "undefined (undefined)") {
1005+
// Construct a detailed error message with available information
1006+
let errorMessage = `Block execution of ${tool.name || block.config.tool} failed`;
1007+
1008+
// Add block name if available
1009+
if (block.metadata?.name) {
1010+
errorMessage += `: ${block.metadata.name}`;
1011+
}
1012+
1013+
// Add status code if available
1014+
if (error.status) {
1015+
errorMessage += ` (Status: ${error.status})`;
1016+
}
1017+
1018+
error.message = errorMessage;
1019+
}
1020+
1021+
// Add additional context to the error
1022+
if (typeof error === 'object' && error !== null) {
1023+
if (!error.toolId) error.toolId = block.config.tool;
1024+
if (!error.blockName) error.blockName = block.metadata?.name || 'Unnamed Block';
1025+
}
1026+
1027+
throw error;
1028+
}
8721029
}
8731030
}

sim/app/executor/index.ts

Lines changed: 134 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,12 @@ export class Executor {
114114
logs: context.blockLogs,
115115
}
116116
} catch (error: any) {
117-
logger.error('Workflow execution failed:', { error })
117+
logger.error('Workflow execution failed:', this.sanitizeError(error))
118118

119119
return {
120120
success: false,
121121
output: finalOutput,
122-
error: error.message || 'Workflow execution failed',
122+
error: this.extractErrorMessage(error),
123123
logs: context.blockLogs,
124124
}
125125
} finally {
@@ -442,15 +442,15 @@ export class Executor {
442442
return output
443443
} catch (error: any) {
444444
blockLog.success = false
445-
blockLog.error = error.message
445+
blockLog.error = error.message || `Error executing ${block.metadata?.id || 'unknown'} block: ${String(error)}`
446446
blockLog.endedAt = new Date().toISOString()
447447
blockLog.durationMs =
448448
new Date(blockLog.endedAt).getTime() - new Date(blockLog.startedAt).getTime()
449449

450450
context.blockLogs.push(blockLog)
451451
addConsole({
452452
output: {},
453-
error: error.message,
453+
error: error.message || `Error executing ${block.metadata?.id || 'unknown'} block: ${String(error)}`,
454454
durationMs: blockLog.durationMs,
455455
startedAt: blockLog.startedAt,
456456
endedAt: blockLog.endedAt,
@@ -460,7 +460,22 @@ export class Executor {
460460
blockType: block.metadata?.id || 'unknown',
461461
})
462462

463-
throw error
463+
// Create a proper error message that is never undefined
464+
let errorMessage = error.message;
465+
466+
// Handle the specific "undefined (undefined)" case
467+
if (!errorMessage || errorMessage === "undefined (undefined)") {
468+
errorMessage = `Error executing ${block.metadata?.id || 'unknown'} block: ${block.metadata?.name || 'Unnamed Block'}`;
469+
470+
// Try to get more details if possible
471+
if (error && typeof error === 'object') {
472+
if (error.code) errorMessage += ` (code: ${error.code})`;
473+
if (error.status) errorMessage += ` (status: ${error.status})`;
474+
if (error.type) errorMessage += ` (type: ${error.type})`;
475+
}
476+
}
477+
478+
throw new Error(errorMessage);
464479
}
465480
}
466481

@@ -590,4 +605,118 @@ export class Executor {
590605
success: false,
591606
}
592607
}
608+
609+
/**
610+
* Extracts a meaningful error message from any error object structure.
611+
* Handles nested error objects, undefined messages, and various error formats.
612+
*
613+
* @param error - The error object to extract a message from
614+
* @returns A meaningful error message string
615+
*/
616+
private extractErrorMessage(error: any): string {
617+
if (!error) return 'Unknown error occurred';
618+
619+
// Handle Error instances
620+
if (error instanceof Error) {
621+
return error.message || `Error: ${String(error)}`;
622+
}
623+
624+
// Handle string errors
625+
if (typeof error === 'string') {
626+
return error;
627+
}
628+
629+
// Handle object errors with nested structure
630+
if (typeof error === 'object') {
631+
// Case: { error: { message: "msg" } }
632+
if (error.error && typeof error.error === 'object' && error.error.message) {
633+
return error.error.message;
634+
}
635+
636+
// Case: { error: "msg" }
637+
if (error.error && typeof error.error === 'string') {
638+
return error.error;
639+
}
640+
641+
// Case: { message: "msg" }
642+
if (error.message) {
643+
return error.message;
644+
}
645+
646+
// Add specific handling for HTTP errors
647+
if (error.status || error.request) {
648+
let message = 'API request failed';
649+
650+
// Add URL information if available
651+
if (error.request && error.request.url) {
652+
message += `: ${error.request.url}`;
653+
}
654+
655+
// Add status code if available
656+
if (error.status) {
657+
message += ` (Status: ${error.status})`;
658+
}
659+
660+
return message;
661+
}
662+
663+
// Last resort: try to stringify the object
664+
try {
665+
return `Error details: ${JSON.stringify(error)}`;
666+
} catch {
667+
return 'Error occurred but details could not be displayed';
668+
}
669+
}
670+
671+
return 'Unknown error occurred';
672+
}
673+
674+
/**
675+
* Sanitizes an error object for logging purposes.
676+
* Ensures the error is in a format that won't cause "undefined" to appear in logs.
677+
*
678+
* @param error - The error object to sanitize
679+
* @returns A sanitized version of the error for logging
680+
*/
681+
private sanitizeError(error: any): any {
682+
if (!error) return { message: 'No error details available' };
683+
684+
// Handle Error instances
685+
if (error instanceof Error) {
686+
return {
687+
message: error.message || 'Error without message',
688+
stack: error.stack
689+
};
690+
}
691+
692+
// Handle string errors
693+
if (typeof error === 'string') {
694+
return { message: error };
695+
}
696+
697+
// Handle object errors with nested structure
698+
if (typeof error === 'object') {
699+
// If error has a nested error object with undefined message, fix it
700+
if (error.error && typeof error.error === 'object') {
701+
if (!error.error.message) {
702+
error.error.message = 'No specific error message provided';
703+
}
704+
}
705+
706+
// If no message property exists at root level, add one
707+
if (!error.message) {
708+
if (error.error && typeof error.error === 'string') {
709+
error.message = error.error;
710+
} else if (error.status) {
711+
error.message = `API request failed with status ${error.status}`;
712+
} else {
713+
error.message = 'Error occurred during workflow execution';
714+
}
715+
}
716+
717+
return error;
718+
}
719+
720+
return { message: `Unexpected error type: ${typeof error}` };
721+
}
593722
}

0 commit comments

Comments
 (0)