/

Exception Handling

ByteHide Logger captures comprehensive error details including wrapped errors, error context, and custom information for effective error tracking in Go. Go uses explicit error returns rather than exceptions, requiring different patterns for error handling.

Basic Error Logging

Simple Error Logging

Go functions that can fail return an error as the last return value. Always check and log errors:

Go
import (
    "github.com/bytehide/bytehide-logs-go"
)

result, err := riskyOperation()
if err != nil {
    logs.Error("Operation failed", err)
    return nil, err
}

Critical Errors

Go
err := initializeDatabase()
if err != nil {
    logs.Critical("Database initialization failed")
    os.Exit(1)
}

Error Wrapping Patterns

Go 1.13+ introduced error wrapping with fmt.Errorf(). Always preserve error chains:

Go
import (
    "fmt"
    "github.com/bytehide/bytehide-logs-go"
)

// Wrapping errors preserves the error chain
func processOrder(orderId string) error {
    order, err := fetchOrder(orderId)
    if err != nil {
        logs.Error("Failed to fetch order", err)
        return fmt.Errorf("processOrder: fetch failed: %w", err)
    }
    
    err = validateOrder(order)
    if err != nil {
        logs.Error("Order validation failed", err)
        return fmt.Errorf("processOrder: validation failed: %w", err)
    }
    
    return nil
}

func fetchOrder(id string) (*Order, error) {
    result, err := database.Query("SELECT * FROM orders WHERE id = $1", id)
    if err != nil {
        return nil, fmt.Errorf("fetchOrder: database query failed: %w", err)
    }
    return result, nil
}

Fluent Error Logging

Error with Tags

Go
order, err := processPayment(request)
if err != nil {
    logs.WithTags("payment", "error").
        WithException(err).
        Error("Payment processing failed", err)
    return nil, err
}

Error with Context

Go
order, err := processOrder(orderId)
if err != nil {
    logs.WithContext("orderId", orderId).
        WithContext("customerId", customerId).
        WithException(err).
        Error("Order processing failed", err)
    return nil, err
}

Error with Correlation ID

Go
order, err := processRequest(request, correlationId)
if err != nil {
    logs.WithCorrelationID(correlationId).
        WithTags("api", "error").
        WithException(err).
        Error("Request processing failed", err)
    return nil, err
}

Nested Error Handling

Go encourages returning errors at the point of failure rather than nesting try-catch blocks:

Go
func processOrder(orderId string) error {
    order, err := validateOrder(orderId)
    if err != nil {
        logs.WithTags("validation").
            WithException(err).
            Warn("Order validation failed")
        return fmt.Errorf("validation failed: %w", err)
    }
    
    err = processPayment(order)
    if err != nil {
        logs.WithTags("payment", "critical").
            WithException(err).
            Error("Payment processing failed", err)
        return fmt.Errorf("payment failed: %w", err)
    }
    
    err = shipOrder(order)
    if err != nil {
        logs.WithTags("order", "unexpected").
            WithException(err).
            Critical("Unexpected error during order processing")
        return fmt.Errorf("shipping failed: %w", err)
    }
    
    return nil
}

Defer and Error Recovery

Use defer for cleanup and recovery:

Go
func processLargeOrder(orderId string) error {
    // Acquire lock
    mu.Lock()
    defer mu.Unlock()
    
    // Open file
    file, err := os.Open("order_" + orderId + ".json")
    if err != nil {
        logs.Error("Failed to open order file", err)
        return err
    }
    defer file.Close()
    
    // Process file
    data, err := ioutil.ReadAll(file)
    if err != nil {
        logs.Error("Failed to read order file", err)
        return err
    }
    
    return nil
}

Goroutine Error Handling

When using goroutines, establish error channels:

Go
func processOrdersInParallel(orderIds []string) error {
    errChan := make(chan error, len(orderIds))
    
    for _, orderId := range orderIds {
        go func(id string) {
            err := processOrder(id)
            if err != nil {
                logs.WithContext("orderId", id).
                    Error("Goroutine: order processing failed", err)
                errChan <- fmt.Errorf("order %s failed: %w", id, err)
            } else {
                errChan <- nil
            }
        }(orderId)
    }
    
    // Collect results
    for i := 0; i < len(orderIds); i++ {
        if err := <-errChan; err != nil {
            logs.Error("Error processing orders in parallel", err)
            return err
        }
    }
    
    return nil
}

Custom Error Types

Define custom error types for specific error conditions:

Go
type ValidationError struct {
    Field   string
    Message string
    Err     error
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
}

func (e *ValidationError) Unwrap() error {
    return e.Err
}

// Usage
func validateOrderRequest(req *OrderRequest) error {
    if req.CustomerID == "" {
        err := fmt.Errorf("customer id is required")
        logs.WithContext("field", "customerId").
            WithException(&ValidationError{
                Field:   "customerId",
                Message: "required field missing",
                Err:     err,
            }).
            Warn("Order validation failed", err)
        return &ValidationError{
            Field:   "customerId",
            Message: "required field missing",
            Err:     err,
        }
    }
    return nil
}

Best Practices

Go Error Handling Best Practices

  • Always check error values explicitly with if err != nil
  • Use error wrapping with fmt.Errorf() and %w verb to preserve error chains
  • Log errors at the point of failure with appropriate context
  • Don't swallow errors silently - always log or return
  • Use defer for resource cleanup and deferred error handling
  • Define custom error types for domain-specific errors
  • Use error channels for goroutine error communication

When to Use WithException()

Use WithException(err) in fluent chains to capture error details when combining with tags, context, and correlation IDs. For simple error logging, use logs.Error(message, err) directly.

Error Handling in HTTP Middleware

Go
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        
        defer func() {
            if err := recover(); err != nil {
                logs.WithContext("path", r.URL.Path).
                    WithContext("method", r.Method).
                    Critical("Panic in HTTP handler")
                http.Error(w, "Internal Server Error", http.StatusInternalServerError)
            }
        }()
        
        next.ServeHTTP(w, r)
        
        duration := time.Since(start)
        logs.WithContext("path", r.URL.Path).
            WithContext("method", r.Method).
            WithContext("duration_ms", duration.Milliseconds()).
            Debug("Request processed")
    })
}

Next Steps

Previous
Tags