diff --git a/client.go b/client.go index 3804c8a64..6f7b62f59 100644 --- a/client.go +++ b/client.go @@ -140,6 +140,9 @@ type ClientOptions struct { // and if applicable, caught errors type and value. // If the match is found, then a whole event will be dropped. IgnoreErrors []string + // List of regexp strings that will be used to match against a transaction's + // name. If a match is found, then the transaction will be dropped. + IgnoreTransactions []string // If this flag is enabled, certain personally identifiable information (PII) is added by active integrations. // By default, no such data is sent. SendDefaultPII bool @@ -371,6 +374,7 @@ func (client *Client) setupIntegrations() { new(environmentIntegration), new(modulesIntegration), new(ignoreErrorsIntegration), + new(ignoreTransactionsIntegration), } if client.options.Integrations != nil { diff --git a/client_test.go b/client_test.go index 5a4f29e64..7f3f12d01 100644 --- a/client_test.go +++ b/client_test.go @@ -552,6 +552,115 @@ func TestBeforeSendTransactionIsCalled(t *testing.T) { assertEqual(t, lastEvent.Contexts["trace"]["span_id"], transaction.SpanID) } +func TestIgnoreErrors(t *testing.T) { + tests := []struct { + name string + ignoreErrors []string + message string + expectDrop bool + }{ + { + name: "No Match", + message: "Foo", + ignoreErrors: []string{"Bar", "Baz"}, + expectDrop: false, + }, + { + name: "Partial Match", + message: "FooBar", + ignoreErrors: []string{"Foo", "Baz"}, + expectDrop: true, + }, + { + name: "Exact Match", + message: "Foo Bar", + ignoreErrors: []string{"\\bFoo\\b", "Baz"}, + expectDrop: true, + }, + { + name: "Wildcard Match", + message: "Foo", + ignoreErrors: []string{"F*", "Bar"}, + expectDrop: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + scope := &ScopeMock{} + transport := &TransportMock{} + client, err := NewClient(ClientOptions{ + Transport: transport, + IgnoreErrors: tt.ignoreErrors, + }) + if err != nil { + t.Fatal(err) + } + + client.CaptureMessage(tt.message, nil, scope) + + dropped := transport.lastEvent == nil + if !(tt.expectDrop == dropped) { + t.Error("expected event to be dropped") + } + }) + } +} + +func TestIgnoreTransactions(t *testing.T) { + tests := []struct { + name string + ignoreTransactions []string + transaction string + expectDrop bool + }{ + { + name: "No Match", + transaction: "Foo", + ignoreTransactions: []string{"Bar", "Baz"}, + expectDrop: false, + }, + { + name: "Partial Match", + transaction: "FooBar", + ignoreTransactions: []string{"Foo", "Baz"}, + expectDrop: true, + }, + { + name: "Exact Match", + transaction: "Foo Bar", + ignoreTransactions: []string{"\\bFoo\\b", "Baz"}, + expectDrop: true, + }, + { + name: "Wildcard Match", + transaction: "Foo", + ignoreTransactions: []string{"F*", "Bar"}, + expectDrop: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + transport := &TransportMock{} + ctx := NewTestContext(ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + Transport: transport, + IgnoreTransactions: tt.ignoreTransactions, + }) + + transaction := StartTransaction(ctx, + tt.transaction, + ) + transaction.Finish() + + dropped := transport.lastEvent == nil + if !(tt.expectDrop == dropped) { + t.Error("expected event to be dropped") + } + }) + } +} + func TestSampleRate(t *testing.T) { tests := []struct { SampleRate float64 diff --git a/integrations.go b/integrations.go index 5d87f6b21..f5ef3ef21 100644 --- a/integrations.go +++ b/integrations.go @@ -177,6 +177,40 @@ func getIgnoreErrorsSuspects(event *Event) []string { return suspects } +// ================================ +// Ignore Transactions Integration +// ================================ + +type ignoreTransactionsIntegration struct { + ignoreTransactions []*regexp.Regexp +} + +func (iei *ignoreTransactionsIntegration) Name() string { + return "IgnoreTransactions" +} + +func (iei *ignoreTransactionsIntegration) SetupOnce(client *Client) { + iei.ignoreTransactions = transformStringsIntoRegexps(client.options.IgnoreTransactions) + client.AddEventProcessor(iei.processor) +} + +func (iei *ignoreTransactionsIntegration) processor(event *Event, hint *EventHint) *Event { + suspect := event.Transaction + if suspect == "" { + return event + } + + for _, pattern := range iei.ignoreTransactions { + if pattern.Match([]byte(suspect)) { + Logger.Printf("Transaction dropped due to being matched by `IgnoreTransactions` option."+ + "| Value matched: %s | Filter used: %s", suspect, pattern) + return nil + } + } + + return event +} + // ================================ // Contextify Frames Integration // ================================ diff --git a/integrations_test.go b/integrations_test.go index 389fe3013..0b97a9b5e 100644 --- a/integrations_test.go +++ b/integrations_test.go @@ -173,6 +173,39 @@ func TestIgnoreErrorsIntegration(t *testing.T) { } } +func TestIgnoreTransactionsIntegration(t *testing.T) { + iei := ignoreTransactionsIntegration{ + ignoreTransactions: []*regexp.Regexp{ + regexp.MustCompile("foo"), + regexp.MustCompile("(?i)bar"), + }, + } + + dropped := &Event{ + Transaction: "foo", + } + + alsoDropped := &Event{ + Transaction: "Bar", + } + + notDropped := &Event{ + Transaction: "dont", + } + + if iei.processor(dropped, &EventHint{}) != nil { + t.Error("Transaction should be dropped") + } + + if iei.processor(alsoDropped, &EventHint{}) != nil { + t.Error("Transaction should be dropped") + } + + if iei.processor(notDropped, &EventHint{}) == nil { + t.Error("Transaction should not be dropped") + } +} + func TestContextifyFrames(t *testing.T) { cfi := contextifyFramesIntegration{ sr: newSourceReader(),