Repository: getsentry/sentry-go

Issue:
OTel SDK casts all span `data` values to string
## Summary

Something in the interaction between Go and OTel is casting all span `data` values to strings, even well-known measurement fields like `http.response_content_length`

## Steps To Reproduce

I found this data in the wild, so I don't have actual repro steps, sorry! I can provide more details if you contact me on Slack

## Expected Behavior
`data.http.response_content_length` is an integer

## Screenshots
<img width="607" alt="Screenshot 2024-01-08 at 4 35 49 PM" src="https://github.com/getsentry/sentry-go/assets/989898/2cd8f683-8e3d-4316-9c4c-202433a477cd">

## Environment
<!-- Please complete the following information. -->

### SDK
 - `sentry-go` version: `0.25.0`
 - `opentelemetry` version: `1.18.0`

### Sentry
 - Using hosted Sentry in sentry.io? yes

Discussion:
@AbhiPrasad: We are using https://pkg.go.dev/go.opentelemetry.io/otel@v1.21.0/attribute#Value.Emit - this forces everything to be strings https://github.com/getsentry/sentry-go/blob/fff66d18d3019c0c75c88edb26f8d79d52d8a126/otel/span_processor.go#L129.

We should remove the casting.

See https://opentelemetry.io/docs/concepts/signals/traces/#attributes

> Values must be a non-null string, boolean, floating point value, integer, or an array of these values@AbhiPrasad: cc @cleptric

Solution patch:
diff --git a/otel/span_processor.go b/otel/span_processor.go
index d32e2b5..2f59c0a 100644
--- a/otel/span_processor.go
+++ b/otel/span_processor.go
@@ -122,14 +122,14 @@ func getTraceParentContext(ctx context.Context) sentry.TraceParentContext {
 
 func updateTransactionWithOtelData(transaction *sentry.Span, s otelSdkTrace.ReadOnlySpan) {
 	// TODO(michi) This is crazy inefficient
-	attributes := map[attribute.Key]string{}
-	resource := map[attribute.Key]string{}
+	attributes := map[attribute.Key]interface{}{}
+	resource := map[attribute.Key]interface{}{}
 
 	for _, kv := range s.Attributes() {
-		attributes[kv.Key] = kv.Value.Emit()
+		attributes[kv.Key] = kv.Value.AsInterface()
 	}
 	for _, kv := range s.Resource().Attributes() {
-		resource[kv.Key] = kv.Value.Emit()
+		resource[kv.Key] = kv.Value.AsInterface()
 	}
 
 	transaction.SetContext("otel", map[string]interface{}{
@@ -153,6 +153,6 @@ func updateSpanWithOtelData(span *sentry.Span, s otelSdkTrace.ReadOnlySpan) {
 	span.Description = spanAttributes.Description
 	span.SetData("otel.kind", s.SpanKind().String())
 	for _, kv := range s.Attributes() {
-		span.SetData(string(kv.Key), kv.Value.Emit())
+		span.SetData(string(kv.Key), kv.Value.AsInterface())
 	}
 }
diff --git a/tracing.go b/tracing.go
index 4d51c0d..0dce162 100644
--- a/tracing.go
+++ b/tracing.go
@@ -226,7 +226,11 @@ func (s *Span) SetTag(name, value string) {
 // SetData sets a data on the span. It is recommended to use SetData instead of
 // accessing the data map directly as SetData takes care of initializing the map
 // when necessary.
-func (s *Span) SetData(name, value string) {
+func (s *Span) SetData(name string, value interface{}) {
+	if value == nil {
+		return
+	}
+
 	s.mu.Lock()
 	defer s.mu.Unlock()
 



Test patch:
diff --git a/otel/span_processor_test.go b/otel/span_processor_test.go
index 3154b4e..6dbda27 100644
--- a/otel/span_processor_test.go
+++ b/otel/span_processor_test.go
@@ -257,10 +257,10 @@ func TestOnEndWithTransaction(t *testing.T) {
 		t,
 		otelContextGot,
 		map[string]interface{}{
-			"attributes": map[attribute.Key]string{
+			"attributes": map[attribute.Key]interface{}{
 				"key1": "value1",
 			},
-			"resource": map[attribute.Key]string{
+			"resource": map[attribute.Key]interface{}{
 				"service.name": "test-otel",
 			},
 		},
diff --git a/tracing_test.go b/tracing_test.go
index 5055136..1addd83 100644
--- a/tracing_test.go
+++ b/tracing_test.go
@@ -334,8 +334,11 @@ func TestSetData(t *testing.T) {
 	})
 	span := StartSpan(ctx, "Test Span")
 	span.SetData("key", "value")
-
-	if (span.Data == nil) || (span.Data["key"] != "value") {
+	span.SetData("key.nil", nil)
+	span.SetData("key.number", 123)
+	span.SetData("key.bool", true)
+	span.SetData("key.slice", []string{"foo", "bar"})
+	if (span.Data == nil) || (span.Data["key"] != "value") || (span.Data["key.number"] != 123) || (span.Data["key.bool"] != true) || !reflect.DeepEqual(span.Data["key.slice"], []string{"foo", "bar"}) {
 		t.Fatalf("Data mismatch, got %v", span.Data)
 	}
 }