diff --git a/client.go b/client.go index 6f7b62f59..0e14658af 100644 --- a/client.go +++ b/client.go @@ -224,6 +224,8 @@ type ClientOptions struct { // is not optimized for long chains either. The top-level error together with a // stack trace is often the most useful information. MaxErrorDepth int + // Default event tags. These are overridden by tags set on a scope. + Tags map[string]string } // Client is the underlying processor that is used by the main API and Hub @@ -375,6 +377,7 @@ func (client *Client) setupIntegrations() { new(modulesIntegration), new(ignoreErrorsIntegration), new(ignoreTransactionsIntegration), + new(globalTagsIntegration), } if client.options.Integrations != nil { diff --git a/integrations.go b/integrations.go index f5ef3ef21..046ef0a0e 100644 --- a/integrations.go +++ b/integrations.go @@ -2,6 +2,7 @@ package sentry import ( "fmt" + "os" "regexp" "runtime" "runtime/debug" @@ -325,3 +326,66 @@ func (cfi *contextifyFramesIntegration) addContextLinesToFrame(frame Frame, line } return frame } + +// ================================ +// Global Tags Integration +// ================================ + +const envTagsPrefix = "SENTRY_TAGS_" + +type globalTagsIntegration struct { + tags map[string]string + envTags map[string]string +} + +func (ti *globalTagsIntegration) Name() string { + return "GlobalTags" +} + +func (ti *globalTagsIntegration) SetupOnce(client *Client) { + ti.tags = make(map[string]string, len(client.options.Tags)) + for k, v := range client.options.Tags { + ti.tags[k] = v + } + + ti.envTags = loadEnvTags() + + client.AddEventProcessor(ti.processor) +} + +func (ti *globalTagsIntegration) processor(event *Event, hint *EventHint) *Event { + if len(ti.tags) == 0 && len(ti.envTags) == 0 { + return event + } + + if event.Tags == nil { + event.Tags = make(map[string]string, len(ti.tags)+len(ti.envTags)) + } + + for k, v := range ti.tags { + if _, ok := event.Tags[k]; !ok { + event.Tags[k] = v + } + } + + for k, v := range ti.envTags { + if _, ok := event.Tags[k]; !ok { + event.Tags[k] = v + } + } + + return event +} + +func loadEnvTags() map[string]string { + tags := map[string]string{} + for _, pair := range os.Environ() { + parts := strings.Split(pair, "=") + if !strings.HasPrefix(parts[0], envTagsPrefix) { + continue + } + tag := strings.TrimPrefix(parts[0], envTagsPrefix) + tags[tag] = parts[1] + } + return tags +} diff --git a/integrations_test.go b/integrations_test.go index 0b97a9b5e..bd4638821 100644 --- a/integrations_test.go +++ b/integrations_test.go @@ -2,6 +2,7 @@ package sentry import ( "encoding/json" + "os" "path/filepath" "regexp" "runtime/debug" @@ -421,3 +422,50 @@ func TestEnvironmentIntegrationDoesNotOverrideExistingContexts(t *testing.T) { t.Errorf(`contexts["custom"]["key"] = %#v, want "value"`, contexts["custom"]["key"]) } } + +func TestGlobalTagsIntegration(t *testing.T) { + os.Setenv("SENTRY_TAGS_foo", "foo_value_env") + os.Setenv("SENTRY_TAGS_bar", "bar_value_env") + os.Setenv("SENTRY_TAGS_baz", "baz_value_env") + defer os.Unsetenv("SENTRY_TAGS_foo") + defer os.Unsetenv("SENTRY_TAGS_bar") + defer os.Unsetenv("SENTRY_TAGS_baz") + + transport := &TransportMock{} + client, err := NewClient(ClientOptions{ + Transport: transport, + Tags: map[string]string{ + "foo": "foo_value_client_options", + "baz": "baz_value_client_options", + }, + Integrations: func([]Integration) []Integration { + return []Integration{new(globalTagsIntegration)} + }, + }) + if err != nil { + t.Fatal(err) + } + + scope := NewScope() + scope.SetTags(map[string]string{"foo": "foo_value_scope"}) + + event := NewEvent() + event.Message = "event message" + client.CaptureEvent(event, nil, scope) + + assertEqual(t, + transport.lastEvent.Tags["foo"], + "foo_value_scope", + "scope tag should override any global tag", + ) + assertEqual(t, + transport.lastEvent.Tags["bar"], + "bar_value_env", + "env tag present if not overridden by scope or client options tags", + ) + assertEqual(t, + transport.lastEvent.Tags["baz"], + "baz_value_client_options", + "client options tag present if not overridden by scope and overrides env tag", + ) +}