Skip to content

Commit

Permalink
feat: Attachments support (#670)
Browse files Browse the repository at this point in the history
  • Loading branch information
edigaryev committed Jul 25, 2023
1 parent 4b3a135 commit 4f72145
Show file tree
Hide file tree
Showing 12 changed files with 176 additions and 12 deletions.
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

0 comments on commit 4f72145

Please sign in to comment.