惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

大猫的无限游戏
大猫的无限游戏
K
Kaspersky official blog
Apple Machine Learning Research
Apple Machine Learning Research
B
Blog
aimingoo的专栏
aimingoo的专栏
M
MIT News - Artificial intelligence
小众软件
小众软件
云风的 BLOG
云风的 BLOG
腾讯CDC
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Hugging Face - Blog
Hugging Face - Blog
S
SegmentFault 最新的问题
Stack Overflow Blog
Stack Overflow Blog
量子位
S
Secure Thoughts
G
GRAHAM CLULEY
C
CXSECURITY Database RSS Feed - CXSecurity.com
人人都是产品经理
人人都是产品经理
雷峰网
雷峰网
T
Threat Research - Cisco Blogs
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Cisco Talos Blog
Cisco Talos Blog
G
Google Developers Blog
爱范儿
爱范儿
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
有赞技术团队
有赞技术团队
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Martin Fowler
Martin Fowler
The GitHub Blog
The GitHub Blog
Google DeepMind News
Google DeepMind News
C
Cisco Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园 - 聂微东
宝玉的分享
宝玉的分享
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
Netflix TechBlog - Medium
Forbes - Security
Forbes - Security
Engineering at Meta
Engineering at Meta
S
Security Affairs
Help Net Security
Help Net Security
博客园 - 三生石上(FineUI控件)
AWS News Blog
AWS News Blog
博客园 - 叶小钗
Recent Commits to openclaw:main
Recent Commits to openclaw:main
V2EX - 技术
V2EX - 技术
Hacker News: Ask HN
Hacker News: Ask HN
Project Zero
Project Zero
H
Heimdal Security Blog
W
WeLiveSecurity
C
Check Point Blog

文章列表 on polarisxu

通过一个例子让你彻底掌握 Go 工作区模式 这道题正确率竟然只有 22% Go 开源项目推荐:一个简单的 Go 练手项目 go test 如何禁用缓存? Go1.18 快讯:constraints 包被移除标准库 跟着 Go 作者学泛型 「2022 版」轻松搞定 Go 开发环境 鹅厂小哥用行动反对加班:程序员加班能被终结吗? Go 为什么选择 Gopher 作为吉祥物? 本地如何配置多个 GitHub/Gitee 账号? 为 Java 开发者准备的 Go 教程 02:Java 有而 Go 无 GitHub 发现了 studygolang 项目依赖的漏洞 Typora 宣布收费后,这款开源 Markdown 编辑器火了 为 Java 开发者准备的 Go 教程 01:漫游了 我的 2021 年终总结 泛型版 singleflight:Go 中如何防止缓存击穿? 2021 年你写了多少代码?这个 Go 工具帮你统计 Go1.18 新特性:TryLock Go 泛型入门教程 程序员瑞士军刀:各种结构的转换工具 终于有 Go 版的 Elasticsearch 了 Go泛型系列:maps 包讲解 Go 1.18 中的 any 是什么? Go1.18 快讯:新的 IP 包 Go编程模式:详解函数式选项模式 Go泛型系列:slices 包讲解 Nginx 竟然也有 playground — Go 语言构建的 Go泛型系列:Go1.18 类型约束那些事 Go1.18 快讯:Module 工作区模式 Go1.18 快讯:新增的 Cut 函数太方便了 Go1.18 快讯:废弃了这个 API Go1.18 快讯:新增字符串 Clone API Go:如何获得项目根目录? Go 如何获取和设置环境变量 重磅变动:更快找到微信公众号里的Go文章 Go 这样设置版本号:我们的项目也可以 假期结束,推荐 2 本 manning 出的 Go 图书 Go Fiber 框架系列教程 04:测试应用 Go Fiber 框架系列教程 03:中间件 Go泛型系列:提前掌握Go泛型的基本使用 推荐一个 Go GUI 实战项目 Go Fiber 框架系列教程 02:详解相关 API 的使用 Go Fiber 框架系列教程 01: 和 Express 对比学习 Go Module:私有不合规库怎么解决引用问题 Go1.17 新特性:go get 变了 Go1.17 新特性:testing 包的相关变化 StackOverflow 上关于 Go select 死锁的问题 新书推荐:用 Gin 框架构建分布式应用 Go 官网要变天。。。 Gin 这是要成为 Go 官方框架? 扬眉吐气:刚刚,Go 已经默认支持泛型了 Go 1.17 新特性:Module 有哪些变化? Go 第三方库推荐:类型转换如此简单 担心密码提交到 GitHub?建议使用这个 Go 开源工具 Go 的时间格式化为什么是 2006-01-02 15:04:05? GitHub 为 Go 社区带来安全支持 Go开源项目推荐:500行代码确认请求时间花在哪 一道关于 len 函数的诡异 Go 面试题解析 厉害了我的 Go:推荐系统都有开源实现 Go1.17 新特性:新版构建约束 这个工具真好:看看你的Go项目依赖有无漏洞 网友很强大,发现了Go并发下载的Bug Rust 劝退系列 09:函数 Go项目实战:一步步构建一个并发文件下载器 这本 Go 新书挺期待的:100 个常见错误 Go 启用新的官方问答社区 这是要干嘛?!微软招 Go 编译器全职开发人员 Go1.17 快报之标准库越来越注重易用性 Go1.17 新特性之切片变数组 回顾 Go 官网的演变史 站长8年前的Go代码竟然进入大厂的项目里了 Go图书翻译:一个好消息,一个坏消息 不怕烂尾!决定组织翻译这本 Go 图书 Rust 劝退系列 08:模式匹配 一本花了2.5年写成的Go免费在线图书 被黑惨了:一句话,说明自己会 Go,咋整? Go1.16 中的新函数 signal.NotifyContext 怎么用? Go 1.17 新特性提前学之测试执行随机化 Rust 劝退系列 07:流程控制 官方的 Go 多版本管理:使用和原理 Rust新书:给你一个劝退的理由 周刊题解:常量表达式这个规则应该了解下 盘点那些使用 Go 语言的国外公司 编写了50万行Go代码是一种什么体验 Rust 劝退系列 06:常量 Uber 使用 Go 的规模这么大?!都自己定制的 Go 编译器了 Go Team Leader — rsc 大神新开源了一个库,增强模板功能 再一次看到了 Go 的节制:int128 类型要不要支持? Rust 劝退系列 05:复合数据类型 「卷」有理论依据:海勒姆定律—Go又是怎么卷的 Go 真的也可以进行 GUI 开发:还有这样的图书呢 Rust 劝退系列 04:基本数据类型 我的 Go 语言书单 Rust 劝退系列 03:变量 我又来推荐免费 Go 新书了:一本用 Go 讲架构的书 注释竟然还有特殊用途?一文解惑 //go:linkname 指令 这个功能,公众号、微信群会不会被玩坏? 一道 Go 闭包题,面试官说原来自己答错了:面别人也涨知识 Rust 劝退系列 02:第一个 Rust 程序 Rust 劝退系列 01:打造开发环境
除了 fmt.Errorf() 之外—Go 中的日常错误对象:CockroachDB errors 库(第4篇)
2020-11-04 · via 文章列表 on polarisxu

在 Go 中传递错误的惯用方式是使用预定义的类型错误。但是 Go 的标准库仅提供了非常简单的错误对象,errors.New() 和 fmt.Errorf()。

本文介绍了 CockroachDB 的 error 库(它是 Go 标准库库 errors 的直接替代品),Go 程序员如何用它来描述和传播其代码中的错误和错误代号(code)。

由 fmt.Errorf() 构造的 Go 中最常见的“简单”错误对象类似于带有错误接口的包含在结构中的字符串:其 Error() 方法返回构造错误时设置的字符串。

err := fmt.Errorf("hello")
fmt.Println(err) // prints "hello"

什么都没有,仅此而已。打印错误对象也会显示该字符串。顺便说一句,使用 Go 的错误包 errors 的构造函数构建错误 errors.New() 结果一样。

日常代码的简单错误

如果使用 Dave Cheney 的错误库 ,或者甚至更好的 CockroachDB 错误库 (通过导入 github.com/cockroachdb/errors),则简单错误也会在构造错误时自动捕获堆栈跟踪。

仅当详细打印错误时才显示堆栈跟踪。这样可以更轻松地排除错误的来源:

import (
   "fmt"
   "github.com/cockroachdb/errors"
)

func main() {
  err := errors.New("hello")
  fmt.Println(err) // still prints just "hello"

  fmt.Printf("%+v\n", err) // verbose mode
}

这会打印:

hello
(1) attached stack trace
  -- stack trace:
  | main.main
  |     /home/kena/src/errors-tests/test.go:10
  | runtime.main
  |     /usr/lib/go-1.14/src/runtime/proc.go:203
  | runtime.goexit
  |     /usr/lib/go-1.14/src/runtime/asm_amd64.s:1373
Wraps: (2) hello
Error types: (1) *withstack.withStack (2) *errutil.leafError

此详细输出包括第一行的 .Error() 结果,后跟堆栈跟踪内容。

经验一次又一次地表明,在程序中出现意外情况的确切点提取堆栈跟踪的能力对于查明确切原因并成功解决问题至关重要。没有这种能力,程序员会毫无线索,麻木排查,浪费大量时间。

仅出于这个原因,我不鼓励任何人使用 Go 自己的 fmt.Errorf() 或 errors.New()。相反,请导入 github.com/cockroachdb/errors 并仔细阅读以下内容:

  • errors.New():直接替换 Go 标准库的 errors.New(),但它会带有堆栈跟踪;
  • errors.Errorf() 或 errors.Newf():用堆栈跟踪的方式替换 Go 标准库的 fmt.Errorf();
package github.com/cockroachdb/errors

// New constructs a simple error and attaches a stack trace.
func New(msg string) error

// Newf constructs a simple error whose message is composed using printf-like formatting.
// It also attaches a stack trace.
func Newf(format string, args ...interface{}) error

// Errorf is an alias for Newf for convenience
// and drop-in compatibility with github.com/pkg/errors.
func Errorf(format string, args ...interface{}) error

在错误中添加消息前缀以识别上下文

当从多个位置调用相同的逻辑,并且可能因错误而失败时,则希望将消息前缀添加到任何返回的错误对象。

这有助于提供有关“错误发生的位置”的更多上下文,以便在运行时出现错误时(何时出现错误),可以清楚地了解哪个代码路径产生了错误。

例如:

package main

import (
   "fmt"
   "github.com/cockroachdb/errors"
)

func foo() error {
     return errors.New("boo")
}

func bar() error {
     if err := foo(); err != nil {
        return errors.Wrap(err, "bar")
     }
     return nil
}

func baz() error {
     if err := foo(); err != nil {
        return errors.Wrap(err, "baz")
     }
     return nil
}

func main() {
     r := rollDice()
     var err error
     if (r < 4) {
        err = bar()
     } else {
        err = baz()
     }
     fmt.Println(err)
}

多亏了 errors.Wrap(),它为消息添加了前缀,main 函数可能报告 bar:boo 或 baz:boo,并且人可以很方便的知晓是调用了哪个函数导致的错误。如果没有 errors.Wrap(),则导致错误的调用路径将是无法发现的。

为方便起见,当提供 nil 错误作为输入时,errors.Wrap() 返回nil。在许多情况下,这使我们可以消除 if err != nil 条件。例如:

func bar() error {
     return errors.Wrap(foo(), "bar")
}

func baz() error {
     return errors.Wrap(foo(), "baz")
}

最后,errors.Wrap() 还将辅助堆栈跟踪附加到错误对象,从而在对错误的来源进行故障排除时提供了额外的上下文。在 channel 的场景出现错误是特别有用。

对于 errors.New(),此堆栈跟踪仅在显示详细错误时可见。例如:

import (
   "fmt"
   "github.com/cockroachdb/errors"
)

func foo() error { return errors.New("world") }
func bar(err error) error { return errors.Wrap(err, "hello") }
func baz() error { return bar(foo()) }

func main() {
  err := baz()
  fmt.Println(err) // still prints just "hello: world"

  fmt.Printf("%+v\n", err) // verbose mode
}

将打印:

hello: world
(1) attached stack trace
  -- stack trace:
  | main.bar
  |     /home/kena/src/errors-tests/test.go:10
  | [...repeated from below...]
Wraps: (2) hello
Wraps: (3) attached stack trace
  -- stack trace:
  | main.foo
  |     /home/kena/src/errors-tests/test.go:9
  | main.baz
  |     /home/kena/src/errors-tests/test.go:11
  | main.main
  |     /home/kena/src/errors-tests/test.go:14
  | runtime.main
  |     /usr/lib/go-1.14/src/runtime/proc.go:203
  | runtime.goexit
  |     /usr/lib/go-1.14/src/runtime/asm_amd64.s:1373
Wraps: (4) world
Error types: (1) *withstack.withStack (2) *errutil.withPrefix (3) *withstack.withStack (4) *errutil.leafError

和以前一样,.Error() 的结果显示在第一行。然后,打印出最外层的堆栈跟踪(errors.Wrap() 的结果)。这表明错误被包裹在第 10 行,但调用跟踪与下面显示的一样。

然后,详细显示将显示内部错误,并显示消息 world 及其自身的堆栈跟踪。此内部堆栈跟踪显示内部错误是在第 9 行生成的。

错误包装工具用途广泛:可以使用类似于 printf 的格式来编写消息前缀。这是完整的 API:

package github.com/cockroachdb/errors

// Wrap adds a message prefix and also attaches an additional stack trace.
// If the first argument is nil, it returns nil.
func Wrap(err error, msg string) error

// Wrap adds a message prefix composed using printf-like formatting,
// and also attaches an additional stack trace.
// If the first argument is nil, it returns nil.
func Wrapf(err error, format string, args ...interface{}) error

此外,为了兼容 Go 1.13 的 fmt.Errorf(),上面看到的 errors.Newf() 和 errors.Errorf() 函数,它们还能识别出格式化动词 %w,从而触发 wrap 逻辑。

例如:

// The following is similar to errors.Wrapf(err, "hello").
// However, it does not return nil if err is nil!
err = errors.Newf("hello: %w", err)

请注意,只有 Newf()/Errorf() 可以识别 %w:errors.Wrap() 无法识别。

提示:应该优先使用 errors.Wrap() 代替特殊动词 %w:因为它会正确忽略作为输入给出的 nil 错误。

次要错误注解

每个中级 Go 程序员都会迅速陷入这一痛苦的境地:如果在处理错误时遇到错误,该怎么办?

一个常见的示例是在处理文件时遇到错误后清理文件系统:

func writeConfig(out string, cfgA, cfgB Config) (resErr error) {
    // Create the destination directory.
    if err := os.MkDir(out); err != nil {
       return err
    }
    defer func() {
       // If an error is encountered below, remove
       // the destination directory upon exit.
       if resErr != nil {
          if dirErr := os.RemoveAll(out); dirErr != nil {
             // now... what?
             ...
         }
       }
    }()

    if err := writeCfg(out, cfgA, "a.json", "config A"); err != nil {
      return err
    }
    return writeCfg(out, cfgB, "b.json", "config B")
}

func writeCfg(outDir path, cfg Config, filename, desc string) error {
    j, err := json.Marshal(cfg)
    if err != nil {
       return errors.Wrapf(err, "marshaling %s", desc)
    }
    return ioutil.WriteFile(filepath.Join(out, filename), j, 0777)
}

本示例中的函数创建一个输出目录,以将两个配置对象写入其中。但是,在写入某些配置对象时可能会发生错误。在这种情况下,该函数希望通过删除刚刚创建的目录来对其进行清理。

如果在目录删除过程中发生错误,该怎么办?应该返回哪个错误?

  • 如果返回原始错误,我们将看不到目录删除错误。
  • 如果返回目录删除错误,我们将看不到文件生成错误。

我们希望以某种方式返回有关这两个错误的详细信息,以帮助进行故障排除。同时,出于原因分析的目的,我们要谨慎地将遇到的第一个错误保留为“主要”错误。

我们可以通过如下调整代码来实现:

defer func() {
   // If an error is encountered below, remove
   // the destination directory upon exit.
   if resErr != nil {
      if dirErr := os.RemoveAll(out); dirErr != nil {
         // This attaches dirErr as an ancillary error
         // to the error object that was already stored in resErr.
         resErr = errors.WithSecondaryError(resErr, dirErr)
     }
   }
}()

通过这种编程模式,我们可以确信,在处理另一个错误时,我们可以保留遇到错误时所发生事件的全部情况。

次要错误注解不会影响主要错误上 .Error() 返回的文本。从相关代码以及标准 API error.Is() 的角度来看,代码的行为就像仅发生了主要错误一样。

但是,在详细打印过程中会发现第二个错误。例如:

package main

import (
   "fmt"
   "github.com/cockroachdb/errors"
)

func main() {
  err := errors.New("hello")
  err = errors.WithSecondaryError(err, errors.New("friend"))
  fmt.Println(err) // prints just "hello"

  fmt.Printf("%+v\n", err) // verbose mode
}

打印:

hello
(1) secondary error attachment
  | friend
  | (1) attached stack trace
  |   -- stack trace:
  |   | main.main
  |   |         /home/kena/src/errors-tests/test.go:11
  |   | runtime.main
  |   |         /usr/lib/go-1.14/src/runtime/proc.go:203
  |   | runtime.goexit
  |   |         /usr/lib/go-1.14/src/runtime/asm_amd64.s:1373
  | Wraps: (2) friend
  | Error types: (1) *withstack.withStack (2) *errutil.leafError
Wraps: (2) attached stack trace
  -- stack trace:
  | main.main
  |     /home/kena/src/errors-tests/test.go:10
  | runtime.main
  |     /usr/lib/go-1.14/src/runtime/proc.go:203
  | runtime.goexit
  |     /usr/lib/go-1.14/src/runtime/asm_amd64.s:1373
Wraps: (3) hello
Error types: (1) *secondary.withSecondaryError (2) *withstack.withStack (3) *errutil.leafError

像以前一样,我们在第一行看到 .Error() 的文本。然后,我们看到附加的次要错误的详细打印输出,相对于主要错误向右缩进。次要错误自己的 .Error()friend,首先打印它,然后打印次要错误的嵌入式堆栈跟踪。

然后,打印输出继续,不缩进地显示主要错误的堆栈跟踪。

API 概览:

package github.com/cockroachdb/errors

// WithSecondaryError attaches secondary as an annotation
// to the primary error. If primary is nil, nil is returned.
func WithSecondaryError(primary error, secondary error) error

// CombineErrors attaches err2 to err1 as secondary error
// if both err1 and err2 are not nil. If err1 is nil, err2
// is returned instead.
func CombineErrors(err1, err2 error) errors

子任务更智能的错误处理

扩展包 errgroup 提供了一个可重复使用的库,用于“为处理共同任务的子任务的 goroutine 组进行同步,错误传播和上下文取消”。

它的实现可以在这里找到:https://github.com/golang/sync/blob/master/errgroup/errgroup.go

在较高的级别上,它使用 sync.WaitGroup 运行多个 goroutine,并在末尾添加一个屏障。此外,一旦它们中的任何一个因错误终止,它将取消该组中的所有其他 goroutine。

逻辑问题在于,如果两个或多个 goroutine 因错误而失败,则仅报告第一个错误。其他错误是“被遗忘的”:

func (g *Group) Go(f func() error) {
     g.wg.Add(1)

     go func() {
             defer g.wg.Done()

             if err := f(); err != nil {
                     // errOnce.Do executes its argument just once. The second time an
                     // error is encountered, it is simply forgotten altogether! Not nice.
                     g.errOnce.Do(func() {
                             g.err = err
                             if g.cancel != nil {
                                     g.cancel()
                             }
                     })
             }
     }()
}

我们可以按以下方式解决此问题:

type Group struct {
     ...
     errOnce sync.Once
     mu {
        sync.Mutex // makes .err race-free.
        err     error
     }
}

func (g *Group) Wait() error {
   ...
   return g.mu.err
}

func (g *Group) Go(f func() error) {
     ...
     go func() {
             ...
             if err := f(); !errors.Is(err, context.Canceled) {
                   g.mu.Lock()
                   defer g.mu.Unlock()
                   g.mu.err = errors.CombineErrors(g.mu.err, err)
             }
     }()
}

使用 errgroup.Group 的此备用版本,如果子任务中有两个或多个错误,则第一个将成为“主要”错误,而第一个之后的所有其他错误将作为辅助错误注解附加。

该代码还使用 errors.Is(err, context.Canceled) 来排除由组调用共享上下文的 cancel() 函数而产生的错误对象,这些对象只是噪音,可能在故障排除期间没有用。

检查错误的身份

在最常见的情况下,错误会传播,最终通过网络连接返回,或打印到日志文件。

但是,有时代码需要检查错误对象以决定其他行为。

为此,库可以定义一些特定的函数来处理这种情况。例如:

package os

// IsExist returns a boolean indicating whether the error is known to report
// that a file or directory already exists. It is satisfied by ErrExist as
// well as some syscall errors.
func IsExist(err error) bool

可以这样使用:

func ensureDirectoryExists(path string) error {
    if err := os.Mkdir(path); err != nil {
       if os.IsExist(err) {
         // The directory already exists. This is OK,
         // no need to report an error.
         err = nil
       }
       return err
    }
    fmt.Println("directory created")
}

此函数尝试创建目录。如果已经存在,它将不执行任何操作。如果遇到另一个错误(例如磁盘损坏等),则会报告该错误。

另一种技术是使用“前哨”错误,并将返回的错误对象与那些标记进行比较以检测特定情况。

我们看到了上面带有 error.Is(err, context.Canceled) 的示例。这是来自 SQL 客户端程序的另一个示例:

func (c *sqlConn) Query(query string, args []driver.Value) (*sqlRows, error) {
     if err := c.ensureConn(); err != nil {
             return nil, err
     }
     rows, err := c.conn.Query(query, args)
     if errors.Is(err, driver.ErrBadConn) {
             // If the connection has been closed by the server or
             // there was some other kind of network error, close
             // the connection on our side so that the call to
             // ensureConn() above establishes a new connection
             // during the next query.
             c.Close()
             c.reconnecting = true
     }
     if err != nil {
             return nil, err
     }
     return &sqlRows{rows: rows.(sqlRowsI), conn: c}, nil
}

此代码检测 SQL 驱动程序何时返回 driver.ErrBadConn 并在这种情况下选择特殊行为。任何其他错误均按原样返回,并导致程序在此函数的调用程序中的某处停止。

errors.is() 可以通过重复调用 Unwrap() 方法来检测整个错误的直接因果链中的前哨错误。因此,将忽略“在途中”发现的任何次要错误注解。这种行为是有意设计的:类似树的行为将使人们难以推理出错误是另一个“原因”的含义。还会在其他 API errors.As() 错误中引发有关遍历顺序的难题。就个人而言,经验还没有向我表明,除线性因果链之外,其他任何东西在实践中有用。

和 pkg/errors 的不同

自 2016 年以来,事实上是 Go 的错误包的标准替代品是 Dave Cheney 的 pkg/errors 库,该库位于 https://github.com/pkg/errors

该包最初引入了链接列表错误对象的概念,自动包装错误并添加堆栈跟踪以在故障排除期间提供更多上下文。

不幸的是,Go 1.13 的发布使 pkg/errors 过时了:Dave Cheney 定义了自己的库,使用一种名为 Cause() 的方法来提取错误链的线性原因。当 Go 1.13 采纳用链接列表出错的想法时,它定义了另一个方法 Unwrap() 来提取原因。因此,Go 的errors.Is()和其他 API 无法理解源自 pkg/errors 的错误。

此外,来自 pkg/errors 的对象将严重遭受 Go Error 打印灾难,因此该库使自定义错误类型的实现非常困难。

CockroachDB 错误库接管了 pkg/errors:它采用 Go 1.13 约定,提供了 Go 1.13 标准 API 的直接替代品,并避免了 Go 错误打印灾难。它还实现了大多数 pkg/errors 接口,因此可以用作以前使用 Dave Cheney 库的程序的直接替代。

总结

Go 库通过 Go 自己的错误包中的 fmt.Errorf() 和 errors.New() 提供了错误接口的简化实现。

改用 CockroachDB 错误库,代替 Go 的错误包和 Dave Cheney的 pkg/errors,可以获得更好的体验。

它的错误构造函数 errors.New()/errors.Newf()(别名为 errors.Errorf())自动在错误对象中包含堆栈跟踪,可以使用 fmt.Printf("%+v" ,err) 打印堆栈追踪。

它还提供了错误包装器的词汇表。最常见的是带有 errors.Wrap()/errors.Wrapf() 的消息前缀注释,用于注释从多个位置调用的函数的调用路径。这还包括幕后的堆栈跟踪。

另一个常见的包装器解决了在处理另一个错误时遇到错误时如何在 Go 中执行的令人困惑的问题:使用辅助原因注解,并使用 errors.WithSecondaryCause() 或 errors.CombineErrors() 附加,Go 代码可以保留两个错误,因此程序员在故障排除期间可以同时看到两者。

CockroachDB 错误库中的错误还提供了一致的行为,并且在详细格式化错误时提供了有用的显示结构,从而避免了巨大的 Go 错误打印灾难。我们将在本系列的后续文章中专门探讨实现自定义错误,以进一步探讨该主题。