Skip to content

Commit 00f7f6c

Browse files
authored
Tracing without performance (#833)
1 parent 5377e6a commit 00f7f6c

14 files changed

+675
-55
lines changed

Diff for: client.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ type EventProcessor func(event *Event, hint *EventHint) *Event
9090
// ApplyToEvent changes an event based on external data and/or
9191
// an event hint.
9292
type EventModifier interface {
93-
ApplyToEvent(event *Event, hint *EventHint) *Event
93+
ApplyToEvent(event *Event, hint *EventHint, client *Client) *Event
9494
}
9595

9696
var globalEventProcessors []EventProcessor
@@ -690,7 +690,7 @@ func (client *Client) prepareEvent(event *Event, hint *EventHint, scope EventMod
690690
}
691691

692692
if scope != nil {
693-
event = scope.ApplyToEvent(event, hint)
693+
event = scope.ApplyToEvent(event, hint, client)
694694
if event == nil {
695695
return nil
696696
}

Diff for: dynamic_sampling_context.go

+41-9
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,7 @@ func DynamicSamplingContextFromTransaction(span *Span) DynamicSamplingContext {
7878
}
7979
}
8080

81-
if userSegment := scope.user.Segment; userSegment != "" {
82-
entries["user_segment"] = userSegment
83-
}
84-
85-
if span.Sampled.Bool() {
86-
entries["sampled"] = "true"
87-
} else {
88-
entries["sampled"] = "false"
89-
}
81+
entries["sampled"] = strconv.FormatBool(span.Sampled.Bool())
9082

9183
return DynamicSamplingContext{
9284
Entries: entries,
@@ -121,3 +113,43 @@ func (d DynamicSamplingContext) String() string {
121113

122114
return ""
123115
}
116+
117+
// Constructs a new DynamicSamplingContext using a scope and client. Accessing
118+
// fields on the scope are not thread safe, and this function should only be
119+
// called within scope methods.
120+
func DynamicSamplingContextFromScope(scope *Scope, client *Client) DynamicSamplingContext {
121+
entries := map[string]string{}
122+
123+
if client == nil || scope == nil {
124+
return DynamicSamplingContext{
125+
Entries: entries,
126+
Frozen: false,
127+
}
128+
}
129+
130+
propagationContext := scope.propagationContext
131+
132+
if traceID := propagationContext.TraceID.String(); traceID != "" {
133+
entries["trace_id"] = traceID
134+
}
135+
if sampleRate := client.options.TracesSampleRate; sampleRate != 0 {
136+
entries["sample_rate"] = strconv.FormatFloat(sampleRate, 'f', -1, 64)
137+
}
138+
139+
if dsn := client.dsn; dsn != nil {
140+
if publicKey := dsn.publicKey; publicKey != "" {
141+
entries["public_key"] = publicKey
142+
}
143+
}
144+
if release := client.options.Release; release != "" {
145+
entries["release"] = release
146+
}
147+
if environment := client.options.Environment; environment != "" {
148+
entries["environment"] = environment
149+
}
150+
151+
return DynamicSamplingContext{
152+
Entries: entries,
153+
Frozen: true,
154+
}
155+
}

Diff for: dynamic_sampling_context_test.go

+72-8
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,13 @@ func TestDynamicSamplingContextFromTransaction(t *testing.T) {
9797
want: DynamicSamplingContext{
9898
Frozen: true,
9999
Entries: map[string]string{
100-
"sample_rate": "1",
101-
"trace_id": "d49d9bf66f13450b81f65bc51cf49c03",
102-
"public_key": "public",
103-
"release": "1.0.0",
104-
"environment": "test",
105-
"transaction": "name",
106-
"user_segment": "user_segment",
107-
"sampled": "true",
100+
"sample_rate": "1",
101+
"trace_id": "d49d9bf66f13450b81f65bc51cf49c03",
102+
"public_key": "public",
103+
"release": "1.0.0",
104+
"environment": "test",
105+
"transaction": "name",
106+
"sampled": "true",
108107
},
109108
},
110109
},
@@ -181,3 +180,68 @@ func TestString(t *testing.T) {
181180
}
182181
testutils.AssertBaggageStringsEqual(t, dsc.String(), "sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-public_key=public,sentry-sample_rate=1")
183182
}
183+
184+
func TestDynamicSamplingContextFromScope(t *testing.T) {
185+
tests := map[string]struct {
186+
scope *Scope
187+
client *Client
188+
expected DynamicSamplingContext
189+
}{
190+
"Valid input": {
191+
scope: &Scope{
192+
propagationContext: PropagationContext{
193+
TraceID: TraceIDFromHex("d49d9bf66f13450b81f65bc51cf49c03"),
194+
SpanID: SpanIDFromHex("a9f442f9330b4e09"),
195+
},
196+
},
197+
client: func() *Client {
198+
dsn, _ := NewDsn("http://[email protected]/sentry/1")
199+
return &Client{
200+
options: ClientOptions{
201+
Dsn: dsn.String(),
202+
Release: "1.0.0",
203+
Environment: "production",
204+
},
205+
dsn: dsn,
206+
}
207+
}(),
208+
expected: DynamicSamplingContext{
209+
Entries: map[string]string{
210+
"trace_id": "d49d9bf66f13450b81f65bc51cf49c03",
211+
"public_key": "public",
212+
"release": "1.0.0",
213+
"environment": "production",
214+
},
215+
Frozen: true,
216+
},
217+
},
218+
"Nil client": {
219+
scope: &Scope{
220+
propagationContext: PropagationContext{
221+
TraceID: TraceIDFromHex("d49d9bf66f13450b81f65bc51cf49c03"),
222+
SpanID: SpanIDFromHex("a9f442f9330b4e09"),
223+
},
224+
},
225+
client: nil,
226+
expected: DynamicSamplingContext{
227+
Entries: map[string]string{},
228+
Frozen: false,
229+
},
230+
},
231+
"Nil scope": {
232+
scope: nil,
233+
client: &Client{},
234+
expected: DynamicSamplingContext{
235+
Entries: map[string]string{},
236+
Frozen: false,
237+
},
238+
},
239+
}
240+
241+
for name, tt := range tests {
242+
t.Run(name, func(t *testing.T) {
243+
result := DynamicSamplingContextFromScope(tt.scope, tt.client)
244+
assertEqual(t, tt.expected, result)
245+
})
246+
}
247+
}

Diff for: http/sentryhttp.go

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ func (h *Handler) handle(handler http.Handler) http.HandlerFunc {
121121
// level?, ...).
122122
r = r.WithContext(transaction.Context())
123123
hub.Scope().SetRequest(r)
124+
124125
defer h.recoverWithSentry(hub, r)
125126
handler.ServeHTTP(rw, r)
126127
}

Diff for: hub.go

+21
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,27 @@ func (hub *Hub) Flush(timeout time.Duration) bool {
365365
return client.Flush(timeout)
366366
}
367367

368+
// Continue a trace based on HTTP header values. If performance is enabled this
369+
// returns a SpanOption that can be used to start a transaction, otherwise nil.
370+
func (hub *Hub) ContinueTrace(trace, baggage string) (SpanOption, error) {
371+
scope := hub.Scope()
372+
propagationContext, err := PropagationContextFromHeaders(trace, baggage)
373+
if err != nil {
374+
return nil, err
375+
}
376+
377+
scope.SetPropagationContext(propagationContext)
378+
379+
client := hub.Client()
380+
if client != nil && client.options.EnableTracing {
381+
return ContinueFromHeaders(trace, baggage), nil
382+
}
383+
384+
scope.SetContext("trace", propagationContext.Map())
385+
386+
return nil, nil
387+
}
388+
368389
// HasHubOnContext checks whether Hub instance is bound to a given Context struct.
369390
func HasHubOnContext(ctx context.Context) bool {
370391
_, ok := ctx.Value(HubContextKey).(*Hub)

Diff for: hub_test.go

+84
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ package sentry
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"sync"
78
"testing"
89

910
"github.com/google/go-cmp/cmp"
1011
"github.com/google/go-cmp/cmp/cmpopts"
12+
"github.com/stretchr/testify/assert"
1113
)
1214

1315
const testDsn = "http://[email protected]/1337"
@@ -324,6 +326,88 @@ func TestHasHubOnContextReturnsFalseIfHubIsNotThere(t *testing.T) {
324326
assertEqual(t, false, HasHubOnContext(ctx))
325327
}
326328

329+
func TestHub_ContinueTrace(t *testing.T) {
330+
newScope := func() *Scope {
331+
return &Scope{contexts: make(map[string]Context)}
332+
}
333+
334+
mockClient := &Client{options: ClientOptions{EnableTracing: true}}
335+
336+
tests := map[string]struct {
337+
hub *Hub
338+
trace string
339+
baggage string
340+
expectedErr error
341+
expectedSpan bool // Whether a SpanOption is expected to be returned
342+
checkScope func(t *testing.T, scope *Scope) // Additional checks on the scope
343+
}{
344+
"Valid trace and baggage": {
345+
hub: NewHub(mockClient, newScope()),
346+
trace: "4fbfb1b884c8532962a3c0b7b834428e-a9f442f9330b4e09",
347+
baggage: "sentry-release=1.0.0,sentry-environment=production",
348+
expectedErr: nil,
349+
expectedSpan: true,
350+
checkScope: func(t *testing.T, scope *Scope) {
351+
assert.Equal(t, "4fbfb1b884c8532962a3c0b7b834428e", scope.propagationContext.TraceID.String())
352+
},
353+
},
354+
"Invalid trace": {
355+
hub: NewHub(mockClient, newScope()),
356+
trace: "invalid",
357+
baggage: "sentry-release=1.0.0,sentry-environment=production",
358+
expectedErr: nil,
359+
expectedSpan: true,
360+
checkScope: func(t *testing.T, scope *Scope) {
361+
assert.NotEmpty(t, scope.propagationContext.TraceID.String())
362+
},
363+
},
364+
"Invalid baggage": {
365+
hub: NewHub(mockClient, newScope()),
366+
trace: "4fbfb1b884c8532962a3c0b7b834428e-a9f442f9330b4e09",
367+
baggage: "invalid_baggage",
368+
expectedErr: errors.New("invalid baggage list-member: \"invalid_baggage\""),
369+
expectedSpan: false,
370+
checkScope: func(t *testing.T, scope *Scope) {
371+
assert.Equal(t, "00000000000000000000000000000000", scope.propagationContext.TraceID.String())
372+
},
373+
},
374+
"Tracing not enabled": {
375+
hub: NewHub(&Client{options: ClientOptions{EnableTracing: false}}, newScope()),
376+
trace: "4fbfb1b884c8532962a3c0b7b834428e-a9f442f9330b4e09",
377+
baggage: "sentry-release=1.0.0,sentry-environment=production",
378+
expectedErr: nil,
379+
expectedSpan: false,
380+
checkScope: func(t *testing.T, scope *Scope) {
381+
assert.Equal(t, "4fbfb1b884c8532962a3c0b7b834428e", scope.propagationContext.TraceID.String())
382+
assert.Contains(t, scope.contexts, "trace")
383+
},
384+
},
385+
}
386+
387+
for name, tt := range tests {
388+
t.Run(name, func(t *testing.T) {
389+
opt, err := tt.hub.ContinueTrace(tt.trace, tt.baggage)
390+
391+
if tt.expectedErr != nil {
392+
assert.Error(t, err, "expected error, got nil")
393+
assert.Equal(t, tt.expectedErr.Error(), err.Error())
394+
} else {
395+
assert.NoError(t, err, "expected no error, got one")
396+
}
397+
398+
// Check for expected SpanOption
399+
if tt.expectedSpan {
400+
assert.NotNil(t, opt, "expected SpanOption, got nil")
401+
} else {
402+
assert.Nil(t, opt, "expected no SpanOption, got one")
403+
}
404+
405+
// Additional checks on the scope
406+
tt.checkScope(t, tt.hub.Scope())
407+
})
408+
}
409+
}
410+
327411
func TestGetHubFromContext(t *testing.T) {
328412
hub, _, _ := setupHubTest()
329413
ctx := context.Background()

Diff for: mocks_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ func (scope *ScopeMock) AddBreadcrumb(breadcrumb *Breadcrumb, _ int) {
1414
scope.breadcrumb = breadcrumb
1515
}
1616

17-
func (scope *ScopeMock) ApplyToEvent(event *Event, _ *EventHint) *Event {
17+
func (scope *ScopeMock) ApplyToEvent(event *Event, _ *EventHint, _ *Client) *Event {
1818
if scope.shouldDropEvent {
1919
return nil
2020
}

0 commit comments

Comments
 (0)