From 52159489b52c8170494bbecf7d3b09e69adbd9a4 Mon Sep 17 00:00:00 2001 From: Oliver Powell Date: Thu, 7 Sep 2023 15:09:13 -0300 Subject: [PATCH 1/3] feat: support IgnoreErrors and IgnoreTransactions client options --- client.go | 70 ++++++++++++++++++++++++------- client_test.go | 110 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 14 deletions(-) diff --git a/client.go b/client.go index 3804c8a64..60f3cbcc5 100644 --- a/client.go +++ b/client.go @@ -9,6 +9,7 @@ import ( "math/rand" "net/http" "os" + "regexp" "sort" "strings" "sync" @@ -136,10 +137,13 @@ type ClientOptions struct { // The sample rate for profiling traces in the range [0.0, 1.0]. // This is relative to TracesSampleRate - it is a ratio of profiled traces out of all sampled traces. ProfilesSampleRate float64 - // List of regexp strings that will be used to match against event's message - // and if applicable, caught errors type and value. - // If the match is found, then a whole event will be dropped. + // List of regexp strings that will be used to match against an event's + // message and if applicable, the caught error's type and value. If a match + // is found, then the 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 @@ -226,13 +230,15 @@ type ClientOptions struct { // Client is the underlying processor that is used by the main API and Hub // instances. It must be created with NewClient. type Client struct { - mu sync.RWMutex - options ClientOptions - dsn *Dsn - eventProcessors []EventProcessor - integrations []Integration - sdkIdentifier string - sdkVersion string + mu sync.RWMutex + options ClientOptions + dsn *Dsn + eventProcessors []EventProcessor + integrations []Integration + sdkIdentifier string + sdkVersion string + ignoreErrors []*regexp.Regexp + ignoreTransactions []*regexp.Regexp // Transport is read-only. Replacing the transport of an existing client is // not supported, create a new client instead. Transport Transport @@ -328,11 +334,31 @@ func NewClient(options ClientOptions) (*Client, error) { } } + ignoreErrorsRegexes := make([]*regexp.Regexp, len(options.IgnoreErrors)) + for i, re := range options.IgnoreErrors { + compiled, err := regexp.Compile(re) + if err != nil { + return nil, err + } + ignoreErrorsRegexes[i] = compiled + } + + ignoreTransactionsRegexes := make([]*regexp.Regexp, len(options.IgnoreTransactions)) + for i, re := range options.IgnoreTransactions { + compiled, err := regexp.Compile(re) + if err != nil { + return nil, err + } + ignoreTransactionsRegexes[i] = compiled + } + client := Client{ - options: options, - dsn: dsn, - sdkIdentifier: sdkIdentifier, - sdkVersion: SDKVersion, + options: options, + dsn: dsn, + sdkIdentifier: sdkIdentifier, + sdkVersion: SDKVersion, + ignoreErrors: ignoreErrorsRegexes, + ignoreTransactions: ignoreTransactionsRegexes, } client.setupTransport() @@ -627,6 +653,22 @@ func (client *Client) processEvent(event *Event, hint *EventHint, scope EventMod } } + if event.Type == transactionType { + for _, re := range client.ignoreTransactions { + if re.MatchString(event.Transaction) { + Logger.Println("Transaction dropped due to IgnoreTransactions match.") + return nil + } + } + } else { + for _, re := range client.ignoreErrors { + if re.MatchString(event.Message) { + Logger.Println("Event dropped due to IgnoreErrors match.") + return nil + } + } + } + client.Transport.SendEvent(event) return &event.EventID diff --git a/client_test.go b/client_test.go index 5a4f29e64..4d852b6f1 100644 --- a/client_test.go +++ b/client_test.go @@ -552,6 +552,116 @@ 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 From 3b92b116ad17e1b9581510943136a90ec7837a40 Mon Sep 17 00:00:00 2001 From: Oliver Powell Date: Thu, 14 Sep 2023 14:04:15 -0300 Subject: [PATCH 2/3] linting fix --- client_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/client_test.go b/client_test.go index 4d852b6f1..7f3f12d01 100644 --- a/client_test.go +++ b/client_test.go @@ -659,7 +659,6 @@ func TestIgnoreTransactions(t *testing.T) { } }) } - } func TestSampleRate(t *testing.T) { From 1bac9ff9a8a15d7d2e534ddc923bb7fe833d135c Mon Sep 17 00:00:00 2001 From: Oliver Powell Date: Fri, 15 Sep 2023 17:30:42 -0300 Subject: [PATCH 3/3] rework Define another integration rather for IgnoreTransactions. IgnoreErrors is already implemented. --- client.go | 68 ++++++++++---------------------------------- integrations.go | 34 ++++++++++++++++++++++ integrations_test.go | 33 +++++++++++++++++++++ 3 files changed, 82 insertions(+), 53 deletions(-) diff --git a/client.go b/client.go index 60f3cbcc5..6f7b62f59 100644 --- a/client.go +++ b/client.go @@ -9,7 +9,6 @@ import ( "math/rand" "net/http" "os" - "regexp" "sort" "strings" "sync" @@ -137,9 +136,9 @@ type ClientOptions struct { // The sample rate for profiling traces in the range [0.0, 1.0]. // This is relative to TracesSampleRate - it is a ratio of profiled traces out of all sampled traces. ProfilesSampleRate float64 - // List of regexp strings that will be used to match against an event's - // message and if applicable, the caught error's type and value. If a match - // is found, then the whole event will be dropped. + // List of regexp strings that will be used to match against event's message + // 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. @@ -230,15 +229,13 @@ type ClientOptions struct { // Client is the underlying processor that is used by the main API and Hub // instances. It must be created with NewClient. type Client struct { - mu sync.RWMutex - options ClientOptions - dsn *Dsn - eventProcessors []EventProcessor - integrations []Integration - sdkIdentifier string - sdkVersion string - ignoreErrors []*regexp.Regexp - ignoreTransactions []*regexp.Regexp + mu sync.RWMutex + options ClientOptions + dsn *Dsn + eventProcessors []EventProcessor + integrations []Integration + sdkIdentifier string + sdkVersion string // Transport is read-only. Replacing the transport of an existing client is // not supported, create a new client instead. Transport Transport @@ -334,31 +331,11 @@ func NewClient(options ClientOptions) (*Client, error) { } } - ignoreErrorsRegexes := make([]*regexp.Regexp, len(options.IgnoreErrors)) - for i, re := range options.IgnoreErrors { - compiled, err := regexp.Compile(re) - if err != nil { - return nil, err - } - ignoreErrorsRegexes[i] = compiled - } - - ignoreTransactionsRegexes := make([]*regexp.Regexp, len(options.IgnoreTransactions)) - for i, re := range options.IgnoreTransactions { - compiled, err := regexp.Compile(re) - if err != nil { - return nil, err - } - ignoreTransactionsRegexes[i] = compiled - } - client := Client{ - options: options, - dsn: dsn, - sdkIdentifier: sdkIdentifier, - sdkVersion: SDKVersion, - ignoreErrors: ignoreErrorsRegexes, - ignoreTransactions: ignoreTransactionsRegexes, + options: options, + dsn: dsn, + sdkIdentifier: sdkIdentifier, + sdkVersion: SDKVersion, } client.setupTransport() @@ -397,6 +374,7 @@ func (client *Client) setupIntegrations() { new(environmentIntegration), new(modulesIntegration), new(ignoreErrorsIntegration), + new(ignoreTransactionsIntegration), } if client.options.Integrations != nil { @@ -653,22 +631,6 @@ func (client *Client) processEvent(event *Event, hint *EventHint, scope EventMod } } - if event.Type == transactionType { - for _, re := range client.ignoreTransactions { - if re.MatchString(event.Transaction) { - Logger.Println("Transaction dropped due to IgnoreTransactions match.") - return nil - } - } - } else { - for _, re := range client.ignoreErrors { - if re.MatchString(event.Message) { - Logger.Println("Event dropped due to IgnoreErrors match.") - return nil - } - } - } - client.Transport.SendEvent(event) return &event.EventID 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(),