From 932dcf0b89d9c664331abc52fff9af37c11657c9 Mon Sep 17 00:00:00 2001 From: Anton Ovchinnikov Date: Wed, 11 Jan 2023 17:16:25 +0100 Subject: [PATCH] feat: Add BeforeSendTransaction hook (#517) BeforeSendTransaction works similarly to BeforeSend, but it is applied only to transaction events. --- CHANGELOG.md | 5 ++++ _examples/http/main.go | 13 +++++++++- client.go | 19 +++++++++++---- client_test.go | 54 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4442cb5bf..81eff27d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +### Features + +- Add `BeforeSendTransaction` hook to `ClientOptions` ([#517](https://github.com/getsentry/sentry-go/pull/517)) + - Here's [an example](https://github.com/getsentry/sentry-go/blob/master/_examples/http/main.go#L56-L66) of how BeforeSendTransaction can be used to modify or drop transaction events. + ### Bug Fixes - fix(dynamic-sampling): Do not crash in Span.Finish() when Client is empty [#520](https://github.com/getsentry/sentry-go/pull/520) diff --git a/_examples/http/main.go b/_examples/http/main.go index 25c1bbbd9..5c0af03ce 100644 --- a/_examples/http/main.go +++ b/_examples/http/main.go @@ -48,11 +48,22 @@ func run() error { // Useful when getting started or trying to figure something out. Debug: true, BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { - // Here you can inspect/modify events before they are sent. + // Here you can inspect/modify non-transaction events (for example, errors) before they are sent. // Returning nil drops the event. log.Printf("BeforeSend event [%s]", event.EventID) return event }, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + // Here you can inspect/modify transaction events before they are sent. + // Returning nil drops the event. + if strings.Contains(event.Message, "test-transaction") { + // Drop the transaction + return nil + } + event.Message += " [example]" + log.Printf("BeforeSendTransaction event [%s]", event.EventID) + return event + }, // Enable tracing EnableTracing: true, // Specify either a TracesSampleRate... diff --git a/client.go b/client.go index 2c580b037..49e2cd5ff 100644 --- a/client.go +++ b/client.go @@ -140,8 +140,10 @@ type ClientOptions struct { SendDefaultPII bool // BeforeSend is called before error events are sent to Sentry. // Use it to mutate the event or return nil to discard the event. - // See EventProcessor if you need to mutate transactions. BeforeSend func(event *Event, hint *EventHint) *Event + // BeforeSendTransaction is called before transaction events are sent to Sentry. + // Use it to mutate the transaction or return nil to discard the transaction. + BeforeSendTransaction func(event *Event, hint *EventHint) *Event // Before breadcrumb add callback. BeforeBreadcrumb func(breadcrumb *Breadcrumb, hint *BreadcrumbHint) *Breadcrumb // Integrations to be installed on the current Client, receives default @@ -570,11 +572,18 @@ func (client *Client) processEvent(event *Event, hint *EventHint, scope EventMod return nil } - // As per spec, transactions do not go through BeforeSend. - if event.Type != transactionType && options.BeforeSend != nil { - if hint == nil { - hint = &EventHint{} + // Apply beforeSend* processors + if hint == nil { + hint = &EventHint{} + } + if event.Type == transactionType && options.BeforeSendTransaction != nil { + // Transaction events + if event = options.BeforeSendTransaction(event, hint); event == nil { + Logger.Println("Transaction dropped due to BeforeSendTransaction callback.") + return nil } + } else if event.Type != transactionType && options.BeforeSend != nil { + // All other events if event = options.BeforeSend(event, hint); event == nil { Logger.Println("Event dropped due to BeforeSend callback.") return nil diff --git a/client_test.go b/client_test.go index 73f1f126d..5afce8ae7 100644 --- a/client_test.go +++ b/client_test.go @@ -383,6 +383,60 @@ func TestBeforeSendGetAccessToEventHint(t *testing.T) { assertEqual(t, transport.lastEvent.Message, "customComplexError: Foo 42") } +func TestBeforeSendTransactionCanDropTransaction(t *testing.T) { + transport := &TransportMock{} + ctx := NewTestContext(ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + Transport: transport, + BeforeSend: func(event *Event, hint *EventHint) *Event { + t.Error("beforeSend should not be called") + return event + }, + BeforeSendTransaction: func(event *Event, hint *EventHint) *Event { + assertEqual(t, event.Transaction, "Foo") + return nil + }, + }) + + transaction := StartTransaction(ctx, + "Foo", + ) + transaction.Finish() + + if transport.lastEvent != nil { + t.Error("expected event to be dropped") + } +} + +func TestBeforeSendTransactionIsCalled(t *testing.T) { + transport := &TransportMock{} + ctx := NewTestContext(ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + Transport: transport, + BeforeSend: func(event *Event, hint *EventHint) *Event { + t.Error("beforeSend should not be called") + return event + }, + BeforeSendTransaction: func(event *Event, hint *EventHint) *Event { + assertEqual(t, event.Transaction, "Foo") + event.Transaction = "Bar" + return event + }, + }) + + transaction := StartTransaction(ctx, + "Foo", + ) + transaction.Finish() + + lastEvent := transport.lastEvent + assertEqual(t, lastEvent.Transaction, "Bar") + // Make sure it's the same span + assertEqual(t, lastEvent.Contexts["trace"]["span_id"], transaction.SpanID) +} + func TestSampleRate(t *testing.T) { tests := []struct { SampleRate float64