Skip to content

error.type for wrapped errors#8018

Open
Balaji01-4D wants to merge 16 commits intoopen-telemetry:mainfrom
Balaji01-4D:semconv-error-type-unwrap
Open

error.type for wrapped errors#8018
Balaji01-4D wants to merge 16 commits intoopen-telemetry:mainfrom
Balaji01-4D:semconv-error-type-unwrap

Conversation

@Balaji01-4D
Copy link

Fixes #7975

When the reflection fallback derives error.type, walk the standard Go error chain using errors.Unwrap() and reflect on the final unwrapped error instead of the outer wrapper fmt.wrapError.

Scope:

  • Only modifies the reflection fallback logic.
  • Aggregated errors (errors.Join) are unchanged.

Copilot AI review requested due to automatic review settings March 7, 2026 17:12
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.0 tests to validate behavior for fmt.Errorf("%w", ...) wrappers and custom wrappers.
  • Mirror the logic and test updates in semconvkit templates 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.

Copy link
Member

@pellared pellared left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add a changelog entries under a Changed section?

@pellared
Copy link
Member

pellared commented Mar 9, 2026

Please apply the same change here:

func errorType(err error) string {
if et, ok := err.(interface{ ErrorType() string }); ok {
if s := et.ErrorType(); s != "" {
return s
}
}
t := reflect.TypeOf(err)
if t == nil {
return ""
}
pkg, name := t.PkgPath(), t.Name()
if pkg != "" && name != "" {
return pkg + "." + name
}
// The type has no package path or name (predeclared, not-defined,
// or alias for a not-defined type).
//
// The type has no package path or name (predeclared, not-defined,
// or alias for a not-defined type).
//
// This is not guaranteed to be unique, but is a best effort.
return t.String()
}

@Balaji01-4D
Copy link
Author

Please apply the same change here:

func errorType(err error) string {
if et, ok := err.(interface{ ErrorType() string }); ok {
if s := et.ErrorType(); s != "" {
return s
}
}
t := reflect.TypeOf(err)
if t == nil {
return ""
}
pkg, name := t.PkgPath(), t.Name()
if pkg != "" && name != "" {
return pkg + "." + name
}
// The type has no package path or name (predeclared, not-defined,
// or alias for a not-defined type).
//
// The type has no package path or name (predeclared, not-defined,
// or alias for a not-defined type).
//
// This is not guaranteed to be unique, but is a best effort.
return t.String()
}

I noticed that PR #7994 also modified the ErrorType function in internal/tools/semconvkit/templates/error_type.go.tmpl. Should those changes be considered here as well?

@pellared
Copy link
Member

pellared commented Mar 9, 2026

Please apply the same change here:

func errorType(err error) string {
if et, ok := err.(interface{ ErrorType() string }); ok {
if s := et.ErrorType(); s != "" {
return s
}
}
t := reflect.TypeOf(err)
if t == nil {
return ""
}
pkg, name := t.PkgPath(), t.Name()
if pkg != "" && name != "" {
return pkg + "." + name
}
// The type has no package path or name (predeclared, not-defined,
// or alias for a not-defined type).
//
// The type has no package path or name (predeclared, not-defined,
// or alias for a not-defined type).
//
// This is not guaranteed to be unique, but is a best effort.
return t.String()
}

I noticed that PR #7994 also modified the ErrorType function in internal/tools/semconvkit/templates/error_type.go.tmpl. Should those changes be considered here as well?

This would be great!

@Balaji01-4D
Copy link
Author

@pellared I have completed both changes. I applied the same change in sdk/log/logger.go and added test cases for TestErrorType covering wrapped and joined errors.

Copy link
Member

@pellared pellared left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@seh, @MrAlias, I would appreciate your reviews/approvals.

@pellared pellared added this to the v1.43.0 milestone Mar 12, 2026
@codecov
Copy link

codecov bot commented Mar 12, 2026

Codecov Report

❌ Patch coverage is 70.96774% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.5%. Comparing base (2ffde5a) to head (c9b7ec0).

Files with missing lines Patch % Lines
semconv/v1.40.0/error_type.go 0.0% 8 Missing ⚠️
sdk/log/logger.go 95.6% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@           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     
Files with missing lines Coverage Δ
sdk/log/logger.go 98.9% <95.6%> (-1.1%) ⬇️
semconv/v1.40.0/error_type.go 0.0% <0.0%> (ø)

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Balaji01-4D
Copy link
Author

Done add740f

@pellared , Thanks. I will work on the errors.Join part once a dedicated issue is opened.

@seh
Copy link
Contributor

seh commented Mar 12, 2026

I will work on the errors.Join part once a dedicated issue is opened.

Speaking of errors.Join, did you see my #7856 (comment) over on #7856?

@Balaji01-4D
Copy link
Author

Speaking of errors.Join, did you see my #7856 (comment) over on #7856?

Thanks for pointing out. I saw your comment on #7856. You are right that the implementation builds a nested chain of errors.joinError instead of a flat sequence of errors. I’ll take note of this.

// returns an empty value.

t := reflect.TypeOf(err)
t := reflect.TypeOf(rootError(err))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this test and the next three would fail if the standard library changed the names of these two unexported types.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

error.type for wrapped errors

5 participants