diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..46e0c25 --- /dev/null +++ b/404.html @@ -0,0 +1 @@ +
Understanding how to effectively handle errors is crucial for building robust applications. This guide explores various error handling strategies using ewrap and explains when to use each approach.
Error context is more than just an error message - it's the complete picture of what happened when an error occurred. With ewrap, you can capture rich context that helps with debugging and error resolution.
At its simplest, context includes:
err := ewrap.New("user not found",
+ ewrap.WithContext(ctx, ewrap.ErrorTypeNotFound, ewrap.SeverityError))
+
This tells us:
For more complex scenarios, you can add detailed context:
err := ewrap.New("database query failed",
+ ewrap.WithContext(ctx, ewrap.ErrorTypeDatabase, ewrap.SeverityCritical),
+ ewrap.WithLogger(logger)).
+ WithMetadata("query", query).
+ WithMetadata("table", "users").
+ WithMetadata("affected_rows", 0).
+ WithMetadata("latency_ms", queryTime.Milliseconds())
+
This provides a complete picture:
Error groups are powerful tools for handling multiple potential errors in a single operation. Here's how to use them effectively:
When validating multiple fields or conditions:
func validateUser(user User) error {
+ pool := ewrap.NewErrorGroupPool(4)
+ eg := pool.Get()
+ defer eg.Release()
+
+ // Validate email
+ if !isValidEmail(user.Email) {
+ eg.Add(ewrap.New("invalid email format",
+ ewrap.WithErrorType(ewrap.ErrorTypeValidation)))
+ }
+
+ // Validate age
+ if user.Age < 18 {
+ eg.Add(ewrap.New("user must be 18 or older",
+ ewrap.WithErrorType(ewrap.ErrorTypeValidation)))
+ }
+
+ // Validate username
+ if len(user.Username) < 3 {
+ eg.Add(ewrap.New("username too short",
+ ewrap.WithErrorType(ewrap.ErrorTypeValidation)))
+ }
+
+ return eg.Error()
+}
+
When handling concurrent operations:
func processItems(ctx context.Context, items []Item) error {
+ pool := ewrap.NewErrorGroupPool(len(items))
+ eg := pool.Get()
+ defer eg.Release()
+
+ var wg sync.WaitGroup
+ for _, item := range items {
+ wg.Add(1)
+ go func(item Item) {
+ defer wg.Done()
+ if err := processItem(ctx, item); err != nil {
+ eg.Add(ewrap.Wrap(err, fmt.Sprintf("failed to process item %d", item.ID)))
+ }
+ }(item)
+ }
+
+ wg.Wait()
+ return eg.Error()
+}
+
Circuit breakers help prevent system overload by failing fast when problems are detected. Here are some patterns for using them effectively:
For simple protection:
cb := ewrap.NewCircuitBreaker("database", 5, time.Minute)
+
+func queryDatabase() error {
+ if !cb.CanExecute() {
+ return ewrap.New("circuit breaker open",
+ ewrap.WithErrorType(ewrap.ErrorTypeDatabase),
+ ewrap.WithMetadata("breaker", "database"))
+ }
+
+ if err := performQuery(); err != nil {
+ cb.RecordFailure()
+ return err
+ }
+
+ cb.RecordSuccess()
+ return nil
+}
+
For systems with dependencies:
type Service struct {
+ dbBreaker *ewrap.CircuitBreaker
+ cacheBreaker *ewrap.CircuitBreaker
+ apiBreaker *ewrap.CircuitBreaker
+}
+
+func (s *Service) getData(ctx context.Context, id string) (Data, error) {
+ // Try cache first
+ if s.cacheBreaker.CanExecute() {
+ data, err := queryCache(id)
+ if err == nil {
+ s.cacheBreaker.RecordSuccess()
+ return data, nil
+ }
+ s.cacheBreaker.RecordFailure()
+ }
+
+ // Fall back to database
+ if s.dbBreaker.CanExecute() {
+ data, err := queryDatabase(id)
+ if err == nil {
+ s.dbBreaker.RecordSuccess()
+ return data, nil
+ }
+ s.dbBreaker.RecordFailure()
+ }
+
+ // Last resort: external API
+ if s.apiBreaker.CanExecute() {
+ data, err := queryAPI(id)
+ if err == nil {
+ s.apiBreaker.RecordSuccess()
+ return data, nil
+ }
+ s.apiBreaker.RecordFailure()
+ }
+
+ return Data{}, ewrap.New("all data sources failed",
+ ewrap.WithErrorType(ewrap.ErrorTypeInternal),
+ ewrap.WithContext(ctx, ewrap.ErrorTypeInternal, ewrap.SeverityCritical))
+}
+
When handling errors, consider implementing recovery strategies:
Graceful Degradation:
func getProductDetails(ctx context.Context, id string) (Product, error) {
+ var product Product
+
+ // Get core product data
+ data, err := getProductData(id)
+ if err != nil {
+ // Log the error but continue with partial data
+ logger.Error("failed to get full product data", "error", err)
+ product.Status = "partial"
+ } else {
+ product.Status = "complete"
+ }
+
+ // Get non-critical enrichment data
+ reviews, err := getProductReviews(id)
+ if err != nil {
+ // Add metadata about missing data
+ err = ewrap.Wrap(err, "failed to get reviews",
+ ewrap.WithMetadata("missing_component", "reviews"),
+ ewrap.WithErrorType(ewrap.ErrorTypePartial))
+ logger.Warn("serving product without reviews", "error", err)
+ }
+
+ return product, nil
+}
+
Retry Patterns:
func withRetry(operation func() error, maxAttempts int, delay time.Duration) error {
+ var lastErr error
+
+ for attempt := 1; attempt <= maxAttempts; attempt++ {
+ err := operation()
+ if err == nil {
+ return nil
+ }
+
+ lastErr = ewrap.Wrap(err, "operation failed",
+ ewrap.WithMetadata("attempt", attempt),
+ ewrap.WithMetadata("max_attempts", maxAttempts))
+
+ if attempt < maxAttempts {
+ time.Sleep(delay * time.Duration(attempt))
+ }
+ }
+
+ return lastErr
+}
+
The key to effective error handling is choosing the right strategy for each situation. Consider:
By understanding these factors and using ewrap's features appropriately, you can build robust and maintainable error handling systems.