Documentation
¶
Overview ¶
Package errors 提供线程安全的多错误聚合。
支持 errors.Is 链式检查,使用 sync.Pool 优化字符串格式化。
快速接入 ¶
var err error
err = errors.Append(err, op1())
err = errors.Append(err, op2())
if err != nil { ... }
支持 errors.Is ¶
if errors.Is(err, os.ErrNotExist) { ... }
遍历所有错误 ¶
if merr, ok := err.(*errors.MultiError); ok {
for _, e := range merr.Errors() { ... }
}
使用场景 ¶
- 批量操作:同时执行多个操作,收集所有错误
- 资源清理:关闭多个资源,记录所有失败
- 验证:收集所有验证错误
性能优化 ¶
- 使用 sync.Pool 复用 bytes.Buffer
- Copy-on-write 优化常见的"反复 Append 到左侧"模式
- 自动展开嵌套的 MultiError
错误构造仓规(#378) ¶
- 普通错误构造与包装统一 stdlib 形态:fmt.Errorf("...: %w", err) / errors.New(New/As/Is/Unwrap 与 stdlib 同型透传,哨兵判定单 import 即可);
- 结构化上下文场景用 Wrap(err, msg, kv...)——kv 经 Kv / KvFromChain 升格为结构化日志字段,普通包装没有 kv,两者互补而非替代;
- panic 收编统一走 Recovered(见 panic.go):error 身份、goroutine 栈、 panic 值类型信息三样全保留,禁止 `fmt.Errorf("...: %v", recover())`。
(Errorf 的去留史:#378 以「零引用」为由删除,但该审计只扫了 org 内部 ——跨 org 消费方 go-git-platform 72 处 / go-gongfeng 4 处在 MVS 抬升后 编译断裂,断裂沿依赖链中段组件传递(#401/#405 反证)。已按兼容层恢复 为行为逐字一致的 Deprecated 函数并附迁移指引(见 Errorf):新代码仍按 上述仓规走 stdlib,跨 org 存量不强制迁移、编译与行为不变。)
Index ¶
- func Append(left error, right error) error
- func As(err error, target any) bool
- func Cause(err error) error
- func Chain(fns ...func() error) error
- func Errorf(format string, args ...any) errordeprecated
- func In(err error, targets ...error) bool
- func Is(err, target error) bool
- func IsBizError(err error) bool
- func Kv(err error) []any
- func KvFromChain(err error) []any
- func New(msg string) error
- func Recovered(r any) error
- func Unwrap(err error) error
- func Wrap(err error, msg string, kv ...any) error
- type BizError
- func AsBizError(err error) (*BizError, bool)
- func NewBadRequestError(msg string) *BizError
- func NewBizError(code int, msg string) *BizError
- func NewBizErrorWithStatus(bizCode, httpCode int, msg string) *BizError
- func NewConflictError(msg string) *BizError
- func NewForbiddenError(msg string) *BizError
- func NewNotFoundError(msg string) *BizError
- func NewUnauthorizedError(msg string) *BizError
- func WrapBizError(code int, msg string, cause error) *BizError
- func WrapBizErrorWithStatus(bizCode, httpCode int, msg string, cause error) *BizError
- type KVCarrier
- type MultiError
- type PanicError
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Append ¶
Append 将两个错误合并为一个。 任一为 nil 时返回另一个;都非 nil 时返回聚合后的 MultiError。 优化了常见的"反复 Append 到左侧"模式(copy-on-write)。
func Cause ¶
Cause 是 errors.Cause 的便捷版:返回 wrap 链最底层的 error。 等价 errors.Unwrap 递归到 nil。
多分支链(Unwrap() []error,Go 1.20+ 多错误解包,如 Errorf 的多 %w 形态) 取**首分支**继续下钻(#420):单值语义的 Cause 在多分支形态没有「唯一 最底层」,首分支与 KvFromChain 的深度先序口径一致。stdlib 语义优先—— 同时实现两种 Unwrap 的类型先走单分支(errors.Unwrap 的既有序)。
func Chain ¶ added in v0.3.0
Chain 顺序执行 fns,任一返回非 nil 立即停止并返回该错误; 全部成功返回 nil。适合"必须按序执行、前一步失败后续无意义"的场景 (如多阶段清理:阶段间有依赖时不能像 MultiError 那样继续执行后续步骤)。
fns := []func() error{flushBuffers, closeFile, removeLock}
if err := errors.Chain(fns...); err != nil { ... }
func Errorf
deprecated
added in
v0.19.0
Errorf 构造带格式化的错误,支持 %w 包装(可出现多个),与 fmt.Errorf 同型。
与 Wrap 的分工:Errorf 用于「消息里带动态值」,Wrap 用于「固定消息 + kv 上下文」。 两者互补而非替代:需要「动态消息 + 包装 + kv 上下文」时,先 Errorf 再 Wrap。
相对 fmt.Errorf 的差异(都写在这里,行为可预期):
- nil-safe:%w 的实参为 nil 时**不进包装链**,消息按 %v 渲染为 "<nil>"。 fmt.Errorf 在此产出 "%!w(<nil>)" 噪声,且整条包装链被丢弃——这正是 Wrap 当初要解决的坑(见 Wrap 的注释),新原语不重蹈。
- %w 的实参不是 error 时同样按 %v 渲染、不进链(fmt.Errorf 产出 "%!w(...)")。
- 格式化结果为空串**且没有 %w 被包装**时返回 nil(与 Wrap(nil, ...) → nil 同一取向)。**有 %w 时不返回 nil**——被包装错误的报文为空(如 errors.New(""))会让整条消息渲染成空串,此时返回 nil 等于把「失败」 静默吞成「成功」:调用方的 `if err != nil` 分支整个跳过。
- 多个 %w 时包装链是 Unwrap() []error(Go 1.20+ 多错误形态), errors.Is / errors.As 对链上每个错误都生效。
典型用法:
if err := db.Query(ctx, q); err != nil {
return errors.Errorf("load profile %s: %w", userID, err)
}
消息渲染与 fmt.Errorf 一致(%w 位置渲染被包装错误的 Error() 文本), 因此从 fmt.Errorf 迁过来时日志与告警的文案不会变。
Deprecated: 兼容恢复(#401/#405)。#378 曾以「零引用」为由删除本函数, 但该判定只扫了 oekit org 内部——跨 org 消费方(yi-nology/go-git-platform 72 处、yi-nology/go-gongfeng 4 处)在 MVS 把 pkg 抬到 v0.45.0 时全部 编译断裂,且断裂会沿依赖链中段组件(SDK/库)传递给全部下游。本恢复是 **行为逐字一致的兼容层**(语义三样 stdlib 没有:nil-safe %w、空格式串返 nil、非 error 的 %w 实参按 %v 渲染不产 %!w 噪声),不是回退收敛方向:
- 新代码**不建议**使用:普通错误构造与包装统一 stdlib 形态 fmt.Errorf("...: %w", err)(仓规见包文档「错误构造仓规」);
- 迁移口径:纯格式化/包装 → fmt.Errorf(逐点等价,前提是 %w 实参恒为 非 nil error——nil/非 error 实参的渲染差异见上);需要 kv 结构化上下文 → errors.Wrap;两者都需要 → 先 Errorf 再 Wrap;
- 跨 org 存量消费方(guardx/go-git-platform/go-gongfeng 等)不做强制 迁移,本兼容层保证其编译与行为不变。
func In ¶ added in v0.3.0
In 报告 err 是否匹配 targets 中的任意一个(errors.Is 语义, 支持 wrapped 错误链与 Is 自定义匹配)。
if errors.In(err, ErrNotFound, ErrForbidden) { ... }
func IsBizError ¶
IsBizError 检查错误是否为 BizError 类型。
typed-nil(`var e *BizError; var err error = e`)不算命中:接口非 nil 但 指向 nil 指针,此时按文档取 b.BizCode 就是空指针解引用。
func Kv ¶ added in v0.38.2
Kv 返回最外层 Wrap 附带的上下文 kv 对(#292):无 kv 或 err 不携带 kv (非本仓 Wrap 构造、已到链底)返回 nil。
要**全量**收集多层 Wrap 的上下文用 KvFromChain。logx 侧消费形态(#412: 错误字段名示范用 logx.ErrKey 常量——消费方业务仓有 logx 依赖,禁止手写 "err" 字面量;pkg 自身零依赖底座引不了 logx,内部以 internal/logkv.ErrKey 作上游镜像,值与 logx.ErrKey 逐字一致):
err := errors.Wrap(err, "query user", "user_id", uid)
logger.Error(ctx, "query failed", logx.ErrKey, err) // err 字段保持文本
// kv 升格为独立字段(检索/聚合/按 user_id 过滤全通):
logger.Error(ctx, append([]any{logx.ErrKey, err}, errors.Kv(err)...)...)
func KvFromChain ¶ added in v0.38.2
KvFromChain 沿错误链收集**每层** Wrap 附带的 kv 对(#292):多层 Wrap 时 上下文全量展开,外层在前、内层在后(同名 key 由日志端的后写覆盖语义决定 最终值——与 slog Attrs 的重复 key 行为一致)。链上多层 kv 不做去重: 去重会掩盖「两层都带了 user_id 但值不同」的异常现场。多分支链 (Unwrap() []error,如 Errorf 的多 %w 形态)按深度先序遍历。 err 为 nil 或整条链都不携带 kv 时返回 nil。
典型消费(logx 侧 ErrAttrs 形态):
func ErrAttrs(err error) []slog.Attr {
var attrs []slog.Attr
for i := 0; i < len(kv); i += 2 { attrs = append(attrs, ...) }
}
func New ¶ added in v0.12.0
New 构造哨兵错误。与 stdlib errors.New 同型(errors.Is/As 可判定)。 补齐后下游的哨兵错误不再需要双 import(stdlib + oekit)—— 「同一进程只允许一套 errors 包」的类型同一性红线才可完整执行(#8)。
func Recovered ¶ added in v0.45.0
Recovered 把 recover() 的返回值收编为 error:r 为 nil(无 panic)返回 nil;否则返回携带栈与 error 身份的 *PanicError。
推荐惯用法(配合 defer + 具名返回值;无 panic 时 recover() 返回 nil、 本函数直接返回 nil,零包装开销):
func worker() (err error) {
defer func() {
if perr := errors.Recovered(recover()); perr != nil {
err = fmt.Errorf("worker: task panic: %w", perr)
}
}()
...
}
需要组件前缀时用 %w 再包一层(error 身份照旧透传);不需要时把 *PanicError 直接交给日志/上抛即可。
⚠️ 不要再写 `err = fmt.Errorf("...: %v", recover())`——%v 把 panic 值 拍平成纯文本,error 身份(errors.Is / errors.As)当场丢失,正是 #381 的病灶;包装纪律(对齐 pkg/internal/singleflight 的成文约定)「r 是 error 时必须 %w 保留身份」已内建在本函数里,调用方无需再分叉。
func Wrap ¶
Wrap 用 msg 包装 err 并返回新 err。err 为 nil 时返回 nil(不创建无意义包装)。 kv 列表作为附加上下文附加到包装消息(用于日志关联,例如 operation 名)。
跟 fmt.Errorf("...: %w", err) 的区别: Wrap 显式 nil-safe(避免 "%!w(<nil>)" 之类输出),并支持附加 kv 对。
业务场景:
if err := db.Query(...); err != nil {
return errors.Wrap(err, "query user", "user_id", uid)
}
输出: "query user: <原始 err> user_id=42"(kv 用 = 分隔拼接) 解包: errors.Is / errors.As 仍能识别原始 err(走 Unwrap)。
kv 还有**结构化出口**(#292):errors.Kv(err) 取最外层 kv、 errors.KvFromChain(err) 沿链全量收集——交给结构化日志时把 kv 升格为 独立字段(检索/聚合/过滤全通),错误文本照旧留在 err 字段。
Types ¶
type BizError ¶
type BizError struct {
BizCode int `json:"biz_code"` // 业务码(语义标识)
HTTPCode int `json:"http_code"` // HTTP 状态码(0 = 由 StatusMapper 推导)
Message string `json:"message"` // 业务描述
Cause error `json:"-"` // 原始错误(可选)
}
BizError 是业务层错误类型,携带业务码(BizCode)和 HTTP 状态码(HTTPCode)。 在 service 层返回此错误后,handler 层可通过 errors.As 提取 code 和 message。
字段语义(v0.17.0 起分离):
- BizCode:业务语义标识,写入响应 body 的 code 字段(如 1001、40404)
- HTTPCode:HTTP 状态码;为 0 时由 middleware.StatusMapper 从 BizCode 推导
用法:
return nil, errors.NewNotFoundError("项目不存在")
return nil, errors.NewBadRequestError("keyword 不能为空")
return nil, errors.NewBizError(1001, "余额不足") // HTTPCode=0, 走 mapper
return nil, errors.NewBizErrorWithStatus(1001, 402, "余额不足") // 显式两码
func AsBizError ¶
AsBizError 从错误链中提取 BizError。
第二个返回值在「链上是 typed-nil *BizError」时为 false——此时 bizErr 为 nil 指针,按文档用法 `if ok { use b.BizCode }` 直接用就会崩。
func NewBadRequestError ¶
NewBadRequestError 创建 400 业务错误(请求参数错误)。
func NewBizError ¶
NewBizError 创建一个自定义业务码错误,HTTPCode 留空(0), handler 层会经 StatusMapper 从 BizCode 推导 HTTP 状态码。
适用于业务码与 HTTP 对齐的场景(如 BizCode=404 → HTTP 404), 或接受默认映射(BizCode=1001 → HTTP 400)。 需要显式分离两码时使用 NewBizErrorWithStatus。
func NewBizErrorWithStatus ¶
NewBizErrorWithStatus 创建一个显式指定 HTTP 状态码的业务错误。 适用于业务码与 HTTP 状态码需要分离的场景(如 BizCode=1001 但需返回 HTTP 402)。
func NewConflictError ¶
NewConflictError 创建 409 业务错误(资源冲突)。
func NewForbiddenError ¶
NewForbiddenError 创建 403 业务错误(无权限)。
func NewNotFoundError ¶
NewNotFoundError 创建 404 业务错误(资源不存在)。
func NewUnauthorizedError ¶
NewUnauthorizedError 创建 401 业务错误(未认证)。
func WrapBizError ¶
WrapBizError 包装原始错误为业务错误,HTTPCode 留空(0), handler 层经 StatusMapper 从 BizCode 推导。
func WrapBizErrorWithStatus ¶
WrapBizErrorWithStatus 包装原始错误为业务错误,显式指定 HTTP 状态码。 字符串形态与 WrapBizError 同构(#202):Error()/String() 均带 cause—— 此前 cause 从不出现在本构造的错误字符串里(Unwrap 链在、字符串诊断链 断头),双码错误的 console_log 看不到内层原因。
func (*BizError) Error ¶
Error 实现 error 接口。 包含 BizCode 以确保日志中不丢失业务码上下文。
格式(#202 起钉死,表驱动测试钉住):
biz_code:%d[ http_code:%d] message[: cause 片段]*
- http_code 段仅在 HTTPCode != 0 时出现(显式两码分离的错误两码都
可见,单码错误不带 http_code:0 噪声)——Error() 与 String() 统一
走本格式(此前 String() 恒带 http_code 且丢 cause,同一错误两种
字符串:日志走 Stringer、响应走 Error 时信息量不一致);
- cause 链上的 **BizError 只贡献 message**,不再嵌 biz_code:%d 前缀
——嵌套包装时外层码遮蔽内层码、双码混排外泄到用户可见消息的问题
由此消除(外层是包装语义,最外层码即响应码;内层码经 errors.As/
AsBizError 仍完整可取);
- cause 链上首个非 BizError 错误按其自身 Error() 渲染并终止展开
(它自带完整的下层链文本)。
nil 接收者不 panic:typed-nil(`var e *BizError; var err error = e`)是 Go 里极常见的形态——函数返回 *BizError 而调用方把它赋给 error 接口时, 接口本身非 nil。此时任何日志/格式化路径调用 Error() 都会空指针崩溃, 把「错误处理出错了」变成「进程崩了」。
type KVCarrier ¶ added in v0.38.2
Kv() []any 是本仓 Wrap 上下文 kv 的**结构化读取口**(#292)。实现本接口 的错误可被 logx 等结构化日志出口升格为独立字段,而不是退化成 error 字符串 里的一段文本。
type MultiError ¶
type MultiError struct {
// contains filtered or unexported fields
}
MultiError 是聚合后的多错误类型。 实现 error、errors.Is(遍历检查)和 Errors()(获取子错误列表)。
func (*MultiError) Errors ¶
func (merr *MultiError) Errors() []error
Errors 返回所有子错误的切片。
返回值是内部切片的**防御性拷贝**,调用方对其 append/修改元素均不影响 MultiError 内部状态(也不得绕开 Append 的 copy-on-write 保护前提)。 聚合错误量通常为个位数,拷贝成本可忽略。
func (*MultiError) Is ¶
func (merr *MultiError) Is(target error) bool
Is 实现 errors.Is 接口,遍历所有子错误进行匹配。
func (*MultiError) Unwrap ¶
func (merr *MultiError) Unwrap() []error
Unwrap 支持 errors.As / errors.Is 遍历子错误(Go 1.20 多错误解包)。 没有它,聚合进 MultiError 的 BizError 无法被 errors.As 识别, mw.Fail 会把本应 404 的错误降级为 500。
返回**防御性拷贝**(同 Errors()):直接返回内部切片会把 Append 的 copy-on-write 前提捅穿——调用方拿到切片后改写元素,内部状态随之被改。
type PanicError ¶ added in v0.45.0
type PanicError struct {
// Value 是 recover() 返回的原始 panic 值。
Value any
// Stack 是 panic 发生时的 goroutine 栈(Recovered 内采集)。
// 渲染为文本用 string(perr.Stack)。
Stack []byte
}
PanicError 是 panic 值收编为 error 后的载体。
三个承诺:
- error 身份不丢:panic 值本身是 error 时 Unwrap() 返回它——业务侧 panic(err)(常见形态)经收编后,调用方仍能 errors.Is / errors.As 到原始错误;
- 带发生时的 goroutine 栈:Stack 字段是捕获时刻的 debug.Stack() 输出 (defer recover 执行在 panic 现场 goroutine 上,栈即现场);
- Error() 文本包含 panic 值类型信息:非 error 的 panic 值(string、 int、自定义 struct……)也能从日志/告警里看出它是什么类型。
func (*PanicError) Error ¶ added in v0.45.0
func (e *PanicError) Error() string
Error 实现 error。文本格式 "panic: <类型>: <值>"——panic 值的类型信息 始终在场(%T);值本身是 error 时 %v 渲染其 Error() 文本。
func (*PanicError) Unwrap ¶ added in v0.45.0
func (e *PanicError) Unwrap() error
Unwrap 返回 panic 值中的 error(panic 值不是 error 时返回 nil)。 这是「error 身份不丢」的机制所在:errors.Is / errors.As 穿透 PanicError 直达业务侧 panic 出去的原始错误。