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

Make maximum amount of spans configurable #460

Merged
merged 9 commits into from Nov 2, 2022
11 changes: 11 additions & 0 deletions client.go
Expand Up @@ -29,6 +29,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,8 @@ type ClientOptions struct {
Environment string
// Maximum number of breadcrumbs.
MaxBreadcrumbs int
// Maximum number of spans.
MaxSpans int
fsrv-xyz marked this conversation as resolved.
Show resolved Hide resolved
// 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 +257,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
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
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 CurrentHub().Client() != nil {
maxSpans = CurrentHub().Client().options.MaxSpans
fsrv-xyz marked this conversation as resolved.
Show resolved Hide resolved
}
r.mu.Lock()
defer r.mu.Unlock()
if len(r.spans) >= maxSpans {
Expand Down
63 changes: 63 additions & 0 deletions span_recorder_test.go
@@ -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 client after test for not affecting other tests
fsrv-xyz marked this conversation as resolved.
Show resolved Hide resolved
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")
}
})
}
}