Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ type ClientOptions struct {
TracesSampler TracesSampler
// Control with URLs trace propagation should be enabled. Does not support regex patterns.
TracePropagationTargets []string
// PropagateTraceparent is used to control whether the W3C Trace Context HTTP traceparent header
// is propagated on outgoing http requests.
PropagateTraceparent bool
// List of regexp strings that will be used to match against event's message
// and if applicable, caught errors type and value.
// If the match is found, then a whole event will be dropped.
Expand Down
18 changes: 14 additions & 4 deletions httpclient/sentryhttpclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,22 @@ func NewSentryRoundTripper(originalRoundTripper http.RoundTripper, opts ...Sentr

// Configure trace propagation targets
var tracePropagationTargets []string
var propagateTraceparent bool
if hub := sentry.CurrentHub(); hub != nil {
client := hub.Client()
if client != nil {
clientOptions := client.Options()
if clientOptions.TracePropagationTargets != nil {
tracePropagationTargets = clientOptions.TracePropagationTargets
}
propagateTraceparent = clientOptions.PropagateTraceparent
}
}

t := &SentryRoundTripper{
originalRoundTripper: originalRoundTripper,
tracePropagationTargets: tracePropagationTargets,
propagateTraceparent: propagateTraceparent,
}

for _, opt := range opts {
Expand All @@ -72,6 +75,7 @@ func NewSentryRoundTripper(originalRoundTripper http.RoundTripper, opts ...Sentr
type SentryRoundTripper struct {
originalRoundTripper http.RoundTripper

propagateTraceparent bool
tracePropagationTargets []string
}

Expand All @@ -96,8 +100,11 @@ func (s *SentryRoundTripper) RoundTrip(request *http.Request) (*http.Response, e
parentSpan := sentry.SpanFromContext(request.Context())
if parentSpan == nil {
if hub := sentry.GetHubFromContext(request.Context()); hub != nil {
request.Header.Add("Baggage", hub.GetBaggage())
request.Header.Add("Sentry-Trace", hub.GetTraceparent())
request.Header.Add(sentry.SentryBaggageHeader, hub.GetBaggage())
request.Header.Add(sentry.SentryTraceHeader, hub.GetTraceparent())
if s.propagateTraceparent {
request.Header.Add(sentry.TraceparentHeader, hub.GetTraceparentW3C())
}
}

return s.originalRoundTripper.RoundTrip(request)
Expand All @@ -115,8 +122,11 @@ func (s *SentryRoundTripper) RoundTrip(request *http.Request) (*http.Response, e
span.SetData("server.port", request.URL.Port())

// Always add `Baggage` and `Sentry-Trace` headers.
request.Header.Add("Baggage", span.ToBaggage())
request.Header.Add("Sentry-Trace", span.ToSentryTrace())
request.Header.Add(sentry.SentryBaggageHeader, span.ToBaggage())
request.Header.Add(sentry.SentryTraceHeader, span.ToSentryTrace())
if s.propagateTraceparent {
request.Header.Add(sentry.TraceparentHeader, span.ToTraceparent())
}

response, err := s.originalRoundTripper.RoundTrip(request)
if err != nil {
Expand Down
71 changes: 71 additions & 0 deletions httpclient/sentryhttpclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"crypto/rand"
"crypto/tls"
"errors"
"fmt"
"io"
"net/http"
"strconv"
Expand Down Expand Up @@ -467,6 +468,60 @@ func TestIntegration_NoParentSpan(t *testing.T) {
}
}

func TestPropagateTraceparentHeader(t *testing.T) {
sentryClient, err := sentry.NewClient(sentry.ClientOptions{
EnableTracing: true,
TracesSampleRate: 1.0,
PropagateTraceparent: true,
BeforeSendTransaction: func(event *sentry.Event, _ *sentry.EventHint) *sentry.Event { return event },
})
if err != nil {
t.Fatal(err)
}

hub := sentry.NewHub(sentryClient, sentry.NewScope())
ctx := sentry.SetHubOnContext(context.Background(), hub)
span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent"))
ctx = span.Context()

request, err := http.NewRequestWithContext(ctx, "GET", "https://example.com/foo", nil)
if err != nil {
t.Fatal(err)
}

roundTripper := &noopRoundTripper{
ExpectResponseStatus: 200,
ExpectResponseLength: 0,
}

client := &http.Client{
Transport: sentryhttpclient.NewSentryRoundTripper(roundTripper),
}

response, err := client.Do(request)
if err != nil {
t.Fatal(err)
}
if response.Body != nil {
response.Body.Close()
}
span.Finish()

traceparent := response.Request.Header.Get("traceparent")
if traceparent == "" {
t.Fatalf(`Expected "traceparent" header to be set`)
}

sentryTrace := response.Request.Header.Get("Sentry-Trace")
if sentryTrace == "" {
t.Fatalf(`Expected "Sentry-Trace" header to be set`)
}

if want := traceparentFromSentryTraceHeader(t, sentryTrace); traceparent != want {
t.Fatalf(`Unexpected "traceparent" header value, got %q want %q`, traceparent, want)
}
}

func TestDefaults(t *testing.T) {
t.Run("Create a regular outgoing HTTP request with default NewSentryRoundTripper", func(t *testing.T) {
roundTripper := sentryhttpclient.NewSentryRoundTripper(nil)
Expand All @@ -482,3 +537,19 @@ func TestDefaults(t *testing.T) {
}
})
}

func traceparentFromSentryTraceHeader(t *testing.T, sentryTrace string) string {
t.Helper()

traceParentContext, valid := sentry.ParseTraceParentContext([]byte(sentryTrace))
if !valid {
t.Fatalf("Invalid sentry-trace header: %q", sentryTrace)
}

traceFlags := "00"
if traceParentContext.Sampled == sentry.SampledTrue {
traceFlags = "01"
}

return fmt.Sprintf("00-%s-%s-%s", traceParentContext.TraceID.String(), traceParentContext.ParentSpanID.String(), traceFlags)
}
11 changes: 11 additions & 0 deletions hub.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,17 @@ func (hub *Hub) GetTraceparent() string {
return fmt.Sprintf("%s-%s", scope.propagationContext.TraceID, scope.propagationContext.SpanID)
}

// GetTraceparentW3C returns the current traceparent string in W3C format.
// This is intended for propagation to downstream services that expect the W3C header.
func (hub *Hub) GetTraceparentW3C() string {
scope := hub.Scope()
if scope.span != nil {
return scope.span.ToTraceparent()
}

return fmt.Sprintf("00-%s-%s-00", scope.propagationContext.TraceID, scope.propagationContext.SpanID)
}

// 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
Expand Down
10 changes: 10 additions & 0 deletions tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
const (
SentryTraceHeader = "sentry-trace"
SentryBaggageHeader = "baggage"
TraceparentHeader = "traceparent"
)

// SpanOrigin indicates what created a trace or a span. See: https://develop.sentry.dev/sdk/performance/trace-origin/
Expand Down Expand Up @@ -320,6 +321,15 @@ func (s *Span) ToSentryTrace() string {
return b.String()
}

// ToTraceparent returns the W3C traceparent header value for the span.
func (s *Span) ToTraceparent() string {
traceFlags := "00"
if s.Sampled == SampledTrue {
traceFlags = "01"
}
return fmt.Sprintf("00-%s-%s-%s", s.TraceID.String(), s.SpanID.String(), traceFlags)
}

// ToBaggage returns the serialized DynamicSamplingContext from a transaction.
// 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.
Expand Down
Loading