Repository: getsentry/sentry-go

Issue:
Emit `transaction.data` inside `contexts.trace.data`
Missing here https://github.com/getsentry/sentry-go/blob/master/tracing.go#L760-L769

https://github.com/getsentry/team-sdks/issues/95

Discussion:

Solution patch:
diff --git a/tracing.go b/tracing.go
index 0c5877c..d0b7484 100644
--- a/tracing.go
+++ b/tracing.go
@@ -38,20 +38,22 @@ const (
 //
 // Spans must be started with either StartSpan or Span.StartChild.
 type Span struct { //nolint: maligned // prefer readability over optimal memory layout (see note below *)
-	TraceID      TraceID                `json:"trace_id"`
-	SpanID       SpanID                 `json:"span_id"`
-	ParentSpanID SpanID                 `json:"parent_span_id"`
-	Name         string                 `json:"name,omitempty"`
-	Op           string                 `json:"op,omitempty"`
-	Description  string                 `json:"description,omitempty"`
-	Status       SpanStatus             `json:"status,omitempty"`
-	Tags         map[string]string      `json:"tags,omitempty"`
-	StartTime    time.Time              `json:"start_timestamp"`
-	EndTime      time.Time              `json:"timestamp"`
-	Data         map[string]interface{} `json:"data,omitempty"`
-	Sampled      Sampled                `json:"-"`
-	Source       TransactionSource      `json:"-"`
-	Origin       SpanOrigin             `json:"origin,omitempty"`
+	TraceID      TraceID           `json:"trace_id"`
+	SpanID       SpanID            `json:"span_id"`
+	ParentSpanID SpanID            `json:"parent_span_id"`
+	Name         string            `json:"name,omitempty"`
+	Op           string            `json:"op,omitempty"`
+	Description  string            `json:"description,omitempty"`
+	Status       SpanStatus        `json:"status,omitempty"`
+	Tags         map[string]string `json:"tags,omitempty"`
+	StartTime    time.Time         `json:"start_timestamp"`
+	EndTime      time.Time         `json:"timestamp"`
+	// Deprecated: use Data instead. To be removed in 0.33.0
+	Extra   map[string]interface{} `json:"-"`
+	Data    map[string]interface{} `json:"data,omitempty"`
+	Sampled Sampled                `json:"-"`
+	Source  TransactionSource      `json:"-"`
+	Origin  SpanOrigin             `json:"origin,omitempty"`
 
 	// mu protects concurrent writes to map fields
 	mu sync.RWMutex
@@ -569,7 +571,6 @@ func (s *Span) toEvent() *Event {
 		Transaction: s.Name,
 		Contexts:    contexts,
 		Tags:        s.Tags,
-		Extra:       s.Data,
 		Timestamp:   s.EndTime,
 		StartTime:   s.StartTime,
 		Spans:       finished,
@@ -588,6 +589,7 @@ func (s *Span) traceContext() *TraceContext {
 		SpanID:       s.SpanID,
 		ParentSpanID: s.ParentSpanID,
 		Op:           s.Op,
+		Data:         s.Data,
 		Description:  s.Description,
 		Status:       s.Status,
 	}
@@ -763,12 +765,13 @@ func (ss SpanStatus) MarshalJSON() ([]byte, error) {
 // A TraceContext carries information about an ongoing trace and is meant to be
 // stored in Event.Contexts (as *TraceContext).
 type TraceContext struct {
-	TraceID      TraceID    `json:"trace_id"`
-	SpanID       SpanID     `json:"span_id"`
-	ParentSpanID SpanID     `json:"parent_span_id"`
-	Op           string     `json:"op,omitempty"`
-	Description  string     `json:"description,omitempty"`
-	Status       SpanStatus `json:"status,omitempty"`
+	TraceID      TraceID                `json:"trace_id"`
+	SpanID       SpanID                 `json:"span_id"`
+	ParentSpanID SpanID                 `json:"parent_span_id"`
+	Op           string                 `json:"op,omitempty"`
+	Description  string                 `json:"description,omitempty"`
+	Status       SpanStatus             `json:"status,omitempty"`
+	Data         map[string]interface{} `json:"data,omitempty"`
 }
 
 func (tc *TraceContext) MarshalJSON() ([]byte, error) {
@@ -811,6 +814,10 @@ func (tc TraceContext) Map() map[string]interface{} {
 		m["status"] = tc.Status
 	}
 
+	if len(tc.Data) > 0 {
+		m["data"] = tc.Data
+	}
+
 	return m
 }
 



Test patch:
diff --git a/echo/sentryecho_test.go b/echo/sentryecho_test.go
index 4eaa285..03a0605 100644
--- a/echo/sentryecho_test.go
+++ b/echo/sentryecho_test.go
@@ -65,7 +65,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": "GET", "http.response.status_code": http.StatusOK},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				},
 			},
 		},
 		{
@@ -88,8 +97,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": "GET", "http.response.status_code": 404},
-			},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusNotFound,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusNotFound,
+					}.Map(),
+				}},
 		},
 		{
 			RequestPath: "/post",
@@ -135,8 +152,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": "POST", "http.response.status_code": http.StatusOK},
-			},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				}},
 		},
 		{
 			RequestPath: "/get",
@@ -173,8 +198,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": "GET", "http.response.status_code": http.StatusOK},
-			},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				}},
 		},
 		{
 			RequestPath: "/post/large",
@@ -220,8 +253,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": "POST", "http.response.status_code": http.StatusOK},
-			},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				}},
 		},
 		{
 			RequestPath: "/post/body-ignored",
@@ -265,8 +306,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": "POST", "http.response.status_code": http.StatusOK},
-			},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				}},
 		},
 		{
 			RequestPath: "/badreq",
@@ -289,8 +338,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": "GET", "http.response.status_code": 400},
-			},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusBadRequest,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusInvalidArgument,
+					}.Map(),
+				}},
 			WantEvent: nil,
 		},
 	}
@@ -402,7 +459,7 @@ func TestIntegration(t *testing.T) {
 	optstrans := cmp.Options{
 		cmpopts.IgnoreFields(
 			sentry.Event{},
-			"Contexts", "EventID", "Platform", "Modules",
+			"EventID", "Platform", "Modules",
 			"Release", "Sdk", "ServerName", "Timestamp",
 			"sdkMetaData", "StartTime", "Spans",
 		),
@@ -410,6 +467,15 @@ func TestIntegration(t *testing.T) {
 			sentry.Request{},
 			"Env",
 		),
+		cmpopts.IgnoreMapEntries(func(k string, v any) bool {
+			ignoredCtxEntries := []string{"span_id", "trace_id", "device", "os", "runtime"}
+			for _, e := range ignoredCtxEntries {
+				if k == e {
+					return true
+				}
+			}
+			return false
+		}),
 	}
 	if diff := cmp.Diff(wantTrans, gott, optstrans); diff != "" {
 		t.Fatalf("Transaction mismatch (-want +got):\n%s", diff)
diff --git a/fasthttp/sentryfasthttp_test.go b/fasthttp/sentryfasthttp_test.go
index 7fdacc0..5600c42 100644
--- a/fasthttp/sentryfasthttp_test.go
+++ b/fasthttp/sentryfasthttp_test.go
@@ -64,7 +64,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": http.MethodGet, "http.response.status_code": http.StatusOK},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				},
 			},
 		},
 		{
@@ -103,7 +112,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": http.MethodPost, "http.response.status_code": http.StatusOK},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				},
 			},
 		},
 		{
@@ -138,7 +156,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": http.MethodGet, "http.response.status_code": http.StatusOK},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				},
 			},
 		},
 		{
@@ -178,7 +205,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": http.MethodPost, "http.response.status_code": http.StatusOK},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				},
 			},
 		},
 		{
@@ -219,7 +255,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": http.MethodPost, "http.response.status_code": http.StatusOK},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				},
 			},
 		},
 		{
@@ -262,7 +307,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": http.MethodPost, "http.response.status_code": http.StatusOK},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				},
 			},
 		},
 	}
@@ -370,16 +424,17 @@ func TestIntegration(t *testing.T) {
 
 	close(transactionsCh)
 	var gotTransactions []*sentry.Event
-	var statusCodes []sentry.SpanStatus
+	var gotCodes []sentry.SpanStatus
 
 	for e := range transactionsCh {
 		gotTransactions = append(gotTransactions, e)
-		statusCodes = append(statusCodes, e.Contexts["trace"]["status"].(sentry.SpanStatus))
+		gotCodes = append(gotCodes, e.Contexts["trace"]["status"].(sentry.SpanStatus))
 	}
+
 	optstrans := cmp.Options{
 		cmpopts.IgnoreFields(
 			sentry.Event{},
-			"Contexts", "EventID", "Platform", "Modules",
+			"EventID", "Platform", "Modules",
 			"Release", "Sdk", "ServerName", "Timestamp",
 			"sdkMetaData", "StartTime", "Spans",
 		),
@@ -401,12 +456,21 @@ func TestIntegration(t *testing.T) {
 			// ignore them.
 			return k == "Content-Length" || k == "Content-Type"
 		}),
+		cmpopts.IgnoreMapEntries(func(k string, v any) bool {
+			ignoredCtxEntries := []string{"span_id", "trace_id", "device", "os", "runtime"}
+			for _, e := range ignoredCtxEntries {
+				if k == e {
+					return true
+				}
+			}
+			return false
+		}),
 	}
 	if diff := cmp.Diff(wantTransactions, gotTransactions, optstrans); diff != "" {
 		t.Fatalf("Transactions mismatch (-want +gotEvents):\n%s", diff)
 	}
 
-	if diff := cmp.Diff(wantCodes, statusCodes, cmp.Options{}); diff != "" {
+	if diff := cmp.Diff(wantCodes, gotCodes, cmp.Options{}); diff != "" {
 		t.Fatalf("Transaction status codes mismatch (-want +got):\n%s", diff)
 	}
 
diff --git a/fiber/sentryfiber_test.go b/fiber/sentryfiber_test.go
index d135b12..842215e 100644
--- a/fiber/sentryfiber_test.go
+++ b/fiber/sentryfiber_test.go
@@ -1,12 +1,10 @@
 package sentryfiber_test
 
 import (
-	"encoding/json"
 	"errors"
 	"fmt"
 	"net/http"
 	"reflect"
-	"strconv"
 	"strings"
 	"testing"
 	"time"
@@ -71,18 +69,17 @@ func TestIntegration(t *testing.T) {
 	})
 
 	tests := []struct {
-		Path       string
-		Method     string
-		Body       string
-		WantStatus int
-
+		Path            string
+		Method          string
+		Body            string
+		WantStatus      int
 		WantEvent       *sentry.Event
 		WantTransaction *sentry.Event
 	}{
 		{
 			Path:       "/panic",
 			Method:     "GET",
-			WantStatus: http.StatusOK,
+			WantStatus: 200,
 			WantEvent: &sentry.Event{
 				Level:   sentry.LevelFatal,
 				Message: "test",
@@ -108,9 +105,15 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "url"},
-				Extra: map[string]any{
-					"http.request.method":       http.MethodGet,
-					"http.response.status_code": http.StatusOK,
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
 				},
 			},
 		},
@@ -118,7 +121,7 @@ func TestIntegration(t *testing.T) {
 			Path:       "/post",
 			Method:     "POST",
 			Body:       "payload",
-			WantStatus: http.StatusOK,
+			WantStatus: 200,
 			WantEvent: &sentry.Event{
 				Level:   sentry.LevelInfo,
 				Message: "post: payload",
@@ -148,16 +151,22 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "url"},
-				Extra: map[string]any{
-					"http.request.method":       http.MethodPost,
-					"http.response.status_code": http.StatusOK,
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
 				},
 			},
 		},
 		{
 			Path:       "/get",
 			Method:     "GET",
-			WantStatus: http.StatusOK,
+			WantStatus: 200,
 
 			WantEvent: &sentry.Event{
 				Level:   sentry.LevelInfo,
@@ -184,16 +193,22 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "url"},
-				Extra: map[string]any{
-					"http.request.method":       http.MethodGet,
-					"http.response.status_code": http.StatusOK,
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
 				},
 			},
 		},
 		{
 			Path:       "/get/123",
 			Method:     "GET",
-			WantStatus: http.StatusOK,
+			WantStatus: 200,
 			WantEvent: &sentry.Event{
 				Level:   sentry.LevelInfo,
 				Message: "get: 123",
@@ -219,16 +234,22 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "url"},
-				Extra: map[string]any{
-					"http.request.method":       http.MethodGet,
-					"http.response.status_code": http.StatusOK,
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
 				},
 			},
 		},
 		{
 			Path:       "/post/large",
 			Method:     "POST",
-			WantStatus: http.StatusOK,
+			WantStatus: 200,
 			Body:       largePayload,
 			WantEvent: &sentry.Event{
 				Level:   sentry.LevelInfo,
@@ -260,15 +281,21 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "url"},
-				Extra: map[string]any{
-					"http.request.method":       http.MethodPost,
-					"http.response.status_code": http.StatusOK,
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
 				},
 			},
 		},
 		{
 			Path:       "/post/body-ignored",
-			WantStatus: http.StatusOK,
+			WantStatus: 200,
 			Method:     "POST",
 			Body:       "client sends, fiber always reads, SDK reports",
 
@@ -301,16 +328,22 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "url"},
-				Extra: map[string]any{
-					"http.request.method":       http.MethodPost,
-					"http.response.status_code": http.StatusOK,
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
 				},
 			},
 		},
 		{
 			Path:       "/post/error-handler",
 			Method:     "POST",
-			WantStatus: http.StatusOK,
+			WantStatus: 200,
 
 			WantEvent: &sentry.Event{
 				Level: sentry.LevelError,
@@ -344,9 +377,15 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "url"},
-				Extra: map[string]any{
-					"http.request.method":       http.MethodPost,
-					"http.response.status_code": http.StatusOK,
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
 				},
 			},
 		},
@@ -354,7 +393,6 @@ func TestIntegration(t *testing.T) {
 
 	eventsCh := make(chan *sentry.Event, len(tests))
 	transactionsCh := make(chan *sentry.Event, len(tests))
-
 	err := sentry.Init(sentry.ClientOptions{
 		EnableTracing:    true,
 		TracesSampleRate: 1.0,
@@ -363,16 +401,7 @@ func TestIntegration(t *testing.T) {
 			return event
 		},
 		BeforeSendTransaction: func(tx *sentry.Event, hint *sentry.EventHint) *sentry.Event {
-			// Deep copy the transaction
-			jsonStr, err := json.Marshal(tx)
-			if err != nil {
-				t.Fatal(err)
-			}
-			var cpTx sentry.Event
-			if err := json.Unmarshal(jsonStr, &cpTx); err != nil {
-				t.Fatal(err)
-			}
-			transactionsCh <- &cpTx
+			transactionsCh <- tx
 			return tx
 		},
 	})
@@ -380,16 +409,13 @@ func TestIntegration(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	var wantEvents, gotEvents []*sentry.Event
-	var wantTransactions, gotTransactions []*sentry.Event
-	var wantCodes, gotCodes []sentry.SpanStatus
-
+	var wantEvents []*sentry.Event
+	var wantTransactions []*sentry.Event
+	var wantCodes []sentry.SpanStatus
 	for _, tt := range tests {
 		wantEvents = append(wantEvents, tt.WantEvent)
 		wantTransactions = append(wantTransactions, tt.WantTransaction)
-
 		wantCodes = append(wantCodes, sentry.HTTPtoSpanStatus(tt.WantStatus))
-
 		req, err := http.NewRequest(tt.Method, "http://example.com"+tt.Path, strings.NewReader(tt.Body))
 		if err != nil {
 			t.Fatal(err)
@@ -410,6 +436,7 @@ func TestIntegration(t *testing.T) {
 	}
 
 	close(eventsCh)
+	var gotEvents []*sentry.Event
 	for e := range eventsCh {
 		gotEvents = append(gotEvents, e)
 	}
@@ -435,24 +462,18 @@ func TestIntegration(t *testing.T) {
 	}
 
 	close(transactionsCh)
+	var gotTransactions []*sentry.Event
+	var gotCodes []sentry.SpanStatus
 
 	for e := range transactionsCh {
-		for k, v := range e.Extra {
-			if k != "http.response.status_code" {
-				continue
-			}
-			f, _ := v.(float64)
-			e.Extra[k] = int(f)
-		}
 		gotTransactions = append(gotTransactions, e)
-		gotCode, _ := strconv.Atoi(e.Contexts["trace"]["status"].(string))
-		gotCodes = append(gotCodes, sentry.HTTPtoSpanStatus(gotCode))
+		gotCodes = append(gotCodes, e.Contexts["trace"]["status"].(sentry.SpanStatus))
 	}
 
 	optstrans := cmp.Options{
 		cmpopts.IgnoreFields(
 			sentry.Event{},
-			"Contexts", "EventID", "Platform", "Modules",
+			"EventID", "Platform", "Modules",
 			"Release", "Sdk", "ServerName", "Timestamp",
 			"sdkMetaData", "StartTime", "Spans",
 		),
@@ -464,6 +485,15 @@ func TestIntegration(t *testing.T) {
 			sentry.Exception{},
 			"Stacktrace",
 		),
+		cmpopts.IgnoreMapEntries(func(k string, v any) bool {
+			ignoredCtxEntries := []string{"span_id", "trace_id", "device", "os", "runtime"}
+			for _, e := range ignoredCtxEntries {
+				if k == e {
+					return true
+				}
+			}
+			return false
+		}),
 	}
 
 	if diff := cmp.Diff(wantTransactions, gotTransactions, optstrans); diff != "" {
diff --git a/gin/sentrygin_test.go b/gin/sentrygin_test.go
index 3b33bc6..01dbdff 100644
--- a/gin/sentrygin_test.go
+++ b/gin/sentrygin_test.go
@@ -54,8 +54,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": string("GET"), "http.response.status_code": http.StatusOK},
-			},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				}},
 			WantEvent: &sentry.Event{
 				Level:   sentry.LevelFatal,
 				Message: "test",
@@ -88,7 +96,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "url"},
-				Extra:           map[string]any{"http.request.method": string("GET"), "http.response.status_code": 404},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusNotFound,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusNotFound,
+					}.Map(),
+				},
 			},
 			WantEvent: nil,
 		},
@@ -122,8 +139,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": string("POST"), "http.response.status_code": http.StatusOK},
-			},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				}},
 			WantEvent: &sentry.Event{
 				Level:   sentry.LevelInfo,
 				Message: "post: payload",
@@ -162,8 +187,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": string("GET"), "http.response.status_code": http.StatusOK},
-			},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				}},
 			WantEvent: &sentry.Event{
 				Level:   sentry.LevelInfo,
 				Message: "get",
@@ -205,8 +238,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": string("POST"), "http.response.status_code": http.StatusOK},
-			},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				}},
 			WantEvent: &sentry.Event{
 				Level:   sentry.LevelInfo,
 				Message: "post: 15 KB",
@@ -249,8 +290,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": string("POST"), "http.response.status_code": http.StatusOK},
-			},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				}},
 			WantEvent: &sentry.Event{
 				Level:   sentry.LevelInfo,
 				Message: "body ignored",
@@ -288,8 +337,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra:           map[string]any{"http.request.method": string("GET"), "http.response.status_code": 400},
-			},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusBadRequest,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusInvalidArgument,
+					}.Map(),
+				}},
 			WantEvent: nil,
 		},
 	}
@@ -394,7 +451,7 @@ func TestIntegration(t *testing.T) {
 	optstrans := cmp.Options{
 		cmpopts.IgnoreFields(
 			sentry.Event{},
-			"Contexts", "EventID", "Platform", "Modules",
+			"EventID", "Platform", "Modules",
 			"Release", "Sdk", "ServerName", "Timestamp",
 			"sdkMetaData", "StartTime", "Spans",
 		),
@@ -402,6 +459,15 @@ func TestIntegration(t *testing.T) {
 			sentry.Request{},
 			"Env",
 		),
+		cmpopts.IgnoreMapEntries(func(k string, v any) bool {
+			ignoredCtxEntries := []string{"span_id", "trace_id", "device", "os", "runtime"}
+			for _, e := range ignoredCtxEntries {
+				if k == e {
+					return true
+				}
+			}
+			return false
+		}),
 	}
 	if diff := cmp.Diff(wanttrans, gott, optstrans); diff != "" {
 		t.Fatalf("Transaction mismatch (-want +got):\n%s", diff)
diff --git a/http/sentryhttp_test.go b/http/sentryhttp_test.go
index 7d2e731..6fcaa47 100644
--- a/http/sentryhttp_test.go
+++ b/http/sentryhttp_test.go
@@ -61,7 +61,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "url"},
-				Extra:           map[string]any{"http.request.method": http.MethodGet, "http.response.status_code": http.StatusOK},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				},
 			},
 		},
 		{
@@ -107,7 +116,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "url"},
-				Extra:           map[string]any{"http.request.method": http.MethodPost, "http.response.status_code": http.StatusOK},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				},
 			},
 		},
 		{
@@ -143,7 +161,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "url"},
-				Extra:           map[string]any{"http.request.method": http.MethodGet, "http.response.status_code": http.StatusOK},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				},
 			},
 		},
 		{
@@ -191,7 +218,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "url"},
-				Extra:           map[string]any{"http.request.method": http.MethodPost, "http.response.status_code": http.StatusOK},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				},
 			},
 		},
 		{
@@ -235,8 +271,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "url"},
-				Extra:           map[string]any{"http.request.method": http.MethodPost, "http.response.status_code": http.StatusOK},
-			},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				}},
 		},
 	}
 
@@ -339,14 +383,19 @@ func TestIntegration(t *testing.T) {
 	optstrans := cmp.Options{
 		cmpopts.IgnoreFields(
 			sentry.Event{},
-			"Contexts", "EventID", "Platform", "Modules",
+			"EventID", "Platform", "Modules",
 			"Release", "Sdk", "ServerName", "Timestamp",
 			"sdkMetaData", "StartTime", "Spans",
 		),
-		cmpopts.IgnoreFields(
-			sentry.Request{},
-			"Env",
-		),
+		cmpopts.IgnoreMapEntries(func(k string, _ any) bool {
+			ignoredCtxEntries := []string{"span_id", "trace_id", "device", "os", "runtime"}
+			for _, e := range ignoredCtxEntries {
+				if k == e {
+					return true
+				}
+			}
+			return false
+		}),
 	}
 	if diff := cmp.Diff(wantTrans, gott, optstrans); diff != "" {
 		t.Fatalf("Transaction mismatch (-want +got):\n%s", diff)
diff --git a/iris/sentryiris_test.go b/iris/sentryiris_test.go
index b1b3595..313f154 100644
--- a/iris/sentryiris_test.go
+++ b/iris/sentryiris_test.go
@@ -53,9 +53,15 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra: map[string]interface{}{
-					"http.request.method":       string("GET"),
-					"http.response.status_code": http.StatusOK,
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
 				},
 			},
 			WantEvent: &sentry.Event{
@@ -126,9 +132,15 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra: map[string]interface{}{
-					"http.request.method":       string("POST"),
-					"http.response.status_code": http.StatusOK,
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
 				},
 			},
 			WantEvent: &sentry.Event{
@@ -170,9 +182,15 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra: map[string]interface{}{
-					"http.request.method":       string("GET"),
-					"http.response.status_code": http.StatusOK,
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
 				},
 			},
 			WantEvent: &sentry.Event{
@@ -216,9 +234,15 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra: map[string]interface{}{
-					"http.request.method":       string("POST"),
-					"http.response.status_code": http.StatusOK,
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
 				},
 			},
 			WantEvent: &sentry.Event{
@@ -263,9 +287,15 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra: map[string]interface{}{
-					"http.request.method":       string("POST"),
-					"http.response.status_code": http.StatusOK,
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
 				},
 			},
 			WantEvent: &sentry.Event{
@@ -306,9 +336,15 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "route"},
-				Extra: map[string]interface{}{
-					"http.request.method":       string("GET"),
-					"http.response.status_code": http.StatusBadRequest,
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusBadRequest,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusInvalidArgument,
+					}.Map(),
 				},
 			},
 			WantEvent: nil,
@@ -415,7 +451,7 @@ func TestIntegration(t *testing.T) {
 	optstrans := cmp.Options{
 		cmpopts.IgnoreFields(
 			sentry.Event{},
-			"Contexts", "EventID", "Platform", "Modules",
+			"EventID", "Platform", "Modules",
 			"Release", "Sdk", "ServerName", "Timestamp",
 			"sdkMetaData", "StartTime", "Spans",
 		),
@@ -423,6 +459,15 @@ func TestIntegration(t *testing.T) {
 			sentry.Request{},
 			"Env",
 		),
+		cmpopts.IgnoreMapEntries(func(k string, v any) bool {
+			ignoredCtxEntries := []string{"span_id", "trace_id", "device", "os", "runtime"}
+			for _, e := range ignoredCtxEntries {
+				if k == e {
+					return true
+				}
+			}
+			return false
+		}),
 	}
 	if diff := cmp.Diff(wanttrans, gott, optstrans); diff != "" {
 		t.Fatalf("Transaction mismatch (-want +got):\n%s", diff)
diff --git a/negroni/sentrynegroni_test.go b/negroni/sentrynegroni_test.go
index 2336d90..339e4b5 100644
--- a/negroni/sentrynegroni_test.go
+++ b/negroni/sentrynegroni_test.go
@@ -62,7 +62,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "url"},
-				Extra:           map[string]any{"http.request.method": http.MethodGet, "http.response.status_code": http.StatusOK},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				},
 			},
 		},
 		{
@@ -108,8 +117,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "url"},
-				Extra:           map[string]any{"http.request.method": http.MethodPost, "http.response.status_code": http.StatusOK},
-			},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				}},
 		},
 		{
 			Path: "/get",
@@ -144,8 +161,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "url"},
-				Extra:           map[string]any{"http.request.method": http.MethodGet, "http.response.status_code": http.StatusOK},
-			},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodGet,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				}},
 		},
 		{
 			Path:   "/post/large",
@@ -192,8 +217,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "url"},
-				Extra:           map[string]any{"http.request.method": http.MethodPost, "http.response.status_code": http.StatusOK},
-			},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				}},
 		},
 		{
 			Path:   "/post/body-ignored",
@@ -236,8 +269,16 @@ func TestIntegration(t *testing.T) {
 					},
 				},
 				TransactionInfo: &sentry.TransactionInfo{Source: "url"},
-				Extra:           map[string]any{"http.request.method": http.MethodPost, "http.response.status_code": http.StatusOK},
-			},
+				Contexts: map[string]sentry.Context{
+					"trace": sentry.TraceContext{
+						Data: map[string]interface{}{
+							"http.request.method":       http.MethodPost,
+							"http.response.status_code": http.StatusOK,
+						},
+						Op:     "http.server",
+						Status: sentry.SpanStatusOK,
+					}.Map(),
+				}},
 		},
 	}
 
@@ -344,7 +385,7 @@ func TestIntegration(t *testing.T) {
 	optstrans := cmp.Options{
 		cmpopts.IgnoreFields(
 			sentry.Event{},
-			"Contexts", "EventID", "Platform", "Modules",
+			"EventID", "Platform", "Modules",
 			"Release", "Sdk", "ServerName", "Timestamp",
 			"sdkMetaData", "StartTime", "Spans",
 		),
@@ -352,6 +393,15 @@ func TestIntegration(t *testing.T) {
 			sentry.Request{},
 			"Env",
 		),
+		cmpopts.IgnoreMapEntries(func(k string, v any) bool {
+			ignoredCtxEntries := []string{"span_id", "trace_id", "device", "os", "runtime"}
+			for _, e := range ignoredCtxEntries {
+				if k == e {
+					return true
+				}
+			}
+			return false
+		}),
 	}
 	if diff := cmp.Diff(wantTrans, gott, optstrans); diff != "" {
 		t.Fatalf("Transaction mismatch (-want +got):\n%s", diff)
diff --git a/tracing_test.go b/tracing_test.go
index 775423f..b3dd9fb 100644
--- a/tracing_test.go
+++ b/tracing_test.go
@@ -138,15 +138,12 @@ func TestStartSpan(t *testing.T) {
 				SpanID:       span.SpanID,
 				ParentSpanID: parentSpanID,
 				Op:           op,
+				Data:         span.Data,
 				Description:  description,
 				Status:       status,
 			}.Map(),
 		},
-		Tags: nil,
-		// TODO(tracing): the root span / transaction data field is
-		// mapped into Event.Extra for now, pending spec clarification.
-		// https://github.com/getsentry/develop/issues/244#issuecomment-778694182
-		Extra:     span.Data,
+		Tags:      nil,
 		Timestamp: endTime,
 		StartTime: startTime,
 		TransactionInfo: &TransactionInfo{
@@ -283,16 +280,13 @@ func TestStartTransaction(t *testing.T) {
 			"trace": TraceContext{
 				TraceID:     transaction.TraceID,
 				SpanID:      transaction.SpanID,
+				Data:        transaction.Data,
 				Description: description,
 				Status:      status,
 			}.Map(),
 			"otel": {"k": "v"},
 		},
-		Tags: nil,
-		// TODO(tracing): the root span / transaction data field is
-		// mapped into Event.Extra for now, pending spec clarification.
-		// https://github.com/getsentry/develop/issues/244#issuecomment-778694182
-		Extra:     transaction.Data,
+		Tags:      nil,
 		Timestamp: endTime,
 		StartTime: startTime,
 		TransactionInfo: &TransactionInfo{