errors

package
v0.47.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 12, 2026 License: MIT Imports: 9 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func Append

func Append(left error, right error) error

Append 将两个错误合并为一个。 任一为 nil 时返回另一个;都非 nil 时返回聚合后的 MultiError。 优化了常见的"反复 Append 到左侧"模式(copy-on-write)。

func As added in v0.12.0

func As(err error, target any) bool

As 断言 err 链上是否有 target 指向类型的错误。等价 stdlib errors.As。

func Cause

func Cause(err error) error

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

func Chain(fns ...func() error) error

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

func Errorf(format string, args ...any) error

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

func In(err error, targets ...error) bool

In 报告 err 是否匹配 targets 中的任意一个(errors.Is 语义, 支持 wrapped 错误链与 Is 自定义匹配)。

if errors.In(err, ErrNotFound, ErrForbidden) { ... }

func Is added in v0.12.0

func Is(err, target error) bool

Is 判定 err 链上是否有与 target 等价的错误。等价 stdlib errors.Is。

func IsBizError

func IsBizError(err error) bool

IsBizError 检查错误是否为 BizError 类型。

typed-nil(`var e *BizError; var err error = e`)不算命中:接口非 nil 但 指向 nil 指针,此时按文档取 b.BizCode 就是空指针解引用。

func Kv added in v0.38.2

func Kv(err error) []any

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

func KvFromChain(err error) []any

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

func New(msg string) error

New 构造哨兵错误。与 stdlib errors.New 同型(errors.Is/As 可判定)。 补齐后下游的哨兵错误不再需要双 import(stdlib + oekit)—— 「同一进程只允许一套 errors 包」的类型同一性红线才可完整执行(#8)。

func Recovered added in v0.45.0

func Recovered(r any) error

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 Unwrap added in v0.12.0

func Unwrap(err error) error

Unwrap 返回 err 的下一层错误。等价 stdlib errors.Unwrap(无下层返回 nil)。

func Wrap

func Wrap(err error, msg string, kv ...any) error

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

func AsBizError(err error) (*BizError, bool)

AsBizError 从错误链中提取 BizError。

第二个返回值在「链上是 typed-nil *BizError」时为 false——此时 bizErr 为 nil 指针,按文档用法 `if ok { use b.BizCode }` 直接用就会崩。

func NewBadRequestError

func NewBadRequestError(msg string) *BizError

NewBadRequestError 创建 400 业务错误(请求参数错误)。

func NewBizError

func NewBizError(code int, msg string) *BizError

NewBizError 创建一个自定义业务码错误,HTTPCode 留空(0), handler 层会经 StatusMapper 从 BizCode 推导 HTTP 状态码。

适用于业务码与 HTTP 对齐的场景(如 BizCode=404 → HTTP 404), 或接受默认映射(BizCode=1001 → HTTP 400)。 需要显式分离两码时使用 NewBizErrorWithStatus。

func NewBizErrorWithStatus

func NewBizErrorWithStatus(bizCode, httpCode int, msg string) *BizError

NewBizErrorWithStatus 创建一个显式指定 HTTP 状态码的业务错误。 适用于业务码与 HTTP 状态码需要分离的场景(如 BizCode=1001 但需返回 HTTP 402)。

func NewConflictError

func NewConflictError(msg string) *BizError

NewConflictError 创建 409 业务错误(资源冲突)。

func NewForbiddenError

func NewForbiddenError(msg string) *BizError

NewForbiddenError 创建 403 业务错误(无权限)。

func NewNotFoundError

func NewNotFoundError(msg string) *BizError

NewNotFoundError 创建 404 业务错误(资源不存在)。

func NewUnauthorizedError

func NewUnauthorizedError(msg string) *BizError

NewUnauthorizedError 创建 401 业务错误(未认证)。

func WrapBizError

func WrapBizError(code int, msg string, cause error) *BizError

WrapBizError 包装原始错误为业务错误,HTTPCode 留空(0), handler 层经 StatusMapper 从 BizCode 推导。

func WrapBizErrorWithStatus

func WrapBizErrorWithStatus(bizCode, httpCode int, msg string, cause error) *BizError

WrapBizErrorWithStatus 包装原始错误为业务错误,显式指定 HTTP 状态码。 字符串形态与 WrapBizError 同构(#202):Error()/String() 均带 cause—— 此前 cause 从不出现在本构造的错误字符串里(Unwrap 链在、字符串诊断链 断头),双码错误的 console_log 看不到内层原因。

func (*BizError) Error

func (e *BizError) Error() string

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() 都会空指针崩溃, 把「错误处理出错了」变成「进程崩了」。

func (*BizError) String

func (e *BizError) String() string

String 实现 fmt.Stringer 接口。与 Error() **完全一致**(#202:此前 String() 恒带 http_code:%d 且不带 cause,与 Error() 分叉——同一错误 两种字符串,日志与响应的信息量不一致。现在统一走同一格式,格式契约 见 Error)。

func (*BizError) Unwrap

func (e *BizError) Unwrap() error

Unwrap 实现 errors.Unwrap 接口,支持 errors.Is / errors.As 链式匹配。

type KVCarrier added in v0.38.2

type KVCarrier interface {
	error
	// Kv 返回本层附带的上下文 kv 对(无 kv 返回 nil)。
	Kv() []any
}

Kv() []any 是本仓 Wrap 上下文 kv 的**结构化读取口**(#292)。实现本接口 的错误可被 logx 等结构化日志出口升格为独立字段,而不是退化成 error 字符串 里的一段文本。

type MultiError

type MultiError struct {
	// contains filtered or unexported fields
}

MultiError 是聚合后的多错误类型。 实现 error、errors.Is(遍历检查)和 Errors()(获取子错误列表)。

func (*MultiError) Error

func (merr *MultiError) Error() string

Error 返回所有错误用 "; " 连接的字符串。

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 出去的原始错误。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL