Golang中fmt.Println()无法打印包装的自定义错误

Golang中fmt.Println()无法打印包装的自定义错误 我有一个自定义错误类型,并以典型方式实现了 error 接口和 Unwrap() 方法。现在假设我有一个三层深的函数调用栈。所有层级都会添加一些错误。

第一层(最深)通过以下方式包装某个 io 错误: fmt.Errorf("err occured in level1 %w", ioErr)

第二层将上面返回的第一层错误包装到我的自定义错误中: return NewCustomError("err occurred in level2", lvl1Err)

第三层再次将上面返回的自定义错误包装到一个“普通”的 Go 错误中,通过: fmt.Errorf("err occured in level3 %w", customErr)

main() 中,我现在对第三层的错误执行 fmt.Println(err)。但打印出来的链只显示了第三层错误和第二层错误。一旦 Println() “碰到”我的自定义错误,它就停止了“打印链”。

我做了以下尝试:

  1. 如果我在第二层用 fmt.Errorf() 调用替换我的自定义错误,main 中的 fmt.Println() 会打印所有层级的错误。
  2. 如果我在 main() 中暂停调试器,我可以清楚地看到,即使使用了自定义错误,链中所有错误也都存在。
  3. 如果我使用 Go 的 errors.Unwrap(),我可以手动解包并打印所有错误,包括我的自定义错误。

简而言之:每个错误都正确地链接在一起,但不知何故,fmt.Println(err) 在到达自定义错误时停止了包装打印。

我一直以为 fmt.Println() 会做与上面第3点中我做的相同的事情:通过调用 Unwrap() 并打印错误,直到它为 nil,来遍历所有错误。然而,它在到达自定义错误时却停止了。我想知道这是为什么?

你可以在这里找到一个简单的示例代码,以便更好地理解:

GitHub - MBODM/golang-error-chains: Simple golang error-chains example

非常感谢任何帮助, 祝好


更多关于Golang中fmt.Println()无法打印包装的自定义错误的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

你好,

感谢你的回答!你提到的那行代码不是问题所在。但在你的示例代码中,第15行的 return e.Msg + e.Err.Error() 代码是关键点。顺便说一句:为了遵循 Go 标准库使用的相同模式,它应该是 return e.Msg + „: „ + e.Err.Error()

我在 StackOverflow 上提了一个问题,它比我上面在这里的帖子更好地阐述了我的观点,并且它包含了一个比我在这里做的更简单、更短的示例(对此表示抱歉):

StackOverflow 链接

我仍然是一个 Go 语言新手,所以如果我表述不清,我表示歉意。但感谢你的回复,playground 代码的第15行确实切中了要害。

更多关于Golang中fmt.Println()无法打印包装的自定义错误的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


是否是因为这行代码将实际的错误设置为了 nil?

func parse(fileContent string) (string, error) {
	// 假设我们在这里处理给定的文件内容并进行一些解析。
	// 但我们的解析失败了。现在我们必须“抛出”我们自己的自定义错误。
	// 因为在顶层的 main() 函数中,我们想要区分不同的错误。
	// ...
	// 在这里做一些花哨的解析工作。
	// ...
	// 现在解析在这里失败("WTF?"),我们返回自己的自定义错误:
	if sim.SimulateErrorInPackage2 {
		return "", &app.CustomError{Err: nil, Msg: "parsing failed cause of WTF situation"}
	}
	// 但如果一切顺利,返回解析后的参数(当然我们伪造了这个):
	parsedParams := "/C echo Hello World (some pseudo result from successful parsing in package2)"
	return parsedParams, nil
}

也许,这个链接能提供更多见解;

Go Playground

Go Playground - The Go Programming Language

在Go语言中,fmt.Println() 打印错误链时确实会通过 Unwrap() 方法遍历错误,但自定义错误类型需要正确实现 error 接口和 Unwrap() 方法才能被 fmt 包识别。根据你的描述,问题可能出现在自定义错误的 Error() 方法实现上。fmt 包在打印错误链时,会检查每个错误是否实现了 Unwrap() error 方法,但自定义错误的 Error() 方法返回的字符串可能没有包含完整的错误链信息。

以下是一个示例,展示如何正确实现自定义错误,确保 fmt.Println() 能打印整个错误链:

package main

import (
    "errors"
    "fmt"
    "io"
)

// 自定义错误类型
type CustomError struct {
    msg string
    err error
}

// 实现 error 接口的 Error() 方法
func (e *CustomError) Error() string {
    return e.msg
}

// 实现 Unwrap() 方法以支持错误链
func (e *CustomError) Unwrap() error {
    return e.err
}

// 创建自定义错误的函数
func NewCustomError(msg string, err error) error {
    return &CustomError{msg: msg, err: err}
}

func level1() error {
    // 模拟一个底层错误
    ioErr := io.EOF
    return fmt.Errorf("err occurred in level1: %w", ioErr)
}

func level2() error {
    lvl1Err := level1()
    // 使用自定义错误包装第一层错误
    return NewCustomError("err occurred in level2", lvl1Err)
}

func level3() error {
    customErr := level2()
    // 使用 fmt.Errorf 包装自定义错误
    return fmt.Errorf("err occurred in level3: %w", customErr)
}

func main() {
    err := level3()
    fmt.Println(err)
}

运行上述代码,输出将是完整的错误链:

err occurred in level3: err occurred in level2: err occurred in level1: EOF

如果 fmt.Println() 仍然无法打印完整链,请检查自定义错误的 Error() 方法是否返回了正确的字符串。fmt 包在打印错误链时,会递归调用每个错误的 Error() 方法并拼接它们。确保自定义错误的 Error() 方法返回的字符串包含了内部错误的信息,例如:

func (e *CustomError) Error() string {
    if e.err != nil {
        return fmt.Sprintf("%s: %v", e.msg, e.err)
    }
    return e.msg
}

这样修改后,fmt.Println() 应该能正确打印整个错误链。

回到顶部