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: Attachments support #670

Merged
merged 2 commits into from Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 4 additions & 4 deletions client_test.go
Expand Up @@ -79,7 +79,7 @@ func TestCaptureMessageEmptyString(t *testing.T) {
}
got := transport.lastEvent
opts := cmp.Options{
cmpopts.IgnoreFields(Event{}, "sdkMetaData"),
cmpopts.IgnoreFields(Event{}, "sdkMetaData", "attachments"),
cmp.Transformer("SimplifiedEvent", func(e *Event) *Event {
return &Event{
Exception: e.Exception,
Expand Down Expand Up @@ -286,7 +286,7 @@ func TestCaptureEvent(t *testing.T) {
},
}
got := transport.lastEvent
opts := cmp.Options{cmpopts.IgnoreFields(Event{}, "Release", "sdkMetaData")}
opts := cmp.Options{cmpopts.IgnoreFields(Event{}, "Release", "sdkMetaData", "attachments")}
if diff := cmp.Diff(want, got, opts); diff != "" {
t.Errorf("Event mismatch (-want +got):\n%s", diff)
}
Expand Down Expand Up @@ -314,7 +314,7 @@ func TestCaptureEventNil(t *testing.T) {
}
got := transport.lastEvent
opts := cmp.Options{
cmpopts.IgnoreFields(Event{}, "sdkMetaData"),
cmpopts.IgnoreFields(Event{}, "sdkMetaData", "attachments"),
cmp.Transformer("SimplifiedEvent", func(e *Event) *Event {
return &Event{
Exception: e.Exception,
Expand Down Expand Up @@ -639,7 +639,7 @@ func TestRecover(t *testing.T) {
}
got := events[0]
opts := cmp.Options{
cmpopts.IgnoreFields(Event{}, "sdkMetaData"),
cmpopts.IgnoreFields(Event{}, "sdkMetaData", "attachments"),
cmp.Transformer("SimplifiedEvent", func(e *Event) *Event {
return &Event{
Message: e.Message,
Expand Down
2 changes: 1 addition & 1 deletion fasthttp/sentryfasthttp_test.go
Expand Up @@ -207,7 +207,7 @@ func TestIntegration(t *testing.T) {
sentry.Event{},
"Contexts", "EventID", "Extra", "Platform", "Modules",
"Release", "Sdk", "ServerName", "Tags", "Timestamp",
"sdkMetaData",
"sdkMetaData", "attachments",
),
cmpopts.IgnoreMapEntries(func(k string, v string) bool {
// fasthttp changed Content-Length behavior in
Expand Down
4 changes: 2 additions & 2 deletions gin/sentrygin_test.go
Expand Up @@ -330,7 +330,7 @@ func TestIntegration(t *testing.T) {
sentry.Event{},
"Contexts", "EventID", "Extra", "Platform", "Modules",
"Release", "Sdk", "ServerName", "Tags", "Timestamp",
"sdkMetaData",
"sdkMetaData", "attachments",
),
cmpopts.IgnoreFields(
sentry.Request{},
Expand All @@ -354,7 +354,7 @@ func TestIntegration(t *testing.T) {
sentry.Event{},
"Contexts", "EventID", "Platform", "Modules",
"Release", "Sdk", "ServerName", "Timestamp",
"sdkMetaData", "StartTime", "Spans",
"sdkMetaData", "StartTime", "Spans", "attachments",
),
cmpopts.IgnoreFields(
sentry.Request{},
Expand Down
2 changes: 1 addition & 1 deletion http/sentryhttp_test.go
Expand Up @@ -210,7 +210,7 @@ func TestIntegration(t *testing.T) {
sentry.Event{},
"Contexts", "EventID", "Extra", "Platform", "Modules",
"Release", "Sdk", "ServerName", "Tags", "Timestamp",
"sdkMetaData",
"sdkMetaData", "attachments",
),
cmpopts.IgnoreFields(
sentry.Request{},
Expand Down
9 changes: 9 additions & 0 deletions interfaces.go
Expand Up @@ -108,6 +108,14 @@ func (b *Breadcrumb) MarshalJSON() ([]byte, error) {
return json.Marshal((*breadcrumb)(b))
}

// Attachment allows associating files with your events to aid in investigation.
// An event may contain one or more attachments.
type Attachment struct {
Filename string
ContentType string
Payload []byte
}

// User describes the user associated with an Event. If this is used, at least
// an ID or an IP address should be provided.
type User struct {
Expand Down Expand Up @@ -326,6 +334,7 @@ type Event struct {
// The fields below are not part of the final JSON payload.

sdkMetaData SDKMetaData
attachments []*Attachment
}

// SetException appends the unwrapped errors to the event's exception list.
Expand Down
2 changes: 1 addition & 1 deletion logrus/logrusentry_test.go
Expand Up @@ -249,7 +249,7 @@ func Test_entryToEvent(t *testing.T) {
got := h.entryToEvent(tt.entry)
opts := cmp.Options{
cmpopts.IgnoreFields(sentry.Event{},
"sdkMetaData",
"sdkMetaData", "attachments",
),
}
if d := cmp.Diff(tt.want, got, opts); d != "" {
Expand Down
24 changes: 24 additions & 0 deletions scope.go
Expand Up @@ -25,6 +25,7 @@ import (
type Scope struct {
mu sync.RWMutex
breadcrumbs []*Breadcrumb
attachments []*Attachment
user User
tags map[string]string
contexts map[string]Context
Expand All @@ -48,6 +49,7 @@ type Scope struct {
func NewScope() *Scope {
scope := Scope{
breadcrumbs: make([]*Breadcrumb, 0),
attachments: make([]*Attachment, 0),
tags: make(map[string]string),
contexts: make(map[string]Context),
extra: make(map[string]interface{}),
Expand Down Expand Up @@ -81,6 +83,22 @@ func (scope *Scope) ClearBreadcrumbs() {
scope.breadcrumbs = []*Breadcrumb{}
}

// AddAttachment adds new attachment to the current scope.
func (scope *Scope) AddAttachment(attachment *Attachment) {
scope.mu.Lock()
defer scope.mu.Unlock()

scope.attachments = append(scope.attachments, attachment)
}

// ClearAttachments clears all attachments from the current scope.
func (scope *Scope) ClearAttachments() {
scope.mu.Lock()
defer scope.mu.Unlock()

scope.attachments = []*Attachment{}
}

// SetUser sets the user for the current scope.
func (scope *Scope) SetUser(user User) {
scope.mu.Lock()
Expand Down Expand Up @@ -283,6 +301,8 @@ func (scope *Scope) Clone() *Scope {
clone.user = scope.user
clone.breadcrumbs = make([]*Breadcrumb, len(scope.breadcrumbs))
copy(clone.breadcrumbs, scope.breadcrumbs)
clone.attachments = make([]*Attachment, len(scope.attachments))
copy(clone.attachments, scope.attachments)
for key, value := range scope.tags {
clone.tags[key] = value
}
Expand Down Expand Up @@ -323,6 +343,10 @@ func (scope *Scope) ApplyToEvent(event *Event, hint *EventHint) *Event {
event.Breadcrumbs = append(event.Breadcrumbs, scope.breadcrumbs...)
}

if len(scope.attachments) > 0 {
event.attachments = append(event.attachments, scope.attachments...)
}

if len(scope.tags) > 0 {
if event.Tags == nil {
event.Tags = make(map[string]string, len(scope.tags))
Expand Down
1 change: 1 addition & 0 deletions scope_concurrency_test.go
Expand Up @@ -54,6 +54,7 @@ func touchScope(scope *sentry.Scope, x int) {
scope.SetLevel(sentry.LevelDebug)
scope.SetFingerprint([]string{"foo"})
scope.AddBreadcrumb(&sentry.Breadcrumb{Message: "foo"}, 100)
scope.AddAttachment(&sentry.Attachment{Filename: "foo.txt"})
scope.SetUser(sentry.User{ID: "foo"})
scope.SetRequest(httptest.NewRequest("GET", "/foo", nil))

Expand Down
55 changes: 55 additions & 0 deletions scope_test.go
Expand Up @@ -13,6 +13,12 @@ var testNow = time.Now().UTC()

func fillScopeWithData(scope *Scope) *Scope {
scope.breadcrumbs = []*Breadcrumb{{Timestamp: testNow, Message: "scopeBreadcrumbMessage"}}
scope.attachments = []*Attachment{
{
Filename: "scope-attachment.txt",
Payload: []byte("Scope attachment contents."),
},
}
scope.user = User{ID: "1337"}
scope.tags = map[string]string{"scopeTagKey": "scopeTagValue"}
scope.contexts = map[string]Context{
Expand All @@ -28,6 +34,12 @@ func fillScopeWithData(scope *Scope) *Scope {

func fillEventWithData(event *Event) *Event {
event.Breadcrumbs = []*Breadcrumb{{Timestamp: testNow, Message: "eventBreadcrumbMessage"}}
event.attachments = []*Attachment{
{
Filename: "event-attachment.txt",
Payload: []byte("Event attachment contents."),
},
}
event.User = User{ID: "42"}
event.Tags = map[string]string{"eventTagKey": "eventTagValue"}
event.Contexts = map[string]Context{
Expand Down Expand Up @@ -357,6 +369,25 @@ func TestAddBreadcrumbAddsTimestamp(t *testing.T) {
}
}

func TestAddAttachmentAddsAttachment(t *testing.T) {
scope := NewScope()
scope.AddAttachment(&Attachment{Filename: "test.txt", Payload: []byte("Hello, World")})
assertEqual(t, []*Attachment{{Filename: "test.txt", Payload: []byte("Hello, World")}}, scope.attachments)
}

func TestAddAttachmentAppendsAttachment(t *testing.T) {
scope := NewScope()
scope.AddAttachment(&Attachment{Filename: "test1.txt", Payload: []byte("Hello, World!")})
scope.AddAttachment(&Attachment{Filename: "test2.txt", Payload: []byte("Hello, World?")})
scope.AddAttachment(&Attachment{Filename: "test3.txt", Payload: []byte("Hello, World.")})

assertEqual(t, []*Attachment{
{Filename: "test1.txt", Payload: []byte("Hello, World!")},
{Filename: "test2.txt", Payload: []byte("Hello, World?")},
{Filename: "test3.txt", Payload: []byte("Hello, World.")},
}, scope.attachments)
}

func TestScopeBasicInheritance(t *testing.T) {
scope := NewScope()
scope.SetExtra("a", 1)
Expand All @@ -381,6 +412,7 @@ func TestScopeParentChangedInheritance(t *testing.T) {
clone.SetLevel(LevelDebug)
clone.SetFingerprint([]string{"foo"})
clone.AddBreadcrumb(&Breadcrumb{Timestamp: testNow, Message: "foo"}, maxBreadcrumbs)
clone.AddAttachment(&Attachment{Filename: "foo.txt", Payload: []byte("foo")})
clone.SetUser(User{ID: "foo"})
r1 := httptest.NewRequest("GET", "/foo", nil)
clone.SetRequest(r1)
Expand All @@ -391,6 +423,7 @@ func TestScopeParentChangedInheritance(t *testing.T) {
scope.SetLevel(LevelFatal)
scope.SetFingerprint([]string{"bar"})
scope.AddBreadcrumb(&Breadcrumb{Timestamp: testNow, Message: "bar"}, maxBreadcrumbs)
scope.AddAttachment(&Attachment{Filename: "bar.txt", Payload: []byte("bar")})
scope.SetUser(User{ID: "bar"})
r2 := httptest.NewRequest("GET", "/bar", nil)
scope.SetRequest(r2)
Expand All @@ -401,6 +434,7 @@ func TestScopeParentChangedInheritance(t *testing.T) {
assertEqual(t, LevelDebug, clone.level)
assertEqual(t, []string{"foo"}, clone.fingerprint)
assertEqual(t, []*Breadcrumb{{Timestamp: testNow, Message: "foo"}}, clone.breadcrumbs)
assertEqual(t, []*Attachment{{Filename: "foo.txt", Payload: []byte("foo")}}, clone.attachments)
assertEqual(t, User{ID: "foo"}, clone.user)
assertEqual(t, r1, clone.request)

Expand All @@ -410,6 +444,7 @@ func TestScopeParentChangedInheritance(t *testing.T) {
assertEqual(t, LevelFatal, scope.level)
assertEqual(t, []string{"bar"}, scope.fingerprint)
assertEqual(t, []*Breadcrumb{{Timestamp: testNow, Message: "bar"}}, scope.breadcrumbs)
assertEqual(t, []*Attachment{{Filename: "bar.txt", Payload: []byte("bar")}}, scope.attachments)
assertEqual(t, User{ID: "bar"}, scope.user)
assertEqual(t, r2, scope.request)
}
Expand All @@ -423,6 +458,7 @@ func TestScopeChildOverrideInheritance(t *testing.T) {
scope.SetLevel(LevelFatal)
scope.SetFingerprint([]string{"bar"})
scope.AddBreadcrumb(&Breadcrumb{Timestamp: testNow, Message: "bar"}, maxBreadcrumbs)
scope.AddAttachment(&Attachment{Filename: "bar.txt", Payload: []byte("bar")})
scope.SetUser(User{ID: "bar"})
r1 := httptest.NewRequest("GET", "/bar", nil)
scope.SetRequest(r1)
Expand All @@ -437,6 +473,7 @@ func TestScopeChildOverrideInheritance(t *testing.T) {
clone.SetLevel(LevelDebug)
clone.SetFingerprint([]string{"foo"})
clone.AddBreadcrumb(&Breadcrumb{Timestamp: testNow, Message: "foo"}, maxBreadcrumbs)
clone.AddAttachment(&Attachment{Filename: "foo.txt", Payload: []byte("foo")})
clone.SetUser(User{ID: "foo"})
r2 := httptest.NewRequest("GET", "/foo", nil)
clone.SetRequest(r2)
Expand All @@ -453,6 +490,10 @@ func TestScopeChildOverrideInheritance(t *testing.T) {
{Timestamp: testNow, Message: "bar"},
{Timestamp: testNow, Message: "foo"},
}, clone.breadcrumbs)
assertEqual(t, []*Attachment{
{Filename: "bar.txt", Payload: []byte("bar")},
{Filename: "foo.txt", Payload: []byte("foo")},
}, clone.attachments)
assertEqual(t, User{ID: "foo"}, clone.user)
assertEqual(t, r2, clone.request)

Expand All @@ -462,6 +503,7 @@ func TestScopeChildOverrideInheritance(t *testing.T) {
assertEqual(t, LevelFatal, scope.level)
assertEqual(t, []string{"bar"}, scope.fingerprint)
assertEqual(t, []*Breadcrumb{{Timestamp: testNow, Message: "bar"}}, scope.breadcrumbs)
assertEqual(t, []*Attachment{{Filename: "bar.txt", Payload: []byte("bar")}}, scope.attachments)
assertEqual(t, User{ID: "bar"}, scope.user)
assertEqual(t, r1, scope.request)

Expand All @@ -474,6 +516,7 @@ func TestClear(t *testing.T) {
scope.Clear()

assertEqual(t, []*Breadcrumb{}, scope.breadcrumbs)
assertEqual(t, []*Attachment{}, scope.attachments)
assertEqual(t, User{}, scope.user)
assertEqual(t, map[string]string{}, scope.tags)
assertEqual(t, map[string]Context{}, scope.contexts)
Expand All @@ -493,6 +536,7 @@ func TestClearAndReconfigure(t *testing.T) {
scope.SetLevel(LevelDebug)
scope.SetFingerprint([]string{"foo"})
scope.AddBreadcrumb(&Breadcrumb{Timestamp: testNow, Message: "foo"}, maxBreadcrumbs)
scope.AddAttachment(&Attachment{Filename: "foo.txt", Payload: []byte("foo")})
scope.SetUser(User{ID: "foo"})
r := httptest.NewRequest("GET", "/foo", nil)
scope.SetRequest(r)
Expand All @@ -503,6 +547,7 @@ func TestClearAndReconfigure(t *testing.T) {
assertEqual(t, LevelDebug, scope.level)
assertEqual(t, []string{"foo"}, scope.fingerprint)
assertEqual(t, []*Breadcrumb{{Timestamp: testNow, Message: "foo"}}, scope.breadcrumbs)
assertEqual(t, []*Attachment{{Filename: "foo.txt", Payload: []byte("foo")}}, scope.attachments)
assertEqual(t, User{ID: "foo"}, scope.user)
assertEqual(t, r, scope.request)
}
Expand All @@ -514,13 +559,21 @@ func TestClearBreadcrumbs(t *testing.T) {
assertEqual(t, []*Breadcrumb{}, scope.breadcrumbs)
}

func TestClearAttachments(t *testing.T) {
scope := fillScopeWithData(NewScope())
scope.ClearAttachments()

assertEqual(t, []*Attachment{}, scope.attachments)
}

func TestApplyToEventWithCorrectScopeAndEvent(t *testing.T) {
scope := fillScopeWithData(NewScope())
event := fillEventWithData(NewEvent())

processedEvent := scope.ApplyToEvent(event, nil)

assertEqual(t, len(processedEvent.Breadcrumbs), 2, "should merge breadcrumbs")
assertEqual(t, len(processedEvent.attachments), 2, "should merge attachments")
assertEqual(t, len(processedEvent.Tags), 2, "should merge tags")
assertEqual(t, len(processedEvent.Contexts), 3, "should merge contexts")
assertEqual(t, event.Contexts[sharedContextsKey], event.Contexts[sharedContextsKey], "should not override event context")
Expand All @@ -538,6 +591,7 @@ func TestApplyToEventUsingEmptyScope(t *testing.T) {
processedEvent := scope.ApplyToEvent(event, nil)

assertEqual(t, len(processedEvent.Breadcrumbs), 1, "should use event breadcrumbs")
assertEqual(t, len(processedEvent.attachments), 1, "should use event attachments")
assertEqual(t, len(processedEvent.Tags), 1, "should use event tags")
assertEqual(t, len(processedEvent.Contexts), 2, "should use event contexts")
assertEqual(t, len(processedEvent.Extra), 1, "should use event extra")
Expand All @@ -554,6 +608,7 @@ func TestApplyToEventUsingEmptyEvent(t *testing.T) {
processedEvent := scope.ApplyToEvent(event, nil)

assertEqual(t, len(processedEvent.Breadcrumbs), 1, "should use scope breadcrumbs")
assertEqual(t, len(processedEvent.attachments), 1, "should use scope attachments")
assertEqual(t, len(processedEvent.Tags), 1, "should use scope tags")
assertEqual(t, len(processedEvent.Contexts), 2, "should use scope contexts")
assertEqual(t, len(processedEvent.Extra), 1, "should use scope extra")
Expand Down
6 changes: 3 additions & 3 deletions tracing_test.go
Expand Up @@ -157,7 +157,7 @@ func TestStartSpan(t *testing.T) {
cmpopts.IgnoreFields(Event{},
"Contexts", "EventID", "Level", "Platform",
"Release", "Sdk", "ServerName", "Modules",
"sdkMetaData",
"sdkMetaData", "attachments",
),
cmpopts.EquateEmpty(),
}
Expand Down Expand Up @@ -221,7 +221,7 @@ func TestStartChild(t *testing.T) {
cmpopts.IgnoreFields(Event{},
"EventID", "Level", "Platform", "Modules",
"Release", "Sdk", "ServerName", "Timestamp", "StartTime",
"sdkMetaData",
"sdkMetaData", "attachments",
),
cmpopts.IgnoreMapEntries(func(k string, v interface{}) bool {
return k != "trace"
Expand Down Expand Up @@ -302,7 +302,7 @@ func TestStartTransaction(t *testing.T) {
cmpopts.IgnoreFields(Event{},
"Contexts", "EventID", "Level", "Platform",
"Release", "Sdk", "ServerName", "Modules",
"sdkMetaData",
"sdkMetaData", "attachments",
),
cmpopts.EquateEmpty(),
}
Expand Down