Files
mev-beta/pkg/errors/structured_error.go
2025-11-08 10:37:52 -06:00

277 lines
8.6 KiB
Go

package errors
import (
"fmt"
"runtime"
"time"
)
// ErrorCategory represents the category of an error
type ErrorCategory string
const (
CategoryNetwork ErrorCategory = "NETWORK" // RPC, DNS, connection issues
CategoryParsing ErrorCategory = "PARSING" // ABI decoding, transaction parsing
CategoryValidation ErrorCategory = "VALIDATION" // Input validation, data validation
CategoryExecution ErrorCategory = "EXECUTION" // Transaction execution, contract calls
CategoryConfiguration ErrorCategory = "CONFIGURATION" // Config loading, invalid settings
CategoryDatabase ErrorCategory = "DATABASE" // Database operations
CategorySecurity ErrorCategory = "SECURITY" // Security violations, unauthorized access
CategoryMath ErrorCategory = "MATH" // Arithmetic errors, overflow/underflow
CategoryInternal ErrorCategory = "INTERNAL" // Internal logic errors, unexpected state
CategoryExternal ErrorCategory = "EXTERNAL" // External service failures
CategoryUnknown ErrorCategory = "UNKNOWN" // Uncategorized errors
)
// ErrorSeverity represents how critical an error is
type ErrorSeverity string
const (
SeverityDebug ErrorSeverity = "DEBUG" // Informational, not an actual error
SeverityInfo ErrorSeverity = "INFO" // Notable but not problematic
SeverityWarning ErrorSeverity = "WARNING" // Potential issue, should investigate
SeverityError ErrorSeverity = "ERROR" // Actual error, functionality impacted
SeverityCritical ErrorSeverity = "CRITICAL" // Critical error, immediate attention required
SeverityFatal ErrorSeverity = "FATAL" // Fatal error, system cannot continue
)
// StructuredError represents a comprehensive error with full context
type StructuredError struct {
// Core error information
Message string // Human-readable error message
Category ErrorCategory // Error category for classification
Severity ErrorSeverity // How critical is this error
// Origin tracking
File string // Source file where error occurred
Function string // Function where error occurred
Line int // Line number where error occurred
Package string // Go package where error occurred
// Context
Reason string // Why this error occurred (root cause)
Action string // What the code was trying to do when it failed
Impact string // Impact of this error on the system
Suggestion string // Suggested fix or next steps
Details map[string]interface{} // Additional structured context
UnderlyingErr error // Original error if wrapping
// Metadata
Timestamp time.Time // When the error occurred
ErrorID string // Unique identifier for this error instance
}
// Error implements the error interface
func (e *StructuredError) Error() string {
if e.UnderlyingErr != nil {
return fmt.Sprintf("[%s/%s] %s: %v (in %s:%d)",
e.Category, e.Severity, e.Message, e.UnderlyingErr, e.File, e.Line)
}
return fmt.Sprintf("[%s/%s] %s (in %s:%d)",
e.Category, e.Severity, e.Message, e.File, e.Line)
}
// Unwrap returns the underlying error for error chain support
func (e *StructuredError) Unwrap() error {
return e.UnderlyingErr
}
// NewStructuredError creates a new structured error with automatic caller tracking
func NewStructuredError(category ErrorCategory, severity ErrorSeverity, message string) *StructuredError {
return newStructuredErrorWithDepth(category, severity, message, 2)
}
// newStructuredErrorWithDepth creates a structured error with custom stack depth
func newStructuredErrorWithDepth(category ErrorCategory, severity ErrorSeverity, message string, depth int) *StructuredError {
// Get caller information
pc, file, line, ok := runtime.Caller(depth)
if !ok {
file = "unknown"
line = 0
}
funcName := "unknown"
packageName := "unknown"
if fn := runtime.FuncForPC(pc); fn != nil {
funcName = fn.Name()
// Extract package name from function name (format: "package/path.FunctionName")
for i := len(funcName) - 1; i >= 0; i-- {
if funcName[i] == '/' {
packageName = funcName[:i]
break
}
}
}
// Generate unique error ID
errorID := fmt.Sprintf("ERR-%d-%s", time.Now().UnixNano(), category)
return &StructuredError{
Message: message,
Category: category,
Severity: severity,
File: file,
Function: funcName,
Line: line,
Package: packageName,
Details: make(map[string]interface{}),
Timestamp: time.Now(),
ErrorID: errorID,
}
}
// WithReason adds the reason (root cause) for the error
func (e *StructuredError) WithReason(reason string) *StructuredError {
e.Reason = reason
return e
}
// WithAction describes what the code was trying to do
func (e *StructuredError) WithAction(action string) *StructuredError {
e.Action = action
return e
}
// WithImpact describes the impact of this error
func (e *StructuredError) WithImpact(impact string) *StructuredError {
e.Impact = impact
return e
}
// WithSuggestion provides a suggestion for fixing or handling the error
func (e *StructuredError) WithSuggestion(suggestion string) *StructuredError {
e.Suggestion = suggestion
return e
}
// WithDetail adds a key-value detail to the error context
func (e *StructuredError) WithDetail(key string, value interface{}) *StructuredError {
e.Details[key] = value
return e
}
// WithDetails adds multiple details at once
func (e *StructuredError) WithDetails(details map[string]interface{}) *StructuredError {
for k, v := range details {
e.Details[k] = v
}
return e
}
// Wrap wraps an underlying error with structured context
func (e *StructuredError) Wrap(err error) *StructuredError {
e.UnderlyingErr = err
return e
}
// FormatForLogging returns a comprehensive string representation for logging
func (e *StructuredError) FormatForLogging() string {
result := fmt.Sprintf(
"[%s] %s/%s: %s\n"+
" Origin: %s:%d (%s)\n"+
" ErrorID: %s\n"+
" Timestamp: %s\n",
e.ErrorID,
e.Category,
e.Severity,
e.Message,
e.File,
e.Line,
e.Function,
e.ErrorID,
e.Timestamp.Format(time.RFC3339),
)
if e.Reason != "" {
result += fmt.Sprintf(" Reason: %s\n", e.Reason)
}
if e.Action != "" {
result += fmt.Sprintf(" Action: %s\n", e.Action)
}
if e.Impact != "" {
result += fmt.Sprintf(" Impact: %s\n", e.Impact)
}
if e.Suggestion != "" {
result += fmt.Sprintf(" Suggestion: %s\n", e.Suggestion)
}
if len(e.Details) > 0 {
result += " Details:\n"
for k, v := range e.Details {
result += fmt.Sprintf(" - %s: %v\n", k, v)
}
}
if e.UnderlyingErr != nil {
result += fmt.Sprintf(" Underlying: %v\n", e.UnderlyingErr)
}
return result
}
// FormatCompact returns a single-line representation
func (e *StructuredError) FormatCompact() string {
compact := fmt.Sprintf("[%s/%s] %s", e.Category, e.Severity, e.Message)
if e.Reason != "" {
compact += fmt.Sprintf(" | Reason: %s", e.Reason)
}
if e.Action != "" {
compact += fmt.Sprintf(" | Action: %s", e.Action)
}
compact += fmt.Sprintf(" | Origin: %s:%d", e.File, e.Line)
if e.UnderlyingErr != nil {
compact += fmt.Sprintf(" | Underlying: %v", e.UnderlyingErr)
}
return compact
}
// Helper functions for common error patterns
// NetworkError creates a network-related error
func NetworkError(message string) *StructuredError {
return newStructuredErrorWithDepth(CategoryNetwork, SeverityError, message, 2)
}
// ParsingError creates a parsing-related error
func ParsingError(message string) *StructuredError {
return newStructuredErrorWithDepth(CategoryParsing, SeverityError, message, 2)
}
// ValidationError creates a validation-related error
func ValidationError(message string) *StructuredError {
return newStructuredErrorWithDepth(CategoryValidation, SeverityWarning, message, 2)
}
// ExecutionError creates an execution-related error
func ExecutionError(message string) *StructuredError {
return newStructuredErrorWithDepth(CategoryExecution, SeverityCritical, message, 2)
}
// ConfigurationError creates a configuration-related error
func ConfigurationError(message string) *StructuredError {
return newStructuredErrorWithDepth(CategoryConfiguration, SeverityCritical, message, 2)
}
// InternalError creates an internal logic error
func InternalError(message string) *StructuredError {
return newStructuredErrorWithDepth(CategoryInternal, SeverityError, message, 2)
}
// MathError creates a mathematical error
func MathError(message string) *StructuredError {
return newStructuredErrorWithDepth(CategoryMath, SeverityError, message, 2)
}
// SecurityError creates a security-related error
func SecurityError(message string) *StructuredError {
return newStructuredErrorWithDepth(CategorySecurity, SeverityCritical, message, 2)
}