error.type for wrapped errors#8018
Conversation
There was a problem hiding this comment.
Pull request overview
Updates the semconv v1.40.0 ErrorType reflection fallback to avoid reporting wrapper types (notably *fmt.wrapError) by unwrapping via errors.Unwrap() before reflecting, and extends the generated tests/templates accordingly.
Changes:
- Walk the standard Go unwrap chain in the reflection fallback and reflect on the final unwrapped error.
- Adjust
semconv/v1.40.0tests to validate behavior forfmt.Errorf("%w", ...)wrappers and custom wrappers. - Mirror the logic and test updates in
semconvkittemplates so future generated semconv packages match.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| semconv/v1.40.0/error_type.go | Changes reflection fallback to unwrap the error chain before deriving error.type. |
| semconv/v1.40.0/error_type_test.go | Adds coverage for wrapped errors and updates expected type selection behavior. |
| internal/tools/semconvkit/templates/error_type.go.tmpl | Mirrors the unwrap-before-reflect logic in the code generator template. |
| internal/tools/semconvkit/templates/error_type_test.go.tmpl | Mirrors the updated test expectations in the generator template. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Please apply the same change here: opentelemetry-go/sdk/log/logger.go Lines 160 to 185 in 4b025b4 |
I noticed that PR #7994 also modified the |
This would be great! |
|
@pellared I have completed both changes. I applied the same change in |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #8018 +/- ##
=======================================
- Coverage 81.6% 81.5% -0.1%
=======================================
Files 304 304
Lines 23452 23473 +21
=======================================
+ Hits 19141 19150 +9
- Misses 3928 3939 +11
- Partials 383 384 +1
🚀 New features to boost your workflow:
|
Speaking of |
Thanks for pointing out. I saw your comment on #7856. You are right that the implementation builds a nested chain of |
| // returns an empty value. | ||
|
|
||
| t := reflect.TypeOf(err) | ||
| t := reflect.TypeOf(rootError(err)) |
There was a problem hiding this comment.
This replaces the current reporting solely of the outermost error with reporting solely of the innermost. There are three types that could be involved potentially, though, for these errors:
- The outermost error
- The error returned by the first call of
errors.Unwrap(presumably non-nil) on the outermost error - The innermost error discovered by digging through successive calls to
errors.Unwrap, as you've done here
This change would improve things from my perspective, but I'm anticipating that others will complain that they're interested more in one of the first two types mentioned above.
Do we need a semantic convention to bless us introducing a separate attribute name to report on these types—especially the second two—when they differ?
There was a problem hiding this comment.
Good point. Using rootError changes the behavior from reporting the outermost error to solely reporting the innermost one discovered via repeated errors.Unwrap. I agree this could blur the distinction between the outermost error and the first Unwrap result.
My reasoning was that the innermost error typically represents the concrete failure type, while outer wrappers often add context as mention this comment, but not meant for categorize as you noted.
If distinguishing those cases is important, introducing a separate attribute for wrapper types might make the semantics clearer rather than overwriting error.type. for example, error.cause.type for the first Unwrap result and likewise error.root.type. However, I’m not entirely sure about this.
| 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, wrapped(custom("")), pkg+".ErrCustomType") // empty ErrorType in chain, unwraps to innermost type. | ||
| check(t, wrapped(errors.New("msg")), "*errors.errorString") // wrapper unwraps to inner type. |
There was a problem hiding this comment.
Note that this test and the next three would fail if the standard library changed the names of these two unexported types.
There was a problem hiding this comment.
I see. These tests rely on unexported stdlib name which could change. Would it make sense to avoid asserting the exact type string?
| - Introduce the `EMPTY` Type in `go.opentelemetry.io/otel/attribute` to reflect that an empty value is now a valid value, with `INVALID` remaining as a deprecated alias of `EMPTY`. (#8038) | ||
| - Refactor slice handling in `go.opentelemetry.io/otel/attribute` to optimize short slice values with fixed-size fast paths. (#8039) | ||
| - `ErrorType` in `go.opentelemetry.io/otel/semconv` now unwraps error chains when deriving the `error.type` attribute. (#8018) | ||
| - `go.opentelemetry.io/otel/sdk/log` now unwraps error chains when deriving the `error.type` attribute from errors on log records. (#8018) |
There was a problem hiding this comment.
Mentioning "unwraps" is correct mechanically, but it doesn't clarify how much unwrapping we're now doing. Consider including "as deeply as possible" or "as completely as possible" after "error chains".
Signed-off-by: Balaji J <j.balaji2468@gmail.com>
Signed-off-by: Balaji J <j.balaji2468@gmail.com>
Fixes #7975
When the reflection fallback derives
error.type, walk the standard Go error chain usingerrors.Unwrap()and reflect on the final unwrapped error instead of the outer wrapperfmt.wrapError.Scope:
errors.Join) are unchanged.