Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: global tags #718

Merged
merged 7 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -375,6 +377,7 @@ func (client *Client) setupIntegrations() {
new(modulesIntegration),
new(ignoreErrorsIntegration),
new(ignoreTransactionsIntegration),
new(globalTagsIntegration),
}

if client.options.Integrations != nil {
Expand Down
64 changes: 64 additions & 0 deletions integrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import (
"fmt"
"os"
"regexp"
"runtime"
"runtime/debug"
Expand Down Expand Up @@ -325,3 +326,66 @@
}
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))
}

Check warning on line 363 in integrations.go

View check run for this annotation

Codecov / codecov/patch

integrations.go#L362-L363

Added lines #L362 - L363 were not covered by tests

for k, v := range ti.tags {
if _, ok := event.Tags[k]; !ok {
event.Tags[k] = v
cleptric marked this conversation as resolved.
Show resolved Hide resolved
}
}

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
}
48 changes: 48 additions & 0 deletions integrations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package sentry

import (
"encoding/json"
"os"
"path/filepath"
"regexp"
"runtime/debug"
Expand Down Expand Up @@ -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",
)
}