diff --git a/dynamic_sampling_context.go b/dynamic_sampling_context.go index 1eba2a28..6eec95ba 100644 --- a/dynamic_sampling_context.go +++ b/dynamic_sampling_context.go @@ -39,8 +39,6 @@ func DynamicSamplingContextFromHeader(header []byte) (DynamicSamplingContext, er } func DynamicSamplingContextFromTransaction(span *Span) DynamicSamplingContext { - entries := map[string]string{} - hub := hubFromContext(span.Context()) scope := hub.Scope() client := hub.Client() @@ -52,6 +50,8 @@ func DynamicSamplingContextFromTransaction(span *Span) DynamicSamplingContext { } } + entries := make(map[string]string) + if traceID := span.TraceID.String(); traceID != "" { entries["trace_id"] = traceID } @@ -80,10 +80,7 @@ func DynamicSamplingContextFromTransaction(span *Span) DynamicSamplingContext { entries["sampled"] = strconv.FormatBool(span.Sampled.Bool()) - return DynamicSamplingContext{ - Entries: entries, - Frozen: true, - } + return DynamicSamplingContext{Entries: entries, Frozen: true} } func (d DynamicSamplingContext) HasEntries() bool { diff --git a/echo/sentryecho.go b/echo/sentryecho.go index 26972d9f..f8226fb4 100644 --- a/echo/sentryecho.go +++ b/echo/sentryecho.go @@ -50,7 +50,7 @@ func New(options Options) echo.MiddlewareFunc { func (h *handler) handle(next echo.HandlerFunc) echo.HandlerFunc { return func(ctx echo.Context) error { - hub := sentry.GetHubFromContext(ctx.Request().Context()) + hub := GetHubFromContext(ctx) if hub == nil { hub = sentry.CurrentHub().Clone() } @@ -70,8 +70,8 @@ func (h *handler) handle(next echo.HandlerFunc) echo.HandlerFunc { } options := []sentry.SpanOption{ + sentry.ContinueTrace(hub, r.Header.Get(sentry.SentryTraceHeader), r.Header.Get(sentry.SentryBaggageHeader)), sentry.WithOpName("http.server"), - sentry.ContinueFromRequest(r), sentry.WithTransactionSource(transactionSource), sentry.WithSpanOrigin(sentry.SpanOriginEcho), } @@ -82,7 +82,7 @@ func (h *handler) handle(next echo.HandlerFunc) echo.HandlerFunc { options..., ) - transaction.SetData("http.request.method", ctx.Request().Method) + transaction.SetData("http.request.method", r.Method) defer func() { status := ctx.Response().Status diff --git a/fasthttp/sentryfasthttp.go b/fasthttp/sentryfasthttp.go index f8a5bf5c..bd8cc626 100644 --- a/fasthttp/sentryfasthttp.go +++ b/fasthttp/sentryfasthttp.go @@ -58,17 +58,20 @@ func New(options Options) *Handler { // Handle wraps fasthttp.RequestHandler and recovers from caught panics. func (h *Handler) Handle(handler fasthttp.RequestHandler) fasthttp.RequestHandler { return func(ctx *fasthttp.RequestCtx) { - hub := sentry.CurrentHub().Clone() + hub := GetHubFromContext(ctx) + if hub == nil { + hub = sentry.CurrentHub().Clone() + } if client := hub.Client(); client != nil { client.SetSDKIdentifier(sdkIdentifier) } - convertedHTTPRequest := convert(ctx) + r := convert(ctx) options := []sentry.SpanOption{ + sentry.ContinueTrace(hub, r.Header.Get(sentry.SentryTraceHeader), r.Header.Get(sentry.SentryBaggageHeader)), sentry.WithOpName("http.server"), - sentry.ContinueFromRequest(convertedHTTPRequest), sentry.WithTransactionSource(sentry.SourceRoute), sentry.WithSpanOrigin(sentry.SpanOriginFastHTTP), } @@ -90,11 +93,12 @@ func (h *Handler) Handle(handler fasthttp.RequestHandler) fasthttp.RequestHandle transaction.SetData("http.request.method", method) scope := hub.Scope() - scope.SetRequest(convertedHTTPRequest) + scope.SetRequest(r) scope.SetRequestBody(ctx.Request.Body()) ctx.SetUserValue(valuesKey, hub) ctx.SetUserValue(transactionKey, transaction) defer h.recoverWithSentry(hub, ctx) + handler(ctx) } } diff --git a/fiber/sentryfiber.go b/fiber/sentryfiber.go index 604c5cf1..3586e192 100644 --- a/fiber/sentryfiber.go +++ b/fiber/sentryfiber.go @@ -54,13 +54,16 @@ func New(options Options) fiber.Handler { } func (h *handler) handle(ctx *fiber.Ctx) error { - hub := sentry.CurrentHub().Clone() + hub := GetHubFromContext(ctx) + if hub == nil { + hub = sentry.CurrentHub().Clone() + } if client := hub.Client(); client != nil { client.SetSDKIdentifier(sdkIdentifier) } - convertedHTTPRequest := convert(ctx) + r := convert(ctx) method := ctx.Method() @@ -68,8 +71,8 @@ func (h *handler) handle(ctx *fiber.Ctx) error { transactionSource := sentry.SourceURL options := []sentry.SpanOption{ + sentry.ContinueTrace(hub, r.Header.Get(sentry.SentryTraceHeader), r.Header.Get(sentry.SentryBaggageHeader)), sentry.WithOpName("http.server"), - sentry.ContinueFromRequest(convertedHTTPRequest), sentry.WithTransactionSource(transactionSource), sentry.WithSpanOrigin(sentry.SpanOriginFiber), } @@ -90,11 +93,12 @@ func (h *handler) handle(ctx *fiber.Ctx) error { transaction.SetData("http.request.method", method) scope := hub.Scope() - scope.SetRequest(convertedHTTPRequest) + scope.SetRequest(r) scope.SetRequestBody(ctx.Request().Body()) ctx.Locals(valuesKey, hub) ctx.Locals(transactionKey, transaction) defer h.recoverWithSentry(hub, ctx) + return ctx.Next() } diff --git a/gin/sentrygin.go b/gin/sentrygin.go index 1ccc1d1e..1b658d35 100644 --- a/gin/sentrygin.go +++ b/gin/sentrygin.go @@ -17,6 +17,7 @@ import ( const sdkIdentifier = "sentry.go.gin" const valuesKey = "sentry" +const transactionKey = "sentry_transaction" type handler struct { repanic bool @@ -55,7 +56,6 @@ func (h *handler) handle(c *gin.Context) { hub := sentry.GetHubFromContext(ctx) if hub == nil { hub = sentry.CurrentHub().Clone() - ctx = sentry.SetHubOnContext(ctx, hub) } if client := hub.Client(); client != nil { @@ -71,17 +71,20 @@ func (h *handler) handle(c *gin.Context) { } options := []sentry.SpanOption{ + sentry.ContinueTrace(hub, c.GetHeader(sentry.SentryTraceHeader), c.GetHeader(sentry.SentryBaggageHeader)), sentry.WithOpName("http.server"), - sentry.ContinueFromRequest(c.Request), sentry.WithTransactionSource(transactionSource), sentry.WithSpanOrigin(sentry.SpanOriginGin), } - transaction := sentry.StartTransaction(ctx, + transaction := sentry.StartTransaction( + sentry.SetHubOnContext(ctx, hub), fmt.Sprintf("%s %s", c.Request.Method, transactionName), options..., ) + transaction.SetData("http.request.method", c.Request.Method) + defer func() { status := c.Writer.Status() transaction.Status = sentry.HTTPtoSpanStatus(status) @@ -92,7 +95,9 @@ func (h *handler) handle(c *gin.Context) { c.Request = c.Request.WithContext(transaction.Context()) hub.Scope().SetRequest(c.Request) c.Set(valuesKey, hub) + c.Set(transactionKey, transaction) defer h.recoverWithSentry(hub, c.Request) + c.Next() } @@ -135,3 +140,14 @@ func GetHubFromContext(ctx *gin.Context) *sentry.Hub { } return nil } + +// GetSpanFromContext retrieves attached *sentry.Span instance from gin.Context. +// If there is no transaction on echo.Context, it will return nil. +func GetSpanFromContext(ctx *gin.Context) *sentry.Span { + if span, ok := ctx.Get(transactionKey); ok { + if span, ok := span.(*sentry.Span); ok { + return span + } + } + return nil +} diff --git a/http/sentryhttp.go b/http/sentryhttp.go index f90bc261..62549e5e 100644 --- a/http/sentryhttp.go +++ b/http/sentryhttp.go @@ -84,7 +84,7 @@ func (h *Handler) HandleFunc(handler http.HandlerFunc) http.HandlerFunc { func (h *Handler) handle(handler http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - hub := sentry.GetHubFromContext(ctx) + hub := sentry.GetHubFromContext(r.Context()) if hub == nil { hub = sentry.CurrentHub().Clone() ctx = sentry.SetHubOnContext(ctx, hub) @@ -95,8 +95,8 @@ func (h *Handler) handle(handler http.Handler) http.HandlerFunc { } options := []sentry.SpanOption{ + sentry.ContinueTrace(hub, r.Header.Get(sentry.SentryTraceHeader), r.Header.Get(sentry.SentryBaggageHeader)), sentry.WithOpName("http.server"), - sentry.ContinueFromRequest(r), sentry.WithTransactionSource(sentry.SourceURL), sentry.WithSpanOrigin(sentry.SpanOriginStdLib), } @@ -116,13 +116,10 @@ func (h *Handler) handle(handler http.Handler) http.HandlerFunc { transaction.Finish() }() - // TODO(tracing): if the next handler.ServeHTTP panics, store - // information on the transaction accordingly (status, tag, - // level?, ...). - r = r.WithContext(transaction.Context()) hub.Scope().SetRequest(r) - + r = r.WithContext(transaction.Context()) defer h.recoverWithSentry(hub, r) + handler.ServeHTTP(rw, r) } } diff --git a/hub.go b/hub.go index 5c9fc800..c99b6d70 100644 --- a/hub.go +++ b/hub.go @@ -2,6 +2,7 @@ package sentry import ( "context" + "fmt" "sync" "time" ) @@ -365,25 +366,32 @@ func (hub *Hub) Flush(timeout time.Duration) bool { return client.Flush(timeout) } -// Continue a trace based on HTTP header values. If performance is enabled this -// returns a SpanOption that can be used to start a transaction, otherwise nil. -func (hub *Hub) ContinueTrace(trace, baggage string) (SpanOption, error) { +// GetTraceparent returns the current Sentry traceparent string, to be used as a HTTP header value +// or HTML meta tag value. +// This function is context aware, as in it either returns the traceparent based +// on the current span, or the scope's propagation context. +func (hub *Hub) GetTraceparent() string { scope := hub.Scope() - propagationContext, err := PropagationContextFromHeaders(trace, baggage) - if err != nil { - return nil, err + + if scope.span != nil { + return scope.span.ToSentryTrace() } - scope.SetPropagationContext(propagationContext) + return fmt.Sprintf("%s-%s", scope.propagationContext.TraceID, scope.propagationContext.SpanID) +} - client := hub.Client() - if client != nil && client.options.EnableTracing { - return ContinueFromHeaders(trace, baggage), nil - } +// GetBaggage returns the current Sentry baggage string, to be used as a HTTP header value +// or HTML meta tag value. +// This function is context aware, as in it either returns the baggage based +// on the current span or the scope's propagation context. +func (hub *Hub) GetBaggage() string { + scope := hub.Scope() - scope.SetContext("trace", propagationContext.Map()) + if scope.span != nil { + return scope.span.ToBaggage() + } - return nil, nil + return scope.propagationContext.DynamicSamplingContext.String() } // HasHubOnContext checks whether Hub instance is bound to a given Context struct. diff --git a/hub_test.go b/hub_test.go index 79ea2671..0b306a0a 100644 --- a/hub_test.go +++ b/hub_test.go @@ -2,14 +2,13 @@ package sentry import ( "context" - "errors" "fmt" + "strings" "sync" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/stretchr/testify/assert" ) const testDsn = "http://whatever@example.com/1337" @@ -326,84 +325,83 @@ func TestHasHubOnContextReturnsFalseIfHubIsNotThere(t *testing.T) { assertEqual(t, false, HasHubOnContext(ctx)) } -func TestHub_ContinueTrace(t *testing.T) { - newScope := func() *Scope { - return &Scope{contexts: make(map[string]Context)} +func TestGetTraceparent(t *testing.T) { + tests := map[string]struct { + hub *Hub + expected string + }{ + "With span": { + hub: func() *Hub { + h, _, s := setupHubTest() + s.span = &Span{ + TraceID: TraceIDFromHex("d49d9bf66f13450b81f65bc51cf49c03"), + SpanID: SpanIDFromHex("a9f442f9330b4e09"), + Sampled: SampledTrue, + } + return h + }(), + expected: "d49d9bf66f13450b81f65bc51cf49c03-a9f442f9330b4e09-1", + }, + "Without span": { + hub: func() *Hub { + h, _, s := setupHubTest() + s.propagationContext.TraceID = TraceIDFromHex("d49d9bf66f13450b81f65bc51cf49c03") + s.propagationContext.SpanID = SpanIDFromHex("a9f442f9330b4e09") + return h + }(), + expected: "d49d9bf66f13450b81f65bc51cf49c03-a9f442f9330b4e09", + }, } - mockClient := &Client{options: ClientOptions{EnableTracing: true}} + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + result := tt.hub.GetTraceparent() + assertEqual(t, result, tt.expected) + }) + } +} +func TestGetBaggage(t *testing.T) { tests := map[string]struct { - hub *Hub - trace string - baggage string - expectedErr error - expectedSpan bool // Whether a SpanOption is expected to be returned - checkScope func(t *testing.T, scope *Scope) // Additional checks on the scope + hub *Hub + expected string }{ - "Valid trace and baggage": { - hub: NewHub(mockClient, newScope()), - trace: "4fbfb1b884c8532962a3c0b7b834428e-a9f442f9330b4e09", - baggage: "sentry-release=1.0.0,sentry-environment=production", - expectedErr: nil, - expectedSpan: true, - checkScope: func(t *testing.T, scope *Scope) { - assert.Equal(t, "4fbfb1b884c8532962a3c0b7b834428e", scope.propagationContext.TraceID.String()) - }, - }, - "Invalid trace": { - hub: NewHub(mockClient, newScope()), - trace: "invalid", - baggage: "sentry-release=1.0.0,sentry-environment=production", - expectedErr: nil, - expectedSpan: true, - checkScope: func(t *testing.T, scope *Scope) { - assert.NotEmpty(t, scope.propagationContext.TraceID.String()) - }, + "With span": { + hub: func() *Hub { + h, _, s := setupHubTest() + s.span = &Span{ + dynamicSamplingContext: DynamicSamplingContext{ + Entries: map[string]string{"sample_rate": "1", "release": "1.0.0", "environment": "production"}, + }, + recorder: &spanRecorder{}, + ctx: context.Background(), + Sampled: SampledTrue, + } + + s.span.spanRecorder().record(s.span) + + return h + }(), + expected: "sentry-environment=production,sentry-release=1.0.0,sentry-sample_rate=1", }, - "Invalid baggage": { - hub: NewHub(mockClient, newScope()), - trace: "4fbfb1b884c8532962a3c0b7b834428e-a9f442f9330b4e09", - baggage: "invalid_baggage", - expectedErr: errors.New("invalid baggage list-member: \"invalid_baggage\""), - expectedSpan: false, - checkScope: func(t *testing.T, scope *Scope) { - assert.Equal(t, "00000000000000000000000000000000", scope.propagationContext.TraceID.String()) - }, - }, - "Tracing not enabled": { - hub: NewHub(&Client{options: ClientOptions{EnableTracing: false}}, newScope()), - trace: "4fbfb1b884c8532962a3c0b7b834428e-a9f442f9330b4e09", - baggage: "sentry-release=1.0.0,sentry-environment=production", - expectedErr: nil, - expectedSpan: false, - checkScope: func(t *testing.T, scope *Scope) { - assert.Equal(t, "4fbfb1b884c8532962a3c0b7b834428e", scope.propagationContext.TraceID.String()) - assert.Contains(t, scope.contexts, "trace") - }, + "Without span": { + hub: func() *Hub { + h, _, s := setupHubTest() + s.propagationContext.DynamicSamplingContext = DynamicSamplingContext{ + Entries: map[string]string{"release": "1.0.0", "environment": "production"}, + } + return h + }(), + expected: "sentry-environment=production,sentry-release=1.0.0", }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { - opt, err := tt.hub.ContinueTrace(tt.trace, tt.baggage) - - if tt.expectedErr != nil { - assert.Error(t, err, "expected error, got nil") - assert.Equal(t, tt.expectedErr.Error(), err.Error()) - } else { - assert.NoError(t, err, "expected no error, got one") - } - - // Check for expected SpanOption - if tt.expectedSpan { - assert.NotNil(t, opt, "expected SpanOption, got nil") - } else { - assert.Nil(t, opt, "expected no SpanOption, got one") - } - - // Additional checks on the scope - tt.checkScope(t, tt.hub.Scope()) + result := tt.hub.GetBaggage() + res := strings.Split(result, ",") + sortSlice(res) + assertEqual(t, strings.Join(res, ","), tt.expected) }) } } diff --git a/iris/sentryiris.go b/iris/sentryiris.go index eb00e30a..dbf27317 100644 --- a/iris/sentryiris.go +++ b/iris/sentryiris.go @@ -62,9 +62,11 @@ func (h *handler) handle(ctx iris.Context) { client.SetSDKIdentifier(sdkIdentifier) } + r := ctx.Request() + options := []sentry.SpanOption{ + sentry.ContinueTrace(hub, r.Header.Get(sentry.SentryTraceHeader), r.Header.Get(sentry.SentryBaggageHeader)), sentry.WithOpName("http.server"), - sentry.ContinueFromRequest(ctx.Request()), sentry.WithTransactionSource(sentry.SourceRoute), sentry.WithSpanOrigin(sentry.SpanOriginIris), } @@ -83,12 +85,12 @@ func (h *handler) handle(ctx iris.Context) { transaction.Finish() }() - transaction.SetData("http.request.method", ctx.Request().Method) + transaction.SetData("http.request.method", r.Method) - hub.Scope().SetRequest(ctx.Request()) + hub.Scope().SetRequest(r) ctx.Values().Set(valuesKey, hub) ctx.Values().Set(transactionKey, transaction) - defer h.recoverWithSentry(hub, ctx.Request()) + defer h.recoverWithSentry(hub, r) ctx.Next() } diff --git a/negroni/sentrynegroni.go b/negroni/sentrynegroni.go index 9e6ceda4..6740ee6f 100644 --- a/negroni/sentrynegroni.go +++ b/negroni/sentrynegroni.go @@ -46,8 +46,7 @@ func New(options Options) negroni.Handler { } func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { - ctx := r.Context() - hub := sentry.GetHubFromContext(ctx) + hub := sentry.GetHubFromContext(r.Context()) if hub == nil { hub = sentry.CurrentHub().Clone() } @@ -56,24 +55,19 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.Ha client.SetSDKIdentifier(sdkIdentifier) } - hub.Scope().SetRequest(r) - ctx = sentry.SetHubOnContext( - context.WithValue(ctx, sentry.RequestContextKey, r), - hub, - ) - options := []sentry.SpanOption{ + sentry.ContinueTrace(hub, r.Header.Get(sentry.SentryTraceHeader), r.Header.Get(sentry.SentryBaggageHeader)), sentry.WithOpName("http.server"), - sentry.ContinueFromRequest(r), sentry.WithTransactionSource(sentry.SourceURL), sentry.WithSpanOrigin(sentry.SpanOriginNegroni), } - // We don't mind getting an existing transaction back so we don't need to - // check if it is. - transaction := sentry.StartTransaction(ctx, + + transaction := sentry.StartTransaction( + sentry.SetHubOnContext(r.Context(), hub), traceutils.GetHTTPSpanName(r), options..., ) + transaction.SetData("http.request.method", r.Method) rw := sentry.NewWrapResponseWriter(w, r.ProtoMajor) @@ -83,13 +77,12 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.Ha transaction.SetData("http.response.status_code", status) transaction.Finish() }() - // TODO(tracing): if the next handler.ServeHTTP panics, store - // information on the transaction accordingly (status, tag, - // level?, ...). - r = r.WithContext(transaction.Context()) + hub.Scope().SetRequest(r) + r = r.WithContext(transaction.Context()) defer h.recoverWithSentry(hub, r) - next(rw, r.WithContext(ctx)) + + next(rw, r.WithContext(r.Context())) } func (h *handler) recoverWithSentry(hub *sentry.Hub, r *http.Request) { diff --git a/scope.go b/scope.go index 74eab95e..48621c94 100644 --- a/scope.go +++ b/scope.go @@ -383,15 +383,6 @@ func (scope *Scope) ApplyToEvent(event *Event, hint *EventHint, client *Client) } for key, value := range scope.contexts { - if key == "trace" && event.Type == transactionType { - // Do not override trace context of - // transactions, otherwise it breaks the - // transaction event representation. - // For error events, the trace context is used - // to link errors and traces/spans in Sentry. - continue - } - // Ensure we are not overwriting event fields if _, ok := event.Contexts[key]; !ok { event.Contexts[key] = cloneContext(value) @@ -399,29 +390,25 @@ func (scope *Scope) ApplyToEvent(event *Event, hint *EventHint, client *Client) } } - // Apply the trace context to errors if there is a Span on the scope. If - // there isn't then fall back to the propagation context. - if event.Type != transactionType { - if event.Contexts == nil { - event.Contexts = make(map[string]Context) - } + if event.Contexts == nil { + event.Contexts = make(map[string]Context) + } - if scope.span != nil { - event.Contexts["trace"] = scope.span.traceContext().Map() + if scope.span != nil { + event.Contexts["trace"] = scope.span.traceContext().Map() - transaction := scope.span.GetTransaction() - if transaction != nil { - event.sdkMetaData.dsc = DynamicSamplingContextFromTransaction(transaction) - } - } else { - event.Contexts["trace"] = scope.propagationContext.Map() + transaction := scope.span.GetTransaction() + if transaction != nil { + event.sdkMetaData.dsc = DynamicSamplingContextFromTransaction(transaction) + } + } else { + event.Contexts["trace"] = scope.propagationContext.Map() - dsc := scope.propagationContext.DynamicSamplingContext - if !dsc.HasEntries() && client != nil { - dsc = DynamicSamplingContextFromScope(scope, client) - } - event.sdkMetaData.dsc = dsc + dsc := scope.propagationContext.DynamicSamplingContext + if !dsc.HasEntries() && client != nil { + dsc = DynamicSamplingContextFromScope(scope, client) } + event.sdkMetaData.dsc = dsc } if len(scope.extra) > 0 { diff --git a/tracing.go b/tracing.go index 9c585287..0f5ade2e 100644 --- a/tracing.go +++ b/tracing.go @@ -192,11 +192,11 @@ func StartSpan(ctx context.Context, operation string, options ...SpanOption) *Sp span.recorder.record(&span) - hub := hubFromContext(ctx) - - // Update scope so that all events include a trace context, allowing - // Sentry to correlate errors to transactions/spans. - hub.Scope().SetContext("trace", span.traceContext().Map()) + clientOptions := span.clientOptions() + if clientOptions.EnableTracing { + hub := hubFromContext(ctx) + hub.Scope().SetSpan(&span) + } // Start profiling only if it's a sampled root transaction. if span.IsTransaction() && span.Sampled.Bool() { @@ -324,17 +324,21 @@ func (s *Span) ToSentryTrace() string { // Use this function to propagate the DynamicSamplingContext to a downstream SDK, // either as the value of the "baggage" HTTP header, or as an html "baggage" meta tag. func (s *Span) ToBaggage() string { - if containingTransaction := s.GetTransaction(); containingTransaction != nil { - // In case there is currently no frozen DynamicSamplingContext attached to the transaction, - // create one from the properties of the transaction. - if !s.dynamicSamplingContext.IsFrozen() { - // This will return a frozen DynamicSamplingContext. - s.dynamicSamplingContext = DynamicSamplingContextFromTransaction(containingTransaction) - } + t := s.GetTransaction() + if t == nil { + return "" + } - return containingTransaction.dynamicSamplingContext.String() + // In case there is currently no frozen DynamicSamplingContext attached to the transaction, + // create one from the properties of the transaction. + if !s.dynamicSamplingContext.IsFrozen() { + // This will return a frozen DynamicSamplingContext. + if dsc := DynamicSamplingContextFromTransaction(t); dsc.HasEntries() { + t.dynamicSamplingContext = dsc + } } - return "" + + return t.dynamicSamplingContext.String() } // SetDynamicSamplingContext sets the given dynamic sampling context on the @@ -550,11 +554,10 @@ func (s *Span) toEvent() *Event { s.dynamicSamplingContext = DynamicSamplingContextFromTransaction(s) } - contexts := map[string]Context{} + contexts := make(map[string]Context, len(s.contexts)) for k, v := range s.contexts { contexts[k] = cloneContext(v) } - contexts["trace"] = s.traceContext().Map() // Make sure that the transaction source is valid transactionSource := s.Source @@ -893,20 +896,16 @@ func WithSpanOrigin(origin SpanOrigin) SpanOption { } } -func GetTraceHeader(s *Scope) string { - if s.span != nil { - return s.span.ToSentryTrace() - } - - return fmt.Sprintf("%s-%s", s.propagationContext.TraceID, s.propagationContext.SpanID) -} - -func GetBaggageHeader(s *Scope) string { - if s.span != nil { - return s.span.ToBaggage() - } +// Continue a trace based on traceparent and bagge values. +// If the SDK is configured with tracing enabled, +// this function returns populated SpanOption. +// In any other cases, it populates the propagation context on the scope. +func ContinueTrace(hub *Hub, traceparent, baggage string) SpanOption { + scope := hub.Scope() + propagationContext, _ := PropagationContextFromHeaders(traceparent, baggage) + scope.SetPropagationContext(propagationContext) - return s.propagationContext.DynamicSamplingContext.String() + return ContinueFromHeaders(traceparent, baggage) } // ContinueFromRequest returns a span option that updates the span to continue diff --git a/tracing_test.go b/tracing_test.go index da298028..795c33d3 100644 --- a/tracing_test.go +++ b/tracing_test.go @@ -10,7 +10,6 @@ import ( "math" "net/http" "reflect" - "strings" "sync" "testing" "time" @@ -483,77 +482,6 @@ func TestToSentryTrace(t *testing.T) { } } -func TestGetTraceHeader(t *testing.T) { - tests := map[string]struct { - scope *Scope - expected string - }{ - "With span": { - scope: func() *Scope { - s := NewScope() - s.span = &Span{ - TraceID: TraceIDFromHex("d49d9bf66f13450b81f65bc51cf49c03"), - SpanID: SpanIDFromHex("a9f442f9330b4e09"), - Sampled: SampledTrue, - } - return s - }(), - expected: "d49d9bf66f13450b81f65bc51cf49c03-a9f442f9330b4e09-1", - }, - "Without span": { - scope: func() *Scope { - s := NewScope() - s.propagationContext.TraceID = TraceIDFromHex("d49d9bf66f13450b81f65bc51cf49c03") - s.propagationContext.SpanID = SpanIDFromHex("a9f442f9330b4e09") - return s - }(), - expected: "d49d9bf66f13450b81f65bc51cf49c03-a9f442f9330b4e09", - }, - } - - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - result := GetTraceHeader(tt.scope) - assertEqual(t, tt.expected, result) - }) - } -} - -func TestGetBaggageHeader(t *testing.T) { - tests := map[string]struct { - scope *Scope - expected string - }{ - "With span": { - scope: func() *Scope { - s := NewScope() - s.span = &Span{} - return s - }(), - expected: "", - }, - "Without span": { - scope: func() *Scope { - s := NewScope() - s.propagationContext.DynamicSamplingContext = DynamicSamplingContext{ - Entries: map[string]string{"release": "1.0.0", "environment": "production"}, - } - return s - }(), - expected: "sentry-environment=production,sentry-release=1.0.0", - }, - } - - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - result := GetBaggageHeader(tt.scope) - res := strings.Split(result, ",") - sortSlice(res) - assertEqual(t, tt.expected, strings.Join(res, ",")) - }) - } -} - func TestContinueSpanFromRequest(t *testing.T) { traceID := TraceIDFromHex("bc6d53f15eb88f4320054569b8c553d4") spanID := SpanIDFromHex("b72fa28504b07285")