---
id: daily-golang-samber-oops
name: "golang-samber-oops"
url: https://skills.yangsir.net/skill/daily-golang-samber-oops
author: samber
domain: ai-system-observability-sre
tags: ["error-handling", "observability", "debugging", "logging"]
install_count: 2500
rating: 4.30 (20 reviews)
github: https://github.com/samber/cc-skills-golang
---

# golang-samber-oops

> 使用 samber/oops 将 Go 错误转换为携带上下文的结构化数据，让错误信息自解释，简化线上问题诊断

**Stats**: 2,500 installs · 4.3/5 (20 reviews)

## Before / After 对比

### 错误处理方式对比

**Before**:

使用 fmt.Errorf 包装错误，上下文信息混在错误消息中，日志中只有错误字符串无法结构化查询，排查问题需要联系开发人员回忆当时场景

**After**:

使用 oops.With 领域和属性标注错误，错误包含结构化的 trace 信息，日志系统可以直接按 domain、user_id 等字段查询，问题自动定位

| Metric | Before | After | Change |
|---|---|---|---|
| 问题诊断时间 | 60分钟 | 10分钟 | -83% |

## Readme

# golang-samber-oops

**Persona:** You are a Go engineer who treats errors as structured data. Every error carries enough context — domain, attributes, trace — for an on-call engineer to diagnose the problem without asking the developer.

# samber/oops Structured Error Handling

**samber/oops** is a drop-in replacement for Go's standard error handling that adds structured context, stack traces, error codes, public messages, and panic recovery. Variable data goes in `.With()` attributes (not the message string), so APM tools (Datadog, Loki, Sentry) can group errors properly. Unlike the stdlib approach (adding `slog` attributes at the log site), oops attributes travel with the error through the call stack.

## Why use samber/oops

Standard Go errors lack context — you see `connection failed` but not which user triggered it, what query was running, or the full call stack. `samber/oops` provides:

- **Structured context** — key-value attributes on any error

- **Stack traces** — automatic call stack capture

- **Error codes** — machine-readable identifiers

- **Public messages** — user-safe messages separate from technical details

- **Low-cardinality messages** — variable data in `.With()` attributes, not the message string, so APM tools group errors properly

This skill is not exhaustive. Please refer to library documentation and code examples for more information. Context7 can help as a discoverability platform.

## Core pattern: Error builder chain

All `oops` errors use a fluent builder pattern:

```
err := oops.
    In("user-service").           // domain/feature
    Tags("database", "postgres").  // categorization
    Code("network_failure").       // machine-readable identifier
    User("user-123", "email", "foo@bar.com").  // user context
    With("query", query).          // custom attributes
    Errorf("failed to fetch user: %s", "timeout")

```

Terminal methods:

- `.Errorf(format, args...)` — create a new error

- `.Wrap(err)` — wrap an existing error

- `.Wrapf(err, format, args...)` — wrap with a message

- `.Join(err1, err2, ...)` — combine multiple errors

- `.Recover(fn)` / `.Recoverf(fn, format, args...)` — convert panic to error

### Error builder methods

Methods
Use case

`.With("key", value)`
Add custom key-value attribute (lazy `func() any` values supported)

`.WithContext(ctx, "key1", "key2")`
Extract values from Go context into attributes (lazy values supported)

`.In("domain")`
Set the feature/service/domain

`.Tags("auth", "sql")`
Add categorization tags (query with `err.HasTag("tag")`)

`.Code("iam_authz_missing_permission")`
Set machine-readable error identifier/slug

`.Public("Could not fetch user.")`
Set user-safe message (separate from technical details)

`.Hint("Runbook: https://doc.acme.org/doc/abcd.md")`
Add debugging hint for developers

`.Owner("team/slack")`
Identify responsible team/owner

`.User(id, "k", "v")`
Add user identifier and attributes

`.Tenant(id, "k", "v")`
Add tenant/organization context and attributes

`.Trace(id)`
Add trace / correlation ID (default: ULID)

`.Span(id)`
Add span ID representing a unit of work/operation (default: ULID)

`.Time(t)`
Override error timestamp (default: `time.Now()`)

`.Since(t)`
Set duration based on time since `t` (exposed via `err.Duration()`)

`.Duration(d)`
Set explicit error duration

`.Request(req, includeBody)`
Attach `*http.Request` (optionally including body)

`.Response(res, includeBody)`
Attach `*http.Response` (optionally including body)

`oops.FromContext(ctx)`
Start from an `OopsErrorBuilder` stored in a Go context

## Common scenarios

### Database/repository layer

```
func (r *UserRepository) FetchUser(id string) (*User, error) {
    query := "SELECT * FROM users WHERE id = $1"
    row, err := r.db.Query(query, id)
    if err != nil {
        return nil, oops.
            In("user-repository").
            Tags("database", "postgres").
            With("query", query).
            With("user_id", id).
            Wrapf(err, "failed to fetch user from database")
    }
    // ...
}

```

### HTTP handler layer

```
func (h *Handler) CreateUser(w http.ResponseWriter, r *http.Request) {
    userID := getUserID(r)

    err := h.service.CreateUser(r.Context(), userID)
    if err != nil {
        return oops.
            In("http-handler").
            Tags("endpoint", "/users").
            Request(r, false).
            User(userID).
            Wrapf(err, "create user failed")
    }

    w.WriteHeader(http.StatusCreated)
}

```

### Service layer with reusable builder

```
func (s *UserService) CreateOrder(ctx context.Context, req CreateOrderRequest) error {
    builder := oops.
        In("order-service").
        Tags("orders", "checkout").
        Tenant(req.TenantID, "plan", req.Plan).
        User(req.UserID, "email", req.UserEmail)

    product, err := s.catalog.GetProduct(ctx, req.ProductID)
    if err != nil {
        return builder.
            With("product_id", req.ProductID).
            Wrapf(err, "product lookup failed")
    }

    if product.Stock < req.Quantity {
        return builder.
            Code("insufficient_stock").
            Public("Not enough items in stock.").
            With("requested", req.Quantity).
            With("available", product.Stock).
            Errorf("insufficient stock for product %s", req.ProductID)
    }

    return nil
}

```

## Error wrapping best practices

### DO: Wrap directly, no nil check needed

```
// ✓ Good — Wrap returns nil if err is nil
return oops.Wrapf(err, "operation failed")

// ✗ Bad — unnecessary nil check
if err != nil {
    return oops.Wrapf(err, "operation failed")
}
return nil

```

### DO: Add context at each layer

Each architectural layer SHOULD add context via Wrap/Wrapf — at least once per package boundary (not necessarily at every function call).

```
// ✓ Good — each layer adds relevant context
func Controller() error {
    return oops.In("controller").Trace(traceID).Wrapf(Service(), "user request failed")
}

func Service() error {
    return oops.In("service").With("op", "create_user").Wrapf(Repository(), "db operation failed")
}

func Repository() error {
    return oops.In("repository").Tags("database", "postgres").Errorf("connection timeout")
}

```

### DO: Keep error messages low-cardinality

Error messages MUST be low-cardinality for APM aggregation. Interpolating variable data into the message breaks grouping in Datadog, Loki, Sentry.

```
// ✗ Bad — high-cardinality, breaks APM grouping
oops.Errorf("failed to process user %s in tenant %s", userID, tenantID)

// ✓ Good — static message + structured attributes
oops.With("user_id", userID).With("tenant_id", tenantID).Errorf("failed to process user")

```

## Panic recovery

`oops.Recover()` MUST be used in goroutine boundaries. Convert panics to structured errors:

```
func ProcessData(data string) (err error) {
    return oops.
        In("data-processor").
        Code("panic_recovered").
        Hint("Check input data format and dependencies").
        With("panic_value", r).
        Recover(func() {
            riskyOperation(data)
        })
}

```

## Accessing error information

`samber/oops` errors implement the standard `error` interface. Access additional info:

```
if oopsErr, ok := err.(oops.OopsError); ok {
    fmt.Println("Code:", oopsErr.Code())
    fmt.Println("Domain:", oopsErr.Domain())
    fmt.Println("Tags:", oopsErr.Tags())
    fmt.Println("Context:", oopsErr.Context())
    fmt.Println("Stacktrace:", oopsErr.Stacktrace())
}

// Get public-facing message with fallback
publicMsg := oops.GetPublic(err, "Something went wrong")

```

### Output formats

```
fmt.Printf("%+v\n", err)       // verbose with stack trace
bytes, _ := json.Marshal(err)  // JSON for logging
slog.Error(err.Error(), slog.Any("error", err))  // slog integration

```

## Context propagation

Carry error context through Go contexts:

```
func middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        builder := oops.
            In("http").
            Request(r, false).
            Trace(r.Header.Get("X-Trace-ID"))

        ctx := oops.WithBuilder(r.Context(), builder)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

func handler(ctx context.Context) error {
    return oops.FromContext(ctx).Tags("handler", "users").Errorf("something failed")
}

```

For assertions, configuration, and additional logger examples, see [Advanced patterns](https://github.com/samber/cc-skills-golang/blob/HEAD/skills/golang-samber-oops/./references/advanced.md).

## References

- [github.com/samber/oops](https://github.com/samber/oops)

- [pkg.go.dev/github.com/samber/oops](https://pkg.go.dev/github.com/samber/oops)

## Cross-References

- → See `samber/cc-skills-golang@golang-error-handling` skill for general error handling patterns

- → See `samber/cc-skills-golang@golang-observability` skill for logger integration and structured logging

Weekly Installs687Repository[samber/cc-skills-golang](https://github.com/samber/cc-skills-golang)GitHub Stars1.1KFirst SeenMar 22, 2026Security Audits[Gen Agent Trust HubPass](/samber/cc-skills-golang/golang-samber-oops/security/agent-trust-hub)[SocketPass](/samber/cc-skills-golang/golang-samber-oops/security/socket)[SnykPass](/samber/cc-skills-golang/golang-samber-oops/security/snyk)Installed onopencode656cursor649codex647gemini-cli645github-copilot644amp643

---
*Source: https://skills.yangsir.net/skill/daily-golang-samber-oops*
*Markdown mirror: https://skills.yangsir.net/api/skill/daily-golang-samber-oops/markdown*