1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2026-06-03 18:35:08 +02:00
Robert Pająk
2026-04-07 20:03:30 +02:00
committed by GitHub
parent 5e9a80b3ce
commit 3b18b21580
7 changed files with 97 additions and 5 deletions
+5
View File
@@ -12,6 +12,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add `ByteSlice` and `ByteSliceValue` functions for new `BYTESLICE` attribute type in `go.opentelemetry.io/otel/attribute`. (#7948)
### Changed
- `ErrorType` in `go.opentelemetry.io/otel/semconv` now unwraps errors created with `fmt.Errorf` when deriving the `error.type` attribute. (#8133)
- `go.opentelemetry.io/otel/sdk/log` now unwraps error chains created with `fmt.Errorf` when deriving the `error.type` attribute from errors on log records. (#8133)
<!-- Released section -->
<!-- Don't change this section unless doing release -->
@@ -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
}
+16 -1
View File
@@ -5,6 +5,8 @@ package log // import "go.opentelemetry.io/otel/sdk/log"
import (
"context"
"errors"
"fmt"
"reflect"
"time"
@@ -164,7 +166,7 @@ func errorType(err error) string {
}
}
t := reflect.TypeOf(err)
t := reflect.TypeOf(unwrapFmtWrapped(err))
if t == nil {
return ""
}
@@ -183,3 +185,16 @@ func errorType(err error) string {
// This is not guaranteed to be unique, but is a best effort.
return t.String()
}
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
}
+24
View File
@@ -6,6 +6,7 @@ package log // import "go.opentelemetry.io/otel/sdk/log"
import (
"context"
"errors"
"fmt"
"strconv"
"testing"
"time"
@@ -404,6 +405,21 @@ func TestErrorType(t *testing.T) {
var err error = struct{ baseErr }{}
assert.Contains(t, errorType(err), "struct")
})
t.Run("FmtWrappedFallsBackToWrappedType", func(t *testing.T) {
err := fmt.Errorf("wrapped: %w", errWithType{msg: "boom", typ: ""})
assert.Equal(t, "go.opentelemetry.io/otel/sdk/log.errWithType", errorType(err))
})
t.Run("CustomWrapperStaysTopLevel", func(t *testing.T) {
err := wrappedErr{err: errWithType{msg: "boom", typ: ""}}
assert.Equal(t, "go.opentelemetry.io/otel/sdk/log.wrappedErr", errorType(err))
})
t.Run("FmtWrappedNilError", func(t *testing.T) {
err := fmt.Errorf("wrapped: %w", nil)
assert.Equal(t, fmtWrapErrorType.String(), errorType(err))
})
}
type errWithType struct {
@@ -419,6 +435,14 @@ type baseErr struct{}
func (baseErr) Error() string { return "boom" }
type wrappedErr struct {
err error
}
func (e wrappedErr) Error() string { return "wrapped: " + e.err.Error() }
func (e wrappedErr) Unwrap() error { return e.err }
func TestNewRecordSkipsExceptionWhenPresent(t *testing.T) {
l := newLogger(NewLoggerProvider(), instrumentation.Scope{})
+17 -2
View File
@@ -5,6 +5,7 @@ package semconv // import "go.opentelemetry.io/otel/semconv/v1.40.0"
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 // Should never happen, but avoid returning nil if unwrapping fails.
}
err = u
}
return err
}
+9
View File
@@ -5,6 +5,7 @@ package semconv // import "go.opentelemetry.io/otel/semconv/v1.40.0"
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
}