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:
import (
"github.com/bytehide/bytehide-logs-go"
)
result, err := riskyOperation()
if err != nil {
logs.Error("Operation failed", err)
return nil, err
}import (
"github.com/bytehide/bytehide-logs-go"
)
result, err := riskyOperation()
if err != nil {
logs.Error("Operation failed", err)
return nil, err
}Critical Errors
err := initializeDatabase()
if err != nil {
logs.Critical("Database initialization failed")
os.Exit(1)
}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:
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
}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
order, err := processPayment(request)
if err != nil {
logs.WithTags("payment", "error").
WithException(err).
Error("Payment processing failed", err)
return nil, err
}order, err := processPayment(request)
if err != nil {
logs.WithTags("payment", "error").
WithException(err).
Error("Payment processing failed", err)
return nil, err
}Error with Context
order, err := processOrder(orderId)
if err != nil {
logs.WithContext("orderId", orderId).
WithContext("customerId", customerId).
WithException(err).
Error("Order processing failed", err)
return nil, err
}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
order, err := processRequest(request, correlationId)
if err != nil {
logs.WithCorrelationID(correlationId).
WithTags("api", "error").
WithException(err).
Error("Request processing failed", err)
return nil, err
}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:
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
}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:
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
}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:
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
}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:
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
}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%wverb 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
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")
})
}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")
})
}