Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 76 additions & 13 deletions errors/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,21 @@ The errors module provides enhanced error handling capabilities for Go applicati
- **Stack Traces**: Automatic stack trace capture for debugging
- **Error Wrapping**: Preserve error chains with additional context
- **gRPC Compatibility**: Integration with gRPC status codes
- **Panic Recovery**: Safe recovery from panics with context preservation
- **Panic Recovery**: Works with the `recovery` package for safe panic handling

## Error Message Semantics

`Wrap` / `Wrapf` add context as metadata rather than changing `Error()` text:

```go
err := errors.New("db failed")
wrapped := errors.Wrap(err, "init service failed")

wrapped.Error() // "db failed" — root message preserved
// Context is available via CollectTags(wrapped)["msg"] or wrapped.(*errors.ErrWrap).String()
```

Use `String()`, `DebugPrint`, `MarshalError`, or `FormatChain` when you need the full structured representation.

## Installation

Expand Down Expand Up @@ -119,11 +133,25 @@ err := errors.New("payment processing failed", errors.Tags{
"currency": "USD",
})

// Access metadata
// Access metadata from the root *Err
if tags := errors.GetTags(err); tags != nil {
txnID := tags["transaction_id"]
// Process transaction ID
}

// Merge tags from every layer in the chain
allTags := errors.CollectTags(err)

// User tags exclude automatic wrap context under TagKeyMessage ("msg")
userTags := errors.CollectUserTags(err)
```

### Readable Error Chains

```go
wrapped := errors.Wrap(errors.New("db failed"), "init service")
fmt.Println(wrapped.Error()) // db failed
fmt.Println(errors.FormatChain(wrapped)) // init service: db failed
```

### Stack Trace Analysis
Expand All @@ -143,30 +171,57 @@ data := errors.ErrJsonify(err)

### With gRPC

See [errcode/README.md](./errcode/README.md) for registry and lookup helpers.

```go
import "github.com/pubgo/funk/v2/errors/errcode"

// Convert to gRPC status
status := errcode.ConvertErr2Status(err)
errcode.MustRegisterErrCode(&errorpb.ErrCode{
Name: "demo.user.not_found",
Code: 404,
StatusCode: errorpb.Code_NotFound,
Message: "user not found",
})

// Create error code
code := &errorpb.ErrCode{
Code: 500,
StatusCode: errorpb.Code_Internal,
Name: "INTERNAL_ERROR",
Message: "Internal server error",
code, ok := errcode.LookupErrCode("demo.user.not_found")
if ok {
err := errcode.NewCodeErr(code)
_ = err
}

status := errcode.ConvertErr2Status(errcode.ParseError(err))
```

### With Logging

`log.Err(err)` automatically attaches:

- `error_id`
- `error_chain` via `FormatChain`
- `error_tags` via `CollectUserTags`
- `error_detail` as JSON

```go
import "github.com/pubgo/funk/v2/log"

// Log errors with context
log.Error().Err(err).Msg("operation failed")
```

### With result

// Structured error logging
```go
import "github.com/pubgo/funk/v2/result"

r := result.ErrOf(err)
if r.IsErr() {
_ = r.Message() // full chain
_ = r.Tags() // user tags
}
```

### Manual Structured Logging

```go
log.Error().RawJSON("error", errors.JsonPrint(err)).Send()
```

Expand Down Expand Up @@ -199,11 +254,19 @@ log.Error().RawJSON("error", errors.JsonPrint(err)).Send()
| `Is(err, target error)` | Check error chain for specific error |
| `Unwrap(err error)` | Get underlying error |
| `GetErrorId(err error)` | Get unique error identifier |
| `GetTags(err error)` | Get tags from the innermost `*Err` |
| `CollectTags(err error)` | Merge tags from all layers in the chain |
| `CollectUserTags(err error)` | Merge user tags and omit wrap `msg` context |
| `RootCause(err error)` | Return the deepest error in the chain |
| `Walk(err error, fn func(error) bool)` | Traverse the error chain |
| `FormatChain(err error)` | Join error chain into a single message |
| `FullMessage(err error)` | Alias for `FormatChain` |
| `MarshalError(err error)` | Serialize error to JSON with error return |

### Utility Functions

| Function | Description |
|----------|-------------|
| `DebugPrint(err error)` | Pretty print error with stack trace |
| `JsonPrint(err error)` | Serialize error to JSON |
| `JsonPrint(err error)` | Serialize error to JSON (returns nil on failure) |
| `ErrJsonify(err error)` | Convert error to structured data |
89 changes: 76 additions & 13 deletions errors/README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,21 @@
- **堆栈跟踪**: 自动堆栈跟踪捕获用于调试
- **错误包装**: 使用元数据保留错误链
- **gRPC兼容性**: 与gRPC状态代码集成
- **Panic恢复**: 安全的panic恢复机制
- **Panic恢复**: 与 `recovery` 包配合使用,安全处理 panic

## 错误消息语义

`Wrap` / `Wrapf` 会把上下文作为元数据附加,而不会改变 `Error()` 的文本:

```go
err := errors.New("数据库连接失败")
wrapped := errors.Wrap(err, "初始化服务失败")

wrapped.Error() // "数据库连接失败" — 保留根错误消息
// 上下文可通过 CollectTags(wrapped)["msg"] 或 wrapped.(*errors.ErrWrap).String() 获取
```

需要完整结构化表示时,请使用 `String()`、`DebugPrint`、`MarshalError` 或 `FormatChain`。

## 安装

Expand Down Expand Up @@ -119,11 +133,25 @@ err := errors.New("支付处理失败", errors.Tags{
"currency": "USD",
})

// 访问元数据
// 从根 *Err 获取元数据
if tags := errors.GetTags(err); tags != nil {
txnID := tags["transaction_id"]
// 处理交易ID
}

// 合并整条错误链上的 tags
allTags := errors.CollectTags(err)

// 用户 tags 会排除自动包装上下文 TagKeyMessage ("msg")
userTags := errors.CollectUserTags(err)
```

### 可读错误链

```go
wrapped := errors.Wrap(errors.New("数据库连接失败"), "初始化服务")
fmt.Println(wrapped.Error()) // 数据库连接失败
fmt.Println(errors.FormatChain(wrapped)) // 初始化服务: 数据库连接失败
```

### 堆栈跟踪分析
Expand All @@ -143,30 +171,57 @@ data := errors.ErrJsonify(err)

### 与gRPC

详见 [errcode/README.md](./errcode/README.md)。

```go
import "github.com/pubgo/funk/v2/errors/errcode"

// 转换为gRPC状态
status := errcode.ConvertErr2Status(err)
errcode.MustRegisterErrCode(&errorpb.ErrCode{
Name: "demo.user.not_found",
Code: 404,
StatusCode: errorpb.Code_NotFound,
Message: "用户不存在",
})

// 创建错误代码
code := &errorpb.ErrCode{
Code: 500,
StatusCode: errorpb.Code_Internal,
Name: "INTERNAL_ERROR",
Message: "内部服务器错误",
code, ok := errcode.LookupErrCode("demo.user.not_found")
if ok {
err := errcode.NewCodeErr(code)
_ = err
}

status := errcode.ConvertErr2Status(errcode.ParseError(err))
```

### 与日志记录

`log.Err(err)` 会自动附加:

- `error_id`
- `error_chain`(`FormatChain`)
- `error_tags`(`CollectUserTags`)
- `error_detail`(JSON)

```go
import "github.com/pubgo/funk/v2/log"

// 记录带上下文的错误
log.Error().Err(err).Msg("操作失败")
```

### 与 result

// 结构化错误日志记录
```go
import "github.com/pubgo/funk/v2/result"

r := result.ErrOf(err)
if r.IsErr() {
_ = r.Message() // 完整错误链
_ = r.Tags() // 用户 tags
}
```

### 手动结构化日志

```go
log.Error().RawJSON("error", errors.JsonPrint(err)).Send()
```

Expand Down Expand Up @@ -199,11 +254,19 @@ log.Error().RawJSON("error", errors.JsonPrint(err)).Send()
| `Is(err, target error)` | 检查错误链中的特定错误 |
| `Unwrap(err error)` | 获取底层错误 |
| `GetErrorId(err error)` | 获取唯一错误标识符 |
| `GetTags(err error)` | 获取最内层 `*Err` 上的 tags |
| `CollectTags(err error)` | 合并错误链各层的 tags |
| `CollectUserTags(err error)` | 合并用户 tags,并排除包装层 `msg` |
| `RootCause(err error)` | 返回最底层错误 |
| `Walk(err error, fn func(error) bool)` | 遍历错误链 |
| `FormatChain(err error)` | 将错误链拼接为单条消息 |
| `FullMessage(err error)` | `FormatChain` 的别名 |
| `MarshalError(err error)` | 序列化为 JSON 并返回 error |

### 实用函数

| 函数 | 描述 |
|------|------|
| `DebugPrint(err error)` | 带堆栈跟踪的漂亮打印错误 |
| `JsonPrint(err error)` | 将错误序列化为JSON |
| `JsonPrint(err error)` | 序列化为 JSON(失败时返回 nil) |
| `ErrJsonify(err error)` | 将错误转换为结构化数据 |
21 changes: 5 additions & 16 deletions errors/_doc.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
// Package errors provides structured error creation, wrapping, metadata tags,
// stack capture, and JSON-friendly inspection helpers for Go applications.
//
// Wrap and Wrapf preserve the root Error() text and attach context as metadata.
// Use FormatChain or FullMessage when a joined message is needed for logs.
package errors

// https://github.com/pkg/errors
// https://github.com/go-playground/errors
// https://pkg.go.dev/golang.org/x/xerrors
// https://github.com/getlantern/errors/blob/master/errors.go
// https://github.com/go-kratos/kratos/tree/main/errors
// https://github.com/Southclaws/fault
// https://github.com/rotisserie/eris
// https://github.com/cockroachdb/logtags
// https://github.com/cockroachdb/errors/tree/master/contexttags
// https://github.com/cockroachdb/errors
// https://github.com/mschneider82/problem
// https://github.com/rotisserie/eris
// https://github.com/cockroachdb/errors/tree/master/contexttags
// https://github.com/cockroachdb/logtags
// https://github.com/Southclaws/fault
Loading
Loading