You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2026-06-03 18:35:08 +02:00
unwrap error chains created with fmt.Errorf (#8133)
Fixes https://github.com/open-telemetry/opentelemetry-go/issues/7975 Per https://github.com/open-telemetry/opentelemetry-go/issues/7975#issuecomment-4183251694 Per https://github.com/open-telemetry/semantic-conventions/issues/3588 Credits: - Balaji01-4D for https://github.com/open-telemetry/opentelemetry-go/pull/8018 - seh for providing valuable feedback
This commit is contained in:
@@ -5,6 +5,7 @@ package semconv // import "go.opentelemetry.io/otel/semconv/{{.TagVer}}"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
@@ -22,7 +23,8 @@ import (
|
||||
// the returned attribute has that method's return value. If multiple errors in
|
||||
// the chain implement this method, the value from the first match found by
|
||||
// [errors.As] is used. Otherwise, the returned attribute has a value derived
|
||||
// from the concrete type of err.
|
||||
// from the concrete type of err after unwrapping any wrappers created with
|
||||
// [fmt.Errorf].
|
||||
//
|
||||
// The key of the returned attribute is [ErrorTypeKey].
|
||||
func ErrorType(err error) attribute.KeyValue {
|
||||
@@ -50,7 +52,7 @@ func errorType(err error) string {
|
||||
// Fallback to reflection if the ErrorType method is not supported or
|
||||
// returns an empty value.
|
||||
|
||||
t := reflect.TypeOf(err)
|
||||
t := reflect.TypeOf(unwrapFmtWrapped(err))
|
||||
pkg, name := t.PkgPath(), t.Name()
|
||||
if pkg != "" && name != "" {
|
||||
s = pkg + "." + name
|
||||
@@ -64,3 +66,16 @@ func errorType(err error) string {
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
var fmtWrapErrorType = reflect.TypeOf(fmt.Errorf("wrapped: %w", errors.New("err")))
|
||||
|
||||
func unwrapFmtWrapped(err error) error {
|
||||
for reflect.TypeOf(err) == fmtWrapErrorType {
|
||||
u := errors.Unwrap(err)
|
||||
if u == nil {
|
||||
return err // When the wrapped error is nil, use the concrete type of the wrapper.
|
||||
}
|
||||
err = u
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package semconv // import "go.opentelemetry.io/otel/semconv/{{.TagVer}}"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -18,6 +19,10 @@ func TestErrorType(t *testing.T) {
|
||||
check(t, custom(""), pkg+".ErrCustomType") // empty ErrorType, use concrete type.
|
||||
check(t, wrapped(custom("wrapped-aborted")), "wrapped-aborted")
|
||||
check(t, wrapped(custom("")), pkg+".wrappedErr") // empty ErrorType in chain, use concrete top-level type.
|
||||
check(t, fmtWrapped(custom("")), pkg+".ErrCustomType")
|
||||
check(t, fmtWrapped(wrapped(custom(""))), pkg+".wrappedErr")
|
||||
check(t, fmtWrapped(fmtWrapped(custom(""))), pkg+".ErrCustomType")
|
||||
check(t, fmtWrapped(nil), fmtWrapErrorType.String()) // fmt.Errorf with nil error, use concrete type of the wrapper.
|
||||
}
|
||||
|
||||
func check(t *testing.T, err error, want string) {
|
||||
@@ -39,6 +44,10 @@ func wrapped(err error) error {
|
||||
return wrappedErr{err: err}
|
||||
}
|
||||
|
||||
func fmtWrapped(err error) error {
|
||||
return fmt.Errorf("wrapped: %w", err)
|
||||
}
|
||||
|
||||
type ErrCustomType struct {
|
||||
Type string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user