diff --git a/CHANGELOG.md b/CHANGELOG.md index 9be9fa06d..7a1e084d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `RPCConnectRPCResponseMetadata` - `RPCGRPCRequestMetadata` - `RPCGRPCResponseMetadata` +- Add `ErrorType` attribute helper function to the `go.opentelmetry.io/otel/semconv/v1.34.0` package. (#6962) ### Changed diff --git a/internal/tools/semconvkit/templates/error_type.go.tmpl b/internal/tools/semconvkit/templates/error_type.go.tmpl new file mode 100644 index 000000000..185cf5810 --- /dev/null +++ b/internal/tools/semconvkit/templates/error_type.go.tmpl @@ -0,0 +1,31 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package semconv // import "go.opentelemetry.io/otel/semconv/{{.TagVer}}" + +import ( + "fmt" + "reflect" + + "go.opentelemetry.io/otel/attribute" +) + +// ErrorType returns an [attribute.KeyValue] identifying the error type of err. +func ErrorType(err error) attribute.KeyValue { + if err == nil { + return ErrorTypeOther + } + t := reflect.TypeOf(err) + var value string + if t.PkgPath() == "" && t.Name() == "" { + // Likely a builtin type. + value = t.String() + } else { + value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()) + } + + if value == "" { + return ErrorTypeOther + } + return ErrorTypeKey.String(value) +} diff --git a/internal/tools/semconvkit/templates/error_type_test.go.tmpl b/internal/tools/semconvkit/templates/error_type_test.go.tmpl new file mode 100644 index 000000000..c695b87e0 --- /dev/null +++ b/internal/tools/semconvkit/templates/error_type_test.go.tmpl @@ -0,0 +1,59 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package semconv // import "go.opentelemetry.io/otel/semconv/{{.TagVer}}" + +import ( + "errors" + "fmt" + "reflect" + "testing" + + "go.opentelemetry.io/otel/attribute" +) + +type CustomError struct{} + +func (CustomError) Error() string { + return "custom error" +} + +func TestErrorType(t *testing.T) { + customErr := CustomError{} + builtinErr := errors.New("something went wrong") + var nilErr error + + wantCustomType := reflect.TypeOf(customErr) + wantCustomStr := fmt.Sprintf("%s.%s", wantCustomType.PkgPath(), wantCustomType.Name()) + + tests := []struct { + name string + err error + want attribute.KeyValue + }{ + { + name: "BuiltinError", + err: builtinErr, + want: attribute.String("error.type", "*errors.errorString"), + }, + { + name: "CustomError", + err: customErr, + want: attribute.String("error.type", wantCustomStr), + }, + { + name: "NilError", + err: nilErr, + want: ErrorTypeOther, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ErrorType(tt.err) + if got != tt.want { + t.Errorf("ErrorType(%v) = %v, want %v", tt.err, got, tt.want) + } + }) + } +} diff --git a/semconv/v1.34.0/error_type.go b/semconv/v1.34.0/error_type.go new file mode 100644 index 000000000..19bf02246 --- /dev/null +++ b/semconv/v1.34.0/error_type.go @@ -0,0 +1,31 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package semconv // import "go.opentelemetry.io/otel/semconv/v1.34.0" + +import ( + "fmt" + "reflect" + + "go.opentelemetry.io/otel/attribute" +) + +// ErrorType returns an [attribute.KeyValue] identifying the error type of err. +func ErrorType(err error) attribute.KeyValue { + if err == nil { + return ErrorTypeOther + } + t := reflect.TypeOf(err) + var value string + if t.PkgPath() == "" && t.Name() == "" { + // Likely a builtin type. + value = t.String() + } else { + value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()) + } + + if value == "" { + return ErrorTypeOther + } + return ErrorTypeKey.String(value) +} diff --git a/semconv/v1.34.0/error_type_test.go b/semconv/v1.34.0/error_type_test.go new file mode 100644 index 000000000..744c29f77 --- /dev/null +++ b/semconv/v1.34.0/error_type_test.go @@ -0,0 +1,59 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package semconv // import "go.opentelemetry.io/otel/semconv/v1.34.0" + +import ( + "errors" + "fmt" + "reflect" + "testing" + + "go.opentelemetry.io/otel/attribute" +) + +type CustomError struct{} + +func (CustomError) Error() string { + return "custom error" +} + +func TestErrorType(t *testing.T) { + customErr := CustomError{} + builtinErr := errors.New("something went wrong") + var nilErr error + + wantCustomType := reflect.TypeOf(customErr) + wantCustomStr := fmt.Sprintf("%s.%s", wantCustomType.PkgPath(), wantCustomType.Name()) + + tests := []struct { + name string + err error + want attribute.KeyValue + }{ + { + name: "BuiltinError", + err: builtinErr, + want: attribute.String("error.type", "*errors.errorString"), + }, + { + name: "CustomError", + err: customErr, + want: attribute.String("error.type", wantCustomStr), + }, + { + name: "NilError", + err: nilErr, + want: ErrorTypeOther, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ErrorType(tt.err) + if got != tt.want { + t.Errorf("ErrorType(%v) = %v, want %v", tt.err, got, tt.want) + } + }) + } +}