diff --git a/errortracking/capture.go b/errortracking/capture.go index e1135594719e83037e336abbea47f5e368a2903c..e5f0cf80f20535d2415cfb49124326abe0795493 100644 --- a/errortracking/capture.go +++ b/errortracking/capture.go @@ -8,12 +8,7 @@ import ( // Capture will report an error to the error reporting service func Capture(err error, opts ...CaptureOption) { - event := sentry.NewEvent() - event.Level = sentry.LevelError - - for _, v := range opts { - v(event) - } + config, event := applyCaptureOptions(opts) event.Exception = []sentry.Exception{ { @@ -23,5 +18,5 @@ func Capture(err error, opts ...CaptureOption) { }, } - sentry.CaptureEvent(event) + config.stateProvider.hub().CaptureEvent(event) } diff --git a/errortracking/capture_context.go b/errortracking/capture_context.go index fda98ed6e70d476326c664dcc1336689b7e9d151..175bcc509802bc560f3df4bed7cc21c6bd5182d4 100644 --- a/errortracking/capture_context.go +++ b/errortracking/capture_context.go @@ -12,7 +12,7 @@ const sentryExtraKey = "gitlab.CorrelationID" // WithContext will extract information from the context to add to the error func WithContext(ctx context.Context) CaptureOption { - return func(event *sentry.Event) { + return func(config *captureConfig, event *sentry.Event) { correlationID := correlation.ExtractFromContext(ctx) if correlationID != "" { event.Tags[sentryExtraKey] = correlationID diff --git a/errortracking/capture_field.go b/errortracking/capture_field.go index 59ca7ec4e1ab35cb09921e46f9fab42f8658b018..f03a006d918580d414d1a1a131fd4cb7b4447492 100644 --- a/errortracking/capture_field.go +++ b/errortracking/capture_field.go @@ -6,7 +6,7 @@ import ( // WithField allows to add a custom field to the error func WithField(key string, value string) CaptureOption { - return func(event *sentry.Event) { + return func(config *captureConfig, event *sentry.Event) { event.Tags[key] = value } } diff --git a/errortracking/capture_field_test.go b/errortracking/capture_field_test.go index 5a1eb2685c54d270987bc429b76098ad466b1457..21dcd58b9a2d183b90afafe07cf604899769834c 100644 --- a/errortracking/capture_field_test.go +++ b/errortracking/capture_field_test.go @@ -10,8 +10,9 @@ import ( func TestWithField(t *testing.T) { event := sentry.NewEvent() domain := "http://example.com" + config := &captureConfig{} - WithField("domain", domain)(event) + WithField("domain", domain)(config, event) require.True(t, event.Tags["domain"] == domain) } diff --git a/errortracking/capture_options.go b/errortracking/capture_options.go index 7adabc3a589768bd6d14bbf22f67e95c01d1ce55..fbb1b93b0a5c7b3f953269c36c43ae4a18a40627 100644 --- a/errortracking/capture_options.go +++ b/errortracking/capture_options.go @@ -5,4 +5,23 @@ import ( ) // CaptureOption will configure how an error is captured -type CaptureOption func(*sentry.Event) +type CaptureOption func(*captureConfig, *sentry.Event) + +type captureConfig struct { + stateProvider StateProvider +} + +func applyCaptureOptions(opts []CaptureOption) (captureConfig, *sentry.Event) { + event := sentry.NewEvent() + event.Level = sentry.LevelError + + config := captureConfig{ + stateProvider: DefaultStateProvider, + } + + for _, v := range opts { + v(&config, event) + } + + return config, event +} diff --git a/errortracking/capture_options_test.go b/errortracking/capture_options_test.go new file mode 100644 index 0000000000000000000000000000000000000000..561c97f0031b4212988e71505d63610f63fc9d3e --- /dev/null +++ b/errortracking/capture_options_test.go @@ -0,0 +1,42 @@ +package errortracking + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getsentry/sentry-go" +) + +var altStateProvider = NewStateProvider() + +func Test_applyCaptureOptions(t *testing.T) { + tests := []struct { + name string + opts []CaptureOption + wantConfig captureConfig + wantEventLevel sentry.Level + }{ + { + name: "default", + opts: []CaptureOption{}, + wantConfig: captureConfig{stateProvider: DefaultStateProvider}, + wantEventLevel: sentry.LevelError, + }, + { + name: "want-state-provider", + opts: []CaptureOption{WithCaptureStateProvider(altStateProvider)}, + wantConfig: captureConfig{stateProvider: altStateProvider}, + wantEventLevel: sentry.LevelError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotConfig, gotEvent := applyCaptureOptions(tt.opts) + gotEventLevel := gotEvent.Level + + require.Equalf(t, gotConfig, tt.wantConfig, "applyInitializationOptions() = %v, want %v", gotConfig, tt.wantConfig) + require.Equalf(t, gotEventLevel, tt.wantEventLevel, "applyInitializationOptions() Event.Level = %v, want %v", gotEventLevel, tt.wantEventLevel) + }) + } +} diff --git a/errortracking/capture_registry.go b/errortracking/capture_registry.go new file mode 100644 index 0000000000000000000000000000000000000000..0c4768c2c483007e7c2c2983b5cc715be50eaaa8 --- /dev/null +++ b/errortracking/capture_registry.go @@ -0,0 +1,14 @@ +package errortracking + +import ( + "github.com/getsentry/sentry-go" +) + +// WithCaptureStateProvider capture to take place with an alternative registry +// this can be used with `NewExtraErrorTrackingRegistry()` to use a use a non-global +// error tracker. This is especially useful for testing purposes. +func WithCaptureStateProvider(stateProvider StateProvider) CaptureOption { + return func(config *captureConfig, event *sentry.Event) { + config.stateProvider = stateProvider + } +} diff --git a/errortracking/capture_request.go b/errortracking/capture_request.go index 3db06569acfd90a7d54ac365fcc1e2fe6d19a93c..b261f270dc8ac647fecb0836de2295893cdb7a88 100644 --- a/errortracking/capture_request.go +++ b/errortracking/capture_request.go @@ -10,7 +10,7 @@ import ( // WithRequest will capture details of the request along with the error func WithRequest(r *http.Request) CaptureOption { - return func(event *sentry.Event) { + return func(config *captureConfig, event *sentry.Event) { event.Request = redactRequestInfo(r) event.Request.URL = r.URL.String() event.Request.Headers["host"] = r.URL.Hostname() diff --git a/errortracking/initialization_options.go b/errortracking/initialization_options.go index 921d889ed754eb0bb1b7382040922bcd43afd794..35f8106679a58e32e9a7f15323b54367213290c1 100644 --- a/errortracking/initialization_options.go +++ b/errortracking/initialization_options.go @@ -6,13 +6,17 @@ type initializationConfig struct { version string sentryEnvironment string loggerName string + stateProvider StateProvider } // InitializationOption will configure a correlation handler type InitializationOption func(*initializationConfig) func applyInitializationOptions(opts []InitializationOption) initializationConfig { - config := initializationConfig{} + config := initializationConfig{ + stateProvider: DefaultStateProvider, + } + for _, v := range opts { v(&config) } @@ -48,3 +52,12 @@ func WithLoggerName(loggerName string) InitializationOption { config.loggerName = loggerName } } + +// WithStateProvider configures the handle to use an alternative registry. +// This can be used with `NewExtraStateProvider()` to use a use a non-global +// error tracker. This is especially useful for testing purposes. +func WithStateProvider(stateProvider StateProvider) InitializationOption { + return func(config *initializationConfig) { + config.stateProvider = stateProvider + } +} diff --git a/errortracking/initialization_options_test.go b/errortracking/initialization_options_test.go index 1db5f269530d8f0bbf4ebbfa20dd927374edf040..63ab3e89333e6739922e926bedd734f76b60c637 100644 --- a/errortracking/initialization_options_test.go +++ b/errortracking/initialization_options_test.go @@ -6,11 +6,28 @@ import ( "github.com/stretchr/testify/require" ) -func TestWithLoggerName(t *testing.T) { - loggerName := "test-logger" - config := initializationConfig{} +func Test_applyInitializationOptions(t *testing.T) { + tests := []struct { + name string + opts []InitializationOption + want initializationConfig + }{ + { + name: "with-logger-name", + opts: []InitializationOption{WithLoggerName("test-logger")}, + want: initializationConfig{loggerName: "test-logger", stateProvider: DefaultStateProvider}, + }, + { + name: "with-state-provider", + opts: []InitializationOption{WithStateProvider(altStateProvider)}, + want: initializationConfig{stateProvider: altStateProvider}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := applyInitializationOptions(tt.opts) - WithLoggerName(loggerName)(&config) - - require.Equal(t, config.loggerName, loggerName) + require.Equalf(t, got, tt.want, "applyInitializationOptions() = %v, want %v", got, tt.want) + }) + } } diff --git a/errortracking/stateprovider_default.go b/errortracking/stateprovider_default.go new file mode 100644 index 0000000000000000000000000000000000000000..2ef614d276854942374d58fc83b11516c1b4ff58 --- /dev/null +++ b/errortracking/stateprovider_default.go @@ -0,0 +1,25 @@ +package errortracking + +import ( + "github.com/getsentry/sentry-go" +) + +// StateProvider is a container for any state held by the +// errortracker, for example global clients. By default +// DefaultStateProvider is used, but alternative registries can +// be passed via `WithRegistry` and `WithCaptureRegistry` for testing +// purposes. +type StateProvider interface { + hub() *sentry.Hub +} + +// defaultStateProvider is an implementation of StateProvider +// which uses the default sentry global state +type defaultStateProvider struct{} + +func (c *defaultStateProvider) hub() *sentry.Hub { + return sentry.CurrentHub() +} + +// DefaultStateProvider will use a global client for error handling +var DefaultStateProvider StateProvider = &defaultStateProvider{} diff --git a/errortracking/stateprovider_extra.go b/errortracking/stateprovider_extra.go new file mode 100644 index 0000000000000000000000000000000000000000..d6b0c7f800681420e8a1ada1389719a14f9cf8ff --- /dev/null +++ b/errortracking/stateprovider_extra.go @@ -0,0 +1,25 @@ +package errortracking + +import "github.com/getsentry/sentry-go" + +type extraStateProvider struct { + extraHub *sentry.Hub +} + +func (c *extraStateProvider) hub() *sentry.Hub { + return c.extraHub +} + +// NewStateProvider will provide a non-global state container +// for the errortracking module. This can be used in testing +// to avoid side-effects +func NewStateProvider() StateProvider { + client, err := sentry.NewClient(sentry.ClientOptions{}) + if err != nil { + panic(err) + } + + return &extraStateProvider{ + extraHub: sentry.NewHub(client, sentry.NewScope()), + } +}