Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracing without performance #833

Merged
merged 22 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
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
4 changes: 2 additions & 2 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ type EventProcessor func(event *Event, hint *EventHint) *Event
// ApplyToEvent changes an event based on external data and/or
// an event hint.
type EventModifier interface {
ApplyToEvent(event *Event, hint *EventHint) *Event
ApplyToEvent(event *Event, hint *EventHint, client *Client) *Event
}

var globalEventProcessors []EventProcessor
Expand Down Expand Up @@ -685,7 +685,7 @@ func (client *Client) prepareEvent(event *Event, hint *EventHint, scope EventMod
}

if scope != nil {
event = scope.ApplyToEvent(event, hint)
event = scope.ApplyToEvent(event, hint, client)
if event == nil {
return nil
}
Expand Down
50 changes: 41 additions & 9 deletions dynamic_sampling_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,7 @@
}
}

if userSegment := scope.user.Segment; userSegment != "" {
entries["user_segment"] = userSegment
}

if span.Sampled.Bool() {
entries["sampled"] = "true"
} else {
entries["sampled"] = "false"
}
entries["sampled"] = strconv.FormatBool(span.Sampled.Bool())

return DynamicSamplingContext{
Entries: entries,
Expand Down Expand Up @@ -121,3 +113,43 @@

return ""
}

// Constructs a new DynamicSamplingContext using a scope and client. Accessing
// fields on the scope are not thread safe, and this function should only be
// called within scope methods.
func DynamicSamplingContextFromScope(scope *Scope, client *Client) DynamicSamplingContext {
entries := map[string]string{}

if client == nil || scope == nil {
return DynamicSamplingContext{
Entries: entries,
Frozen: false,

Check warning on line 126 in dynamic_sampling_context.go

View check run for this annotation

Codecov / codecov/patch

dynamic_sampling_context.go#L124-L126

Added lines #L124 - L126 were not covered by tests
}
}

propagationContext := scope.propagationContext

if traceID := propagationContext.TraceID.String(); traceID != "" {
entries["trace_id"] = traceID
}
if sampleRate := client.options.TracesSampleRate; sampleRate != 0 {
entries["sample_rate"] = strconv.FormatFloat(sampleRate, 'f', -1, 64)
}

if dsn := client.dsn; dsn != nil {
if publicKey := dsn.publicKey; publicKey != "" {
entries["public_key"] = publicKey
}
}
if release := client.options.Release; release != "" {
entries["release"] = release
}
if environment := client.options.Environment; environment != "" {
entries["environment"] = environment

Check warning on line 148 in dynamic_sampling_context.go

View check run for this annotation

Codecov / codecov/patch

dynamic_sampling_context.go#L148

Added line #L148 was not covered by tests
}

return DynamicSamplingContext{
Entries: entries,
Frozen: true,
}
}
15 changes: 7 additions & 8 deletions dynamic_sampling_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,13 @@ func TestDynamicSamplingContextFromTransaction(t *testing.T) {
want: DynamicSamplingContext{
Frozen: true,
Entries: map[string]string{
"sample_rate": "1",
"trace_id": "d49d9bf66f13450b81f65bc51cf49c03",
"public_key": "public",
"release": "1.0.0",
"environment": "test",
"transaction": "name",
"user_segment": "user_segment",
"sampled": "true",
"sample_rate": "1",
"trace_id": "d49d9bf66f13450b81f65bc51cf49c03",
"public_key": "public",
"release": "1.0.0",
"environment": "test",
"transaction": "name",
"sampled": "true",
},
},
},
Expand Down
1 change: 1 addition & 0 deletions http/sentryhttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ func (h *Handler) handle(handler http.Handler) http.HandlerFunc {
// level?, ...).
r = r.WithContext(transaction.Context())
hub.Scope().SetRequest(r)

defer h.recoverWithSentry(hub, r)
handler.ServeHTTP(rw, r)
}
Expand Down
26 changes: 26 additions & 0 deletions hub.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import (
"context"
"fmt"
"sync"
"time"
)
Expand Down Expand Up @@ -365,6 +366,31 @@
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) {
ribice marked this conversation as resolved.
Show resolved Hide resolved
scope := hub.Scope()
propagationContext, err := PropagationContextFromHeaders(trace, baggage)
if err != nil {
return nil, err

Check warning on line 375 in hub.go

View check run for this annotation

Codecov / codecov/patch

hub.go#L371-L375

Added lines #L371 - L375 were not covered by tests
}

fmt.Printf("Propagation context ContinueTrace: %v \n", propagationContext.Map())

Check warning on line 378 in hub.go

View check run for this annotation

Codecov / codecov/patch

hub.go#L378

Added line #L378 was not covered by tests

scope.SetPropagationContext(propagationContext)

Check warning on line 380 in hub.go

View check run for this annotation

Codecov / codecov/patch

hub.go#L380

Added line #L380 was not covered by tests

client := hub.Client()
if client != nil && client.options.EnableTracing {
return ContinueFromHeaders(trace, baggage), nil

Check warning on line 384 in hub.go

View check run for this annotation

Codecov / codecov/patch

hub.go#L382-L384

Added lines #L382 - L384 were not covered by tests
}

scope.SetContext("trace", propagationContext.Map())

Check warning on line 387 in hub.go

View check run for this annotation

Codecov / codecov/patch

hub.go#L387

Added line #L387 was not covered by tests

fmt.Println("Scope context in ContinueTrace: ", scope.contexts["trace"])

Check warning on line 389 in hub.go

View check run for this annotation

Codecov / codecov/patch

hub.go#L389

Added line #L389 was not covered by tests

return nil, nil

Check warning on line 391 in hub.go

View check run for this annotation

Codecov / codecov/patch

hub.go#L391

Added line #L391 was not covered by tests
}

// HasHubOnContext checks whether Hub instance is bound to a given Context struct.
func HasHubOnContext(ctx context.Context) bool {
_, ok := ctx.Value(HubContextKey).(*Hub)
Expand Down
2 changes: 1 addition & 1 deletion mocks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func (scope *ScopeMock) AddBreadcrumb(breadcrumb *Breadcrumb, _ int) {
scope.breadcrumb = breadcrumb
}

func (scope *ScopeMock) ApplyToEvent(event *Event, _ *EventHint) *Event {
func (scope *ScopeMock) ApplyToEvent(event *Event, _ *EventHint, _ *Client) *Event {
if scope.shouldDropEvent {
return nil
}
Expand Down
90 changes: 90 additions & 0 deletions propagation_context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package sentry

import (
"crypto/rand"
"encoding/json"
)

type PropagationContext struct {
TraceID TraceID `json:"trace_id"`
SpanID SpanID `json:"span_id"`
ParentSpanID SpanID `json:"parent_span_id"`
DynamicSamplingContext DynamicSamplingContext `json:"-"`
}

func (p PropagationContext) MarshalJSON() ([]byte, error) {
type propagationContext PropagationContext
var parentSpanID string
if p.ParentSpanID != zeroSpanID {
parentSpanID = p.ParentSpanID.String()
}
return json.Marshal(struct {
*propagationContext
ParentSpanID string `json:"parent_span_id,omitempty"`
}{
propagationContext: (*propagationContext)(&p),
ParentSpanID: parentSpanID,
})
}

func (p PropagationContext) Map() map[string]interface{} {
m := map[string]interface{}{
"trace_id": p.TraceID,
"span_id": p.SpanID,
}

if p.ParentSpanID != zeroSpanID {
m["parent_span_id"] = p.ParentSpanID
}

return m
}

func NewPropagationContext() PropagationContext {
p := PropagationContext{}

if _, err := rand.Read(p.TraceID[:]); err != nil {
panic(err)

Check warning on line 47 in propagation_context.go

View check run for this annotation

Codecov / codecov/patch

propagation_context.go#L47

Added line #L47 was not covered by tests
}

if _, err := rand.Read(p.SpanID[:]); err != nil {
panic(err)

Check warning on line 51 in propagation_context.go

View check run for this annotation

Codecov / codecov/patch

propagation_context.go#L51

Added line #L51 was not covered by tests
}

return p
}

func PropagationContextFromHeaders(trace, baggage string) (PropagationContext, error) {
p := NewPropagationContext()

if _, err := rand.Read(p.SpanID[:]); err != nil {
panic(err)

Check warning on line 61 in propagation_context.go

View check run for this annotation

Codecov / codecov/patch

propagation_context.go#L61

Added line #L61 was not covered by tests
}

hasTrace := false
if trace != "" {
if tpc, valid := ParseTraceParentContext([]byte(trace)); valid {
hasTrace = true
p.TraceID = tpc.TraceID
p.ParentSpanID = tpc.ParentSpanID
}
}

if baggage != "" {
dsc, err := DynamicSamplingContextFromHeader([]byte(baggage))
if err != nil {
return PropagationContext{}, err

Check warning on line 76 in propagation_context.go

View check run for this annotation

Codecov / codecov/patch

propagation_context.go#L76

Added line #L76 was not covered by tests
}
p.DynamicSamplingContext = dsc
}

// In case a sentry-trace header is present but there are no sentry-related
// values in the baggage, create an empty, frozen DynamicSamplingContext.
if hasTrace && !p.DynamicSamplingContext.HasEntries() {
p.DynamicSamplingContext = DynamicSamplingContext{
Frozen: true,
}
}

return p, nil
}
133 changes: 133 additions & 0 deletions propagation_context_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package sentry

import (
"bytes"
"encoding/json"
"testing"
)

func TestPropagationContextMarshalJSON(t *testing.T) {
v := NewPropagationContext()
b, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
if bytes.Contains(b, []byte("parent_span_id")) {
t.Fatalf("unwanted parent_span_id: %s", b)
}

v.ParentSpanID = SpanIDFromHex("b72fa28504b07285")
b2, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
if !bytes.Contains(b2, []byte("parent_span_id")) {
t.Fatalf("missing parent_span_id: %s", b)
}
}

func TestPropagationContextMap(t *testing.T) {
p := NewPropagationContext()
assertEqual(t,
p.Map(),
map[string]interface{}{
"trace_id": p.TraceID,
"span_id": p.SpanID,
},
"without parent span id")

p.ParentSpanID = SpanIDFromHex("b72fa28504b07285")
assertEqual(t,
p.Map(),
map[string]interface{}{
"trace_id": p.TraceID,
"span_id": p.SpanID,
"parent_span_id": p.ParentSpanID,
},
"without praent span id")
}

func TestPropagationContextFromHeaders(t *testing.T) {
tests := []struct {
traceStr string
baggageStr string
want PropagationContext
}{
{
// No sentry-trace or baggage => nothing to do, unfrozen DSC
traceStr: "",
baggageStr: "",
want: PropagationContext{
DynamicSamplingContext: DynamicSamplingContext{
Frozen: false,
Entries: nil,
},
},
},
{
// Third-party baggage => nothing to do, unfrozen DSC
traceStr: "",
baggageStr: "other-vendor-key1=value1;value2, other-vendor-key2=value3",
want: PropagationContext{
DynamicSamplingContext: DynamicSamplingContext{
Frozen: false,
Entries: map[string]string{},
},
},
},
{
// sentry-trace and no baggage => we should create a new DSC and freeze it
// immediately.
traceStr: "bc6d53f15eb88f4320054569b8c553d4-b72fa28504b07285-1",
baggageStr: "",
want: PropagationContext{
TraceID: TraceIDFromHex("bc6d53f15eb88f4320054569b8c553d4"),
ParentSpanID: SpanIDFromHex("b72fa28504b07285"),
DynamicSamplingContext: DynamicSamplingContext{
Frozen: true,
},
},
},
{
traceStr: "bc6d53f15eb88f4320054569b8c553d4-b72fa28504b07285-1",
baggageStr: "sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-public_key=public,sentry-sample_rate=1",
want: PropagationContext{
TraceID: TraceIDFromHex("bc6d53f15eb88f4320054569b8c553d4"),
ParentSpanID: SpanIDFromHex("b72fa28504b07285"),
DynamicSamplingContext: DynamicSamplingContext{
Frozen: true,
Entries: map[string]string{
"public_key": "public",
"sample_rate": "1",
"trace_id": "d49d9bf66f13450b81f65bc51cf49c03",
},
},
},
},
}

for _, tt := range tests {
p, err := PropagationContextFromHeaders(tt.traceStr, tt.baggageStr)
if err != nil {
t.Fatal(err)
}

if tt.want.TraceID != zeroTraceID && p.TraceID != tt.want.TraceID {
t.Errorf("got TraceID = %s, want %s", p.TraceID, tt.want.TraceID)
}

if p.TraceID == zeroTraceID {
t.Errorf("got TraceID = %s, want non-zero", p.TraceID)
}

if p.ParentSpanID != tt.want.ParentSpanID {
t.Errorf("got ParentSpanID = %s, want %s", p.ParentSpanID, tt.want.ParentSpanID)
}

if p.SpanID == zeroSpanID {
t.Errorf("got SpanID = %s, want non-zero", p.SpanID)
}

assertEqual(t, p.DynamicSamplingContext, tt.want.DynamicSamplingContext)
}
}
Loading
Loading