Repository: open-telemetry/opentelemetry-go (2025-07-03T15:35:04Z)

Issue:
semconv: Add ErrorType function
### Problem Statement

There is no easy way to set [error.type](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/registry/attributes/error.md#error-type) (stable) attribute.

### Proposed Solution


We can add a helper method to `semconv` package:

```go
func ErrorType(err error) attribute.KeyValue {
	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)
}
```

### Additional Context

Related to:
- https://github.com/open-telemetry/opentelemetry-go-contrib/issues/7470




Discussion:
Hi. I would like to work on this. Please, assign me if it is feasible
@Bakhtiyar-Garashov, sure.

Just please notice that the `semconv` packages are generated so you would need to add the code here https://github.com/open-telemetry/opentelemetry-go/tree/main/internal/tools/semconvkit/templates and then regenerate `semconv/v1.34.0`.
This was my first ever feature implementation. Happy to contribute even more in the future 🎉 

Thanks all who helped me within the PR



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9be9fa06d99..7a1e084d5b9 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 00000000000..185cf5810eb
--- /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/semconv/v1.34.0/error_type.go b/semconv/v1.34.0/error_type.go
new file mode 100644
index 00000000000..19bf022465f
--- /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)
+}


Test patch:
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 00000000000..c695b87e032
--- /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_test.go b/semconv/v1.34.0/error_type_test.go
new file mode 100644
index 00000000000..744c29f77c5
--- /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)
+			}
+		})
+	}
+}