fmt.Println()在包装的自定义错误处停止打印链 (golang)

huangapple go评论84阅读模式
英文:

fmt.Println() stops printing chain at wrapped custom error (golang)

问题

我想知道为什么在Go/golang中,当使用fmt.Errorf()创建的所有包装错误都打印出来时,fmt.Println()会打印出某个错误链中的所有错误,但当其中一个错误是自定义错误(也进行了包装)时,fmt.Println()会停止解包和打印错误链,在自定义错误位置停止。

这是一些简化的示例代码:

type CustomError struct {
	Msg string
	Err error
}

func (e *CustomError) Error() string {
	return e.Msg
}

func (e *CustomError) Unwrap() error {
	return e.Err
}

func main() {
	level1Err := errors.New("[Error in L1]: Boom")
	level2Err := fmt.Errorf("[Error in L2]: Wrap L1Err %w", level1Err)
	level3Err := fmt.Errorf("[Error in L3]: Wrap L2Err %w", level2Err)
	//level3Err := &CustomError{"[Error in L3]: Wrap L2Err", level2Err}
	level4Err := fmt.Errorf("[Error in L4]: Wrap L3Err %w", level3Err)
	fmt.Println(level4Err)
}

// 当取消注释第28行并注释第29行时的控制台输出:
// [Error in L4]: Wrap L3Err [Error in L3]: Wrap L2Err [Error in L2]: Wrap L1Err [Error in L1]: Boom

// 当取消注释第29行并注释第28行时的控制台输出:
// [Error in L4]: Wrap L3Err [Error in L3]: Wrap L2Err

使用Go 1.18版本。

你也可以在https://github.com/MBODM/golang-error-chains-problem找到示例代码。

非常感谢你的帮助。

英文:

i want to know why fmt.Println() in Go/golang does print all the errors of some error-chain, when all wrapped errors in that error-chain where created with fmt.Errorf(). But when one of these errors is a custom error (that also wraps), fmt.Println() stops unwrapping and printing of error chain, at the custom error position.

Here is some simplified sample code:

type CustomError struct {
	Msg string
	Err error
}

func (e *CustomError) Error() string {
	return e.Msg
}

func (e *CustomError) Unwrap() error {
	return e.Err
}

func main() {
	level1Err := errors.New("[Error in L1]: Boom")
	level2Err := fmt.Errorf("[Error in L2]: Wrap L1Err %w", level1Err)
	level3Err := fmt.Errorf("[Error in L3]: Wrap L2Err %w", level2Err)
	//level3Err := &CustomError{"[Error in L3]: Wrap L2Err", level2Err}
	level4Err := fmt.Errorf("[Error in L4]: Wrap L3Err %w", level3Err)
	fmt.Println(level4Err)
}

// Console output, when uncomment line28 and comment line29:
// [Error in L4]: Wrap L3Err [Error in L3]: Wrap L2Err [Error in L2]: Wrap L1Err [Error in L1]: Boom

// Console output, when uncomment line29 and comment line28:
// [Error in L4]: Wrap L3Err [Error in L3]: Wrap L2Err

Using Go 1.18

You can also found the sample code at https://github.com/MBODM/golang-error-chains-problem

Any help is rather appreciated

答案1

得分: 1

如果你查看现有的一个示例来自go源代码

// SyscallError记录来自特定系统调用的错误。
type SyscallError struct {
	Syscall string
	Err     error
}

func (e *SyscallError) Error() string { return e.Syscall + ": " + e.Err.Error() }

func (e *SyscallError) Unwrap() error { return e.Err }

那么你的CustomError#Error()方法应该是:

func (e *CustomError) Error() string {
	if e.Err == nil {
		return e.Msg
	}
	return e.Msg + ": " + e.Err.Error()
}

请参考这个playground示例

[L4中的错误]:包装L3Err [L3中的ErrorC]:包装L2Err:[L2中的错误]:包装L1Err [L1中的错误]:Boom
英文:

If you look at an existing example from go source code:

// SyscallError records an error from a specific system call.
type SyscallError struct {
	Syscall string
	Err     error
}

func (e *SyscallError) Error() string { return e.Syscall + ": " + e.Err.Error() }

func (e *SyscallError) Unwrap() error { return e.Err }

So your CustomError#Error() method should be:

func (e *CustomError) Error() string {
	if e.Err == nil {
		return e.Msg
	}
	return e.Msg + ": " + e.Err.Error()
}

See this playground example:

[Error in L4]: Wrap L3Err [ErrorC in L3]: Wrap L2Err: [Error in L2]: Wrap L1Err [Error in L1]: Boom

答案2

得分: 1

为了帮助像我这样的人(作为一个Go的新手,我在这方面遇到了很多困难),我在这里回答了自己的问题:

似乎fmt.Println(topLevelErr)并没有解包所有的错误并打印所有的错误(通过调用每个解包错误的.Error()方法并用一些“:”作为错误之间的分隔符打印所有的错误),这与我最初的想法不同。事实并非如此。

相反,所有的Go库错误都是通过自己实现error接口来完成的:它们的Error()方法(error的实现)会检查它们自己内部是否有一些包装错误,如果有,就会打印出来。这意味着它们会打印自己的错误消息+“:”+包装错误的消息。

你可以通过查看Go源代码来验证这一点,就像下面的VonC所提到的那样。

我个人认为这是一个相当愚蠢的设计。而且完全没有必要,因为所有的错误都已经被包装了。因此,所有的错误都可以通过go-lib中的errors.Unwrap()方法手动解包。解包后,我们(或者也可以是fmt.Println())可以打印出每个错误的错误消息,以及一些分隔的“:”内容。这将是一个比“每个错误都在自己的消息中包装了它内部错误的消息”更好的设计。我认为这就是为什么它让我感到困惑的原因。:)

<s>
另外,我建议你在下面的示例代码中做和我一样的事情:至少让fmt.Errorf()创建那个包装的错误消息,而不是自己放入一些“:”字符串。这样,当包装错误的行为发生变化时,你的代码也会跟着变化。
</s>

之前的建议是错误的,所以我把它删除了。我不小心认为fmt.Errorf()在用%w包装错误时会在文本中添加一个“:”。这是不正确的。正如VonC已经指出的:Go标准库中的大部分内容在实现它们的错误接口时都是这样做的:

> return "own error text" + ": " + e.err.Error()

所以,无论你在错误的实现中使用哪个,都没有关系:

  • return fmt.Errorf("%s: %w", "own error text", e.err).Error()
  • return "own error text" + ": " + e.err.Error()

在这两种情况下,你都需要自己做“:”的事情。简而言之:fmt.Errorf()函数不会自动将“:”放入创建的错误消息中。

这就是全部内容的展示:

type CustomError struct {
	Msg string
	Err error
}

func (e *CustomError) Error() string {
	if e.Err != nil {
        // 在这里添加了一个“:”,因为fmt.Errorf()并没有自动做到这一点(就像我之前错误地认为的那样)!
		wrappedError := fmt.Errorf("%s: %w", e.Msg, e.Err)
		wrappedErrorMsg := wrappedError.Error()
		return wrappedErrorMsg
        // 你也可以像大多数go-lib包一样这样做:
        // return e.Msg + ": " + e.Err.Error()
	}
	return e.Msg
}

func (e *CustomError) Unwrap() error {
	return e.Err
}

func printAllWrappedErrors(topLevelError error) {
	fmt.Println(topLevelError)
}

func printCustomErrorOnly(topLevelError error) {
	var e *CustomError
	if errors.As(topLevelError, &e) {
		fmt.Println(e.Msg) // <-- 这是不同之处
	}
}

func printCustomErrorIncludingAllWrappedErrors(topLevelError error) {
	var e *CustomError
	if errors.As(topLevelError, &e) {
		fmt.Println(e) // <-- 这是不同之处
	}
}

为了更好地理解,可以尝试使用这个示例代码。你可以在GitHub上找到它这里

非常感谢VonC的及时帮助和启动!非常有帮助!:)

玩得开心。

英文:

To help other ppls like me (i had a hard time with this, as a Go newbie), i answer my own question here:

It seems fmt.Println(topLevelErr) NOT unwraps all the errors and prints all that errors (by calling .Error() of every unwrapped error) with some &quot;: &quot; as delimiter between the errors, as i thought initially. This is NOT what happens.

Instead all the go-lib errors do this by themselfes, by implementing the error interface in this way: Their Error() method (error implementation) tests if they have some wrapped error inside of themselfes and if so, print it "chained". This means they print their own error msg + &quot;: &quot; + the wrapped error´s msg.

You can see this, if you have a look at the Go source code, as VonC mentioned below.

I personally would call this a rather stupid design. And totally unnecessary too, since all errors are wrapped anyway. Therefore all errors could be unwrapped manually, via errors.Unwrap() method from go-lib. When unwrapped, we (or also fmt.Println()) could print the error msg of every error, together with some delimiting &quot;: &quot; stuff. This would be a way better design, than "every error is wrapping the msg of it´s inner error inside it´s own msg". I think that´s the reason why it confused me a lot. fmt.Println()在包装的自定义错误处停止打印链 (golang)

<s>
Also i advice you to do the same thing as i did in sample code below: At least let fmt.Errorf() create that wrapped error msg, instead of putting some &quot;: &quot; string into it, by yourself. So it is granted, when the wrapped error behaviour changes, yours do also.
</s>

Previous advice was incorrect, so i removed it. I accidentally thought fmt.Errorf() adds a &quot;:&quot; to the text, when wrapping the error with %w. This is not true. As VonC already pointed out: Most stuff in the Go standard library do this in the implementation of their error interface:

> return &quot;own error text&quot; + &quot;: &quot; + e.err.Error()

So it doesnt matter, which of these you use, in your error implementation:

  • return fmt.Errorf(&quot;%s: %w&quot;, &quot;own error text&quot;, e.err).Error()
  • return &quot;own error text&quot; + &quot;: &quot; + e.err.Error()

In both cases, you need to do the &quot;: &quot; thingy by your own. In short: The fmt.Errorf() function does NOT automatically put the &quot;: &quot; into the created error´s msg.

This shows, what´s all about:

type CustomError struct {
	Msg string
	Err error
}

func (e *CustomError) Error() string {
	if e.Err != nil {
        // Added a &quot;:&quot; here, because fmt.Errorf() does NOT
        // automatically do this (as i accidentally thought above)!
		wrappedError := fmt.Errorf(&quot;%s: %w&quot;, e.Msg, e.Err)
		wrappedErrorMsg := wrappedError.Error()
		return wrappedErrorMsg
        // Also you can do this instead (like most go-lib pkgs):
        // return e.Msg + &quot;: &quot; + e.Err.Error()
	}
	return e.Msg
}

func (e *CustomError) Unwrap() error {
	return e.Err
}

func printAllWrappedErrors(topLevelError error) {
	fmt.Println(topLevelError)
}

func printCustomErrorOnly(topLevelError error) {
	var e *CustomError
	if errors.As(topLevelError, &amp;e) {
		fmt.Println(e.Msg) // &lt;-- This is the difference
	}
}

func printCustomErrorIncludingAllWrappedErrors(topLevelError error) {
	var e *CustomError
	if errors.As(topLevelError, &amp;e) {
		fmt.Println(e) // &lt;-- This is the difference
	}
}

For a better understanding just play around with the sample.
You can find it here on GitHub.

And big THX to VonC for the prompt help and the Kickoff! Was very helpful! fmt.Println()在包装的自定义错误处停止打印链 (golang)

Have fun.

huangapple
  • 本文由 发表于 2022年6月5日 16:44:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/72505935.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定