Skip to content

Commit

Permalink
feat: Make maximum amount of spans configurable (#460)
Browse files Browse the repository at this point in the history
Co-authored-by: Michi Hoffmann <[email protected]>
  • Loading branch information
fsrv-xyz and cleptric authored Nov 2, 2022
1 parent 78a3b74 commit 5301f85
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- fix: Scope values should not override Event values (#446)
- feat: Extend User inteface by adding Data, Name and Segment (#483)
- feat: Make maximum amount of spans configurable (#460)

## 0.14.0

Expand Down
14 changes: 14 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ import (
// stack trace is often the most useful information.
const maxErrorDepth = 10

// defaultMaxSpans limits the default number of recorded spans per transaction. The limit is
// meant to bound memory usage and prevent too large transaction events that
// would be rejected by Sentry.
const defaultMaxSpans = 1000

// hostname is the host name reported by the kernel. It is precomputed once to
// avoid syscalls when capturing events.
//
Expand Down Expand Up @@ -175,6 +180,11 @@ type ClientOptions struct {
// Maximum number of breadcrumbs
// when MaxBreadcrumbs is negative then ignore breadcrumbs.
MaxBreadcrumbs int
// Maximum number of spans.
//
// See https://develop.sentry.dev/sdk/envelopes/#size-limits for size limits
// applied during event ingestion. Events that exceed these limits might get dropped.
MaxSpans int
// An optional pointer to http.Client that will be used with a default
// HTTPTransport. Using your own client will make HTTPTransport, HTTPProxy,
// HTTPSProxy and CaCerts options ignored.
Expand Down Expand Up @@ -250,6 +260,10 @@ func NewClient(options ClientOptions) (*Client, error) {
options.MaxErrorDepth = maxErrorDepth
}

if options.MaxSpans == 0 {
options.MaxSpans = defaultMaxSpans
}

// SENTRYGODEBUG is a comma-separated list of key=value pairs (similar
// to GODEBUG). It is not a supported feature: recognized debug options
// may change any time.
Expand Down
14 changes: 14 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -520,3 +520,17 @@ func TestRecover(t *testing.T) {
})
}
}

func TestCustomMaxSpansProperty(t *testing.T) {
client, _, _ := setupClientTest()
assertEqual(t, client.Options().MaxSpans, defaultMaxSpans)

client.options.MaxSpans = 2000
assertEqual(t, client.Options().MaxSpans, 2000)

properClient, _ := NewClient(ClientOptions{
MaxSpans: 3000,
})

assertEqual(t, properClient.Options().MaxSpans, 3000)
}
9 changes: 4 additions & 5 deletions span_recorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@ import (
"sync"
)

// maxSpans limits the number of recorded spans per transaction. The limit is
// meant to bound memory usage and prevent too large transaction events that
// would be rejected by Sentry.
const maxSpans = 1000

// A spanRecorder stores a span tree that makes up a transaction. Safe for
// concurrent use. It is okay to add child spans from multiple goroutines.
type spanRecorder struct {
Expand All @@ -20,6 +15,10 @@ type spanRecorder struct {
// record stores a span. The first stored span is assumed to be the root of a
// span tree.
func (r *spanRecorder) record(s *Span) {
maxSpans := defaultMaxSpans
if client := CurrentHub().Client(); client != nil {
maxSpans = client.Options().MaxSpans
}
r.mu.Lock()
defer r.mu.Unlock()
if len(r.spans) >= maxSpans {
Expand Down
63 changes: 63 additions & 0 deletions span_recorder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package sentry

import (
"bytes"
"context"
"fmt"
"io"
"testing"
)

func Test_spanRecorder_record(t *testing.T) {
testRootSpan := StartSpan(context.Background(), "test", TransactionName("test transaction"))

for _, tt := range []struct {
name string
maxSpans int
toRecordSpans int
expectOverflow bool
}{
{
name: "record span without problems",
maxSpans: defaultMaxSpans,
toRecordSpans: 1,
expectOverflow: false,
},
{
name: "record span with overflow",
maxSpans: 2,
toRecordSpans: 4,
expectOverflow: true,
},
} {
t.Run(tt.name, func(t *testing.T) {
logBuffer := bytes.Buffer{}
Logger.SetOutput(&logBuffer)
defer Logger.SetOutput(io.Discard)
spanRecorder := spanRecorder{}

currentHub.BindClient(&Client{
options: ClientOptions{
MaxSpans: tt.maxSpans,
},
})
// Unbind the client afterwards, to not affect other tests
defer currentHub.stackTop().SetClient(nil)

for i := 0; i < tt.toRecordSpans; i++ {
child := testRootSpan.StartChild(fmt.Sprintf("test %d", i))
spanRecorder.record(child)
}

if tt.expectOverflow {
assertNotEqual(t, len(spanRecorder.spans), tt.toRecordSpans, "expected overflow")
} else {
assertEqual(t, len(spanRecorder.spans), tt.toRecordSpans, "expected no overflow")
}
// check if Logger was called for overflow messages
if bytes.Contains(logBuffer.Bytes(), []byte("Too many spans")) && !tt.expectOverflow {
t.Error("unexpected overflow log")
}
})
}
}

0 comments on commit 5301f85

Please sign in to comment.