diff --git a/errortracking/capture.go b/errortracking/capture.go index e1135594719e83037e336abbea47f5e368a2903c..04eda69646c71d1289dbd0db0719a7e5d456a20b 100644 --- a/errortracking/capture.go +++ b/errortracking/capture.go @@ -1,27 +1,6 @@ package errortracking -import ( - "reflect" - - "github.com/getsentry/sentry-go" -) - -// Capture will report an error to the error reporting service +// Capture reports 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) - } - - event.Exception = []sentry.Exception{ - { - Type: reflect.TypeOf(err).String(), - Value: err.Error(), - Stacktrace: sentry.ExtractStacktrace(err), - }, - } - - sentry.CaptureEvent(event) + DefaultTracker().Capture(err, opts...) } 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..d659645850dc93b8d87239dfdfc8c3fde1cbb885 100644 --- a/errortracking/capture_options.go +++ b/errortracking/capture_options.go @@ -5,4 +5,19 @@ import ( ) // CaptureOption will configure how an error is captured -type CaptureOption func(*sentry.Event) +type CaptureOption func(*captureConfig, *sentry.Event) + +type captureConfig struct{} + +func applyCaptureOptions(opts []CaptureOption) (captureConfig, *sentry.Event) { + event := sentry.NewEvent() + event.Level = sentry.LevelError + + config := captureConfig{} + + 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..5652ac17a079ea689de91c880a765a556892660d --- /dev/null +++ b/errortracking/capture_options_test.go @@ -0,0 +1,33 @@ +package errortracking + +import ( + "testing" + + "github.com/getsentry/sentry-go" + "github.com/stretchr/testify/require" +) + +func Test_applyCaptureOptions(t *testing.T) { + tests := []struct { + name string + opts []CaptureOption + wantConfig captureConfig + wantEventLevel sentry.Level + }{ + { + name: "default", + opts: []CaptureOption{}, + wantConfig: captureConfig{}, + 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, "applyCaptureOptions() = %v, want %v", gotConfig, tt.wantConfig) + require.Equalf(t, gotEventLevel, tt.wantEventLevel, "applyCaptureOptions() Event.Level = %v, want %v", gotEventLevel, tt.wantEventLevel) + }) + } +} 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.go b/errortracking/initialization.go index 9b03b62139e84edfa14f00c9a50b9085b7df7807..eaeff35a811b0bea4efebb6f2ea012066387cf7c 100644 --- a/errortracking/initialization.go +++ b/errortracking/initialization.go @@ -4,18 +4,15 @@ import ( "github.com/getsentry/sentry-go" ) -// Initialize will initialize error reporting -func Initialize(opts ...InitializationOption) error { - config := applyInitializationOptions(opts) - - sentryClientOptions := sentry.ClientOptions{ - Dsn: config.sentryDSN, - Environment: config.sentryEnvironment, - } +type InitializationOption = TrackerOption - if config.version != "" { - sentryClientOptions.Release = config.version +// Initialize initializes global error tracking. +// Call this once on the program start. +func Initialize(opts ...InitializationOption) error { + err := sentry.Init(trackerOptionsToSentryClientOptions(opts...)) + if err != nil { + return err } - - return sentry.Init(sentryClientOptions) + defaultTracker = newSentryTracker(sentry.CurrentHub()) + return nil } diff --git a/errortracking/initialization_options.go b/errortracking/initialization_options.go deleted file mode 100644 index 921d889ed754eb0bb1b7382040922bcd43afd794..0000000000000000000000000000000000000000 --- a/errortracking/initialization_options.go +++ /dev/null @@ -1,50 +0,0 @@ -package errortracking - -// The configuration for InjectCorrelationID -type initializationConfig struct { - sentryDSN string - version string - sentryEnvironment string - loggerName string -} - -// InitializationOption will configure a correlation handler -type InitializationOption func(*initializationConfig) - -func applyInitializationOptions(opts []InitializationOption) initializationConfig { - config := initializationConfig{} - for _, v := range opts { - v(&config) - } - - return config -} - -// WithSentryDSN sets the sentry data source name -func WithSentryDSN(sentryDSN string) InitializationOption { - return func(config *initializationConfig) { - config.sentryDSN = sentryDSN - } -} - -// WithVersion is used to configure the version of the service -// that is currently running -func WithVersion(version string) InitializationOption { - return func(config *initializationConfig) { - config.version = version - } -} - -// WithSentryEnvironment sets the sentry environment -func WithSentryEnvironment(sentryEnvironment string) InitializationOption { - return func(config *initializationConfig) { - config.sentryEnvironment = sentryEnvironment - } -} - -// WithLoggerName sets the logger name -func WithLoggerName(loggerName string) InitializationOption { - return func(config *initializationConfig) { - config.loggerName = loggerName - } -} diff --git a/errortracking/initialization_options_test.go b/errortracking/initialization_options_test.go deleted file mode 100644 index 1db5f269530d8f0bbf4ebbfa20dd927374edf040..0000000000000000000000000000000000000000 --- a/errortracking/initialization_options_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package errortracking - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestWithLoggerName(t *testing.T) { - loggerName := "test-logger" - config := initializationConfig{} - - WithLoggerName(loggerName)(&config) - - require.Equal(t, config.loggerName, loggerName) -} diff --git a/errortracking/sentry_tracker.go b/errortracking/sentry_tracker.go new file mode 100644 index 0000000000000000000000000000000000000000..92a5b9ee1fd0c54649b8725f589a65820ca984fe --- /dev/null +++ b/errortracking/sentry_tracker.go @@ -0,0 +1,30 @@ +package errortracking + +import ( + "reflect" + + "github.com/getsentry/sentry-go" +) + +type sentryTracker struct { + hub *sentry.Hub +} + +func newSentryTracker(hub *sentry.Hub) *sentryTracker { + return &sentryTracker{ + hub: hub, + } +} + +func (s *sentryTracker) Capture(err error, opts ...CaptureOption) { + _, event := applyCaptureOptions(opts) + + event.Exception = []sentry.Exception{ + { + Type: reflect.TypeOf(err).String(), + Value: err.Error(), + Stacktrace: sentry.ExtractStacktrace(err), + }, + } + s.hub.CaptureEvent(event) +} diff --git a/errortracking/tracker.go b/errortracking/tracker.go new file mode 100644 index 0000000000000000000000000000000000000000..9a45009777d594e7b7861be6139c9c5a2ac24c6f --- /dev/null +++ b/errortracking/tracker.go @@ -0,0 +1,38 @@ +package errortracking + +import "github.com/getsentry/sentry-go" + +// Tracker is an abstraction for error tracking. +type Tracker interface { + // Capture reports an error to the error reporting service + Capture(err error, opts ...CaptureOption) +} + +var ( + defaultTracker = newSentryTracker(sentry.CurrentHub()) +) + +// DefaultTracker returns the default global error tracker. +func DefaultTracker() Tracker { + return defaultTracker +} + +// NewTracker constructs a new Tracker with the provided options. +func NewTracker(opts ...TrackerOption) (Tracker, error) { + client, err := sentry.NewClient(trackerOptionsToSentryClientOptions(opts...)) + if err != nil { + return nil, err + } + hub := sentry.NewHub(client, sentry.NewScope()) + return newSentryTracker(hub), nil +} + +func trackerOptionsToSentryClientOptions(opts ...TrackerOption) sentry.ClientOptions { + config := applyTrackerOptions(opts) + + return sentry.ClientOptions{ + Dsn: config.sentryDSN, + Release: config.version, + Environment: config.sentryEnvironment, + } +} diff --git a/errortracking/tracker_options.go b/errortracking/tracker_options.go new file mode 100644 index 0000000000000000000000000000000000000000..839f775de1cfb3e1d8e8f38c8ac8102ca61bf971 --- /dev/null +++ b/errortracking/tracker_options.go @@ -0,0 +1,51 @@ +package errortracking + +// The configuration for Tracker. +type trackerConfig struct { + sentryDSN string + version string + sentryEnvironment string + loggerName string +} + +// TrackerOption will configure a Tracker. +type TrackerOption func(*trackerConfig) + +func applyTrackerOptions(opts []TrackerOption) trackerConfig { + config := trackerConfig{} + + for _, v := range opts { + v(&config) + } + + return config +} + +// WithSentryDSN sets the sentry data source name +func WithSentryDSN(sentryDSN string) TrackerOption { + return func(config *trackerConfig) { + config.sentryDSN = sentryDSN + } +} + +// WithVersion is used to configure the version of the service +// that is currently running +func WithVersion(version string) TrackerOption { + return func(config *trackerConfig) { + config.version = version + } +} + +// WithSentryEnvironment sets the sentry environment +func WithSentryEnvironment(sentryEnvironment string) TrackerOption { + return func(config *trackerConfig) { + config.sentryEnvironment = sentryEnvironment + } +} + +// WithLoggerName sets the logger name +func WithLoggerName(loggerName string) TrackerOption { + return func(config *trackerConfig) { + config.loggerName = loggerName + } +} diff --git a/errortracking/tracker_options_test.go b/errortracking/tracker_options_test.go new file mode 100644 index 0000000000000000000000000000000000000000..9a754b91e2bd689e644823c27dd52344c1bc6d79 --- /dev/null +++ b/errortracking/tracker_options_test.go @@ -0,0 +1,38 @@ +package errortracking + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_applyTrackerOptions(t *testing.T) { + tests := []struct { + name string + opts []TrackerOption + want trackerConfig + }{ + { + name: "with-all-options", + opts: []TrackerOption{ + WithSentryDSN("dsn"), + WithVersion("ver"), + WithSentryEnvironment("env"), + WithLoggerName("test-logger"), + }, + want: trackerConfig{ + sentryDSN: "dsn", + version: "ver", + sentryEnvironment: "env", + loggerName: "test-logger", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := applyTrackerOptions(tt.opts) + + require.Equalf(t, got, tt.want, "applyTrackerOptions() = %v, want %v", got, tt.want) + }) + } +}