# Best Practices for fabrica-util's Enhanced Error Handling with Context in go-pantheon Microservices

> Master go-pantheon microservice error handling with fabrica-util. Learn to preserve stack traces across async calls using Wrapf and Go for robust debugging.

- Repository: [Pantheon/fabrica-util](https://github.com/go-pantheon/fabrica-util)
- Tags: best-practices
- Published: 2026-03-02

---

**Use fabrica-util's `errors.Wrapf` and `xsync.Go` with automatic panic recovery to preserve full stack traces and context across asynchronous boundaries in go-pantheon microservices.**

The `go-pantheon/fabrica-util` repository provides a production-ready **errors** package that extends `github.com/pkg/errors` with Go 1.20+ compatibility, delivering utilities for stack trace preservation, structured logging, and concurrent error aggregation. For distributed microservices architectures, these primitives ensure that failures carry complete diagnostic context from the point of origin through goroutine boundaries to centralized observability platforms.

## Core Error Wrapping and Context Enrichment

The `errors` package in [`errors/errors.go`](https://github.com/go-pantheon/fabrica-util/blob/main/errors/errors.go) provides drop-in replacements for standard error creation that capture execution context. Unlike `fmt.Errorf`, these utilities preserve the original error type and stack trace.

### Creating and Wrapping Errors

Use `errors.Wrap` and `errors.Wrapf` when bubbling up errors from lower-level calls. These functions delegate to `pkg/errors` to maintain the original stack trace while adding domain-specific context.

```go
// internal/dao/user.go
func (d *DAO) GetUser(id string) (*User, error) {
    u, err := d.db.QueryUser(id) // lower-level call returns a raw error
    if err != nil {
        // Preserve original error & stack, add high-level context
        return nil, errors.Wrapf(err, "failed to fetch user %s from DB", id)
    }
    return u, nil
}

```

*Implementation note:* `Wrapf` is defined at lines 31-34 in [`errors/errors.go`](https://github.com/go-pantheon/fabrica-util/blob/main/errors/errors.go) as a thin wrapper around `pkg/errors.Wrapf`, ensuring stack trace continuity.

### Attaching Messages Without Stack Alteration

When you need to add user-friendly notes without wrapping the underlying error, use `errors.WithMessage` or `errors.WithMessagef`. This preserves the original error type for `errors.Is` and `errors.As` checks while enriching the display message.

## Preserving Stack Traces in Asynchronous Operations

Microservices rely heavily on background goroutines for tasks like cache warming, event processing, and timeouts. The `xsync` package in [`xsync/routines.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go) provides `xsync.Go`, which automatically recovers panics and logs structured error data including full stack traces.

### Structured Goroutine Execution

`xsync.Go` accepts a function name, the executable function, and an optional error filter. It internally extracts stack traces using `errors.StackTrace(err)` and logs via `slog` at lines 70-81 in [`xsync/routines.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go).

```go
// service/worker.go
func (w *Worker) Start(ctx context.Context) {
    xsync.Go("process pending jobs", func() error {
        return w.processJobs()
    }, xsync.IsCancelled) // optional filter to silence cancel errors
}

```

The internal logging implementation produces structured output:

```go
slog.Error("goroutine error occurred.",
    "message", msg,
    "error", err.Error(),
    "stack", errors.StackTrace(err))

```

## Recovering from Panics as Typed Errors

Uncontrolled panics in goroutines can crash entire microservice instances. `xsync.CatchErr` transforms recovered panics into standard error values with complete stack traces, as implemented at lines 32-44 in [`xsync/routines.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go).

### Converting Panics to Wrapped Errors

When `xsync.Go` detects a panic, it invokes `CatchErr` to capture the panic value and wrap it:

```go
func (s *Scheduler) runTask(task func()) {
    xsync.Go("run scheduled task", func() error {
        task() // may panic
        return nil
    })
}

```

The recovery mechanism produces an error via `errors.Wrap(t, "goroutine panic recovered")`, allowing upstream handlers to inspect the failure using standard Go error interfaces rather than managing raw panic values.

## Aggregating Parallel Execution Errors

Concurrent microservice operations often generate multiple errors that require collation. The `errors` package provides `SafeJoinError` for thread-safe aggregation and `JoinUnsimilar` for deduplication.

### Thread-Safe Error Collection

`SafeJoinError` (defined at line 27 in [`errors/errors.go`](https://github.com/go-pantheon/fabrica-util/blob/main/errors/errors.go)) provides a concurrency-safe mechanism for collecting errors from multiple goroutines without data races.

```go
func (p *Processor) ProcessAll(ctx context.Context, items []Item) error {
    var wg sync.WaitGroup
    errAgg := errors.NewSafeJoinError()

    for _, it := range items {
        wg.Add(1)
        it := it // capture loop variable
        xsync.Go("process item", func() error {
            defer wg.Done()
            if e := p.handle(it); e != nil {
                errAgg.Join(e) // thread-safe aggregation
            }
            return nil
        })
    }
    wg.Wait()
    if errAgg.HasError() {
        return errAgg // implements error, returns combined message
    }
    return nil
}

```

### Deduplicating Similar Errors

For batch operations that may produce identical error messages, `errors.JoinUnsimilar` combines multiple errors while deduplicating similar messages, keeping telemetry payloads compact. This utility resides at lines 61-66 in [`errors/errors.go`](https://github.com/go-pantheon/fabrica-util/blob/main/errors/errors.go).

```go
func validateBatch(batch []Record) error {
    var errs []error
    for _, r := range batch {
        if r.ID == "" {
            errs = append(errs, errors.New("record ID missing"))
        }
        // other checks...
    }
    return errors.JoinUnsimilar(errs...)
}

```

## Summary

- **Always use `errors.Wrapf`** to preserve stack traces when crossing function boundaries in `go-pantheon` microservices.
- **Leverage `xsync.Go`** for automatic panic recovery and structured error logging with full stack traces.
- **Extract diagnostic data** with `errors.StackTrace` for detailed observability in centralized logging systems.
- **Aggregate concurrent errors** using `SafeJoinError` for thread safety or `JoinUnsimilar` to deduplicate similar failure messages from parallel workers.

## Frequently Asked Questions

### How does fabrica-util's `errors.Wrap` differ from `fmt.Errorf`?

`errors.Wrap` and `errors.Wrapf` preserve the original error's stack trace by delegating to `github.com/pkg/errors`, whereas `fmt.Errorf` with the `%w` verb only wraps the error value without capturing the call stack. This distinction is critical in microservices where you need to trace failures across process boundaries back to the originating line in [`errors/errors.go`](https://github.com/go-pantheon/fabrica-util/blob/main/errors/errors.go) or consumer code.

### When should I use `SafeJoinError` versus `errors.Join`?

Use `SafeJoinError` when aggregating errors from concurrent goroutines, as it provides thread-safe methods (`Join`, `HasError`) protected by internal synchronization. Use `errors.Join` for single-threaded error collection where concurrency safety is not required, such as sequential validation checks in a single request handler.

### Can I filter which errors get logged in `xsync.Go`?

Yes. `xsync.Go` accepts an optional filter function parameter (such as `xsync.IsCancelled`) that receives the error before logging. If the filter returns `true`, the error is suppressed from logs. This prevents noise from expected cancellation errors during graceful shutdowns while still capturing genuine failures with full stack traces.

### Where are the error handling utilities defined in the source code?

Core error utilities reside in [`errors/errors.go`](https://github.com/go-pantheon/fabrica-util/blob/main/errors/errors.go), including `Wrapf`, `JoinUnsimilar`, and `SafeJoinError`. Goroutine management and panic recovery are implemented in [`xsync/routines.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go), which defines `Go`, `Timeout`, and `CatchErr`. Custom error definitions for lifecycle control appear in [`xsync/stopper.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/stopper.go), while [`xsync/future.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/future.go) demonstrates asynchronous error propagation patterns.