Skip to content

Commit

Permalink
feat: Allow some non-Golang interfaces to be passed through in Events. (
Browse files Browse the repository at this point in the history
#606)

* feat: Allow some non-Golang interfaces to be passed through in Events.

Some users may wish to use sentry-go as a mechanism to proxy non-Golang
events -- for instance, if reporting errors from a mobile or web
application.  We add Sentry event payload interfaces that make it easier to
support other platforms, including stack frame interface members for
"C-like" applications, and including the Debug Meta interface that allows
the Sentry backend to symbolicate application backtraces.

This change was authored under contract for FullStory.

* test: add individual tests for new stacktrace and debugmeta interfaces
  • Loading branch information
jwise committed Mar 29, 2023
1 parent 85b380d commit 8fdc323
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 7 deletions.
29 changes: 29 additions & 0 deletions interfaces.go
Expand Up @@ -241,6 +241,34 @@ type TransactionInfo struct {
Source TransactionSource `json:"source,omitempty"`
}

// The DebugMeta interface is not used in Golang apps, but may be populated
// when proxying Events from other platforms, like iOS, Android, and the
// Web. (See: https://develop.sentry.dev/sdk/event-payloads/debugmeta/ ).
type DebugMeta struct {
SdkInfo *DebugMetaSdkInfo `json:"sdk_info,omitempty"`
Images []DebugMetaImage `json:"images,omitempty"`
}

type DebugMetaSdkInfo struct {
SdkName string `json:"sdk_name,omitempty"`
VersionMajor int `json:"version_major,omitempty"`
VersionMinor int `json:"version_minor,omitempty"`
VersionPatchlevel int `json:"version_patchlevel,omitempty"`
}

type DebugMetaImage struct {
Type string `json:"type,omitempty"` // all
ImageAddr string `json:"image_addr,omitempty"` // macho,elf,pe
ImageSize int `json:"image_size,omitempty"` // macho,elf,pe
DebugID string `json:"debug_id,omitempty"` // macho,elf,pe,wasm,sourcemap
DebugFile string `json:"debug_file,omitempty"` // macho,elf,pe,wasm
CodeID string `json:"code_id,omitempty"` // macho,elf,pe,wasm
CodeFile string `json:"code_file,omitempty"` // macho,elf,pe,wasm,sourcemap
ImageVmaddr string `json:"image_vmaddr,omitempty"` // macho,elf,pe
Arch string `json:"arch,omitempty"` // macho,elf,pe
UUID string `json:"uuid,omitempty"` // proguard
}

// EventID is a hexadecimal string representing a unique uuid4 for an Event.
// An EventID must be 32 characters long, lowercase and not have any dashes.
type EventID string
Expand Down Expand Up @@ -271,6 +299,7 @@ type Event struct {
Modules map[string]string `json:"modules,omitempty"`
Request *Request `json:"request,omitempty"`
Exception []Exception `json:"exception,omitempty"`
DebugMeta *DebugMeta `json:"debug_meta,omitempty"`

// The fields below are only relevant for transactions.

Expand Down
55 changes: 55 additions & 0 deletions interfaces_test.go
Expand Up @@ -160,6 +160,61 @@ func TestEventMarshalJSON(t *testing.T) {
}
}

func TestEventWithDebugMetaMarshalJSON(t *testing.T) {
event := NewEvent()
event.DebugMeta = &DebugMeta{
SdkInfo: &DebugMetaSdkInfo{
SdkName: "test",
VersionMajor: 1,
VersionMinor: 2,
VersionPatchlevel: 3,
},
Images: []DebugMetaImage{
{
Type: "macho",
ImageAddr: "0xabcd0000",
ImageSize: 32768,
DebugID: "42DB5B96-5144-4079-BE09-45E2142CA3E5",
DebugFile: "foo.dSYM",
CodeID: "A7AF6477-9130-4EB7-ADFE-AD0F57001DBD",
CodeFile: "foo.dylib",
ImageVmaddr: "0x0",
Arch: "arm64",
},
{
Type: "proguard",
UUID: "982E62D4-6493-4E43-864B-6523C79C7064",
},
},
}

got, err := json.Marshal(event)
if err != nil {
t.Fatal(err)
}

want := `{"sdk":{},"user":{},` +
`"debug_meta":{` +
`"sdk_info":{"sdk_name":"test","version_major":1,"version_minor":2,"version_patchlevel":3},` +
`"images":[` +
`{"type":"macho",` +
`"image_addr":"0xabcd0000",` +
`"image_size":32768,` +
`"debug_id":"42DB5B96-5144-4079-BE09-45E2142CA3E5",` +
`"debug_file":"foo.dSYM",` +
`"code_id":"A7AF6477-9130-4EB7-ADFE-AD0F57001DBD",` +
`"code_file":"foo.dylib",` +
`"image_vmaddr":"0x0",` +
`"arch":"arm64"` +
`},` +
`{"type":"proguard","uuid":"982E62D4-6493-4E43-864B-6523C79C7064"}` +
`]}}`

if diff := cmp.Diff(want, string(got)); diff != "" {
t.Errorf("Event mismatch (-want +got):\n%s", diff)
}
}

func TestMechanismMarshalJSON(t *testing.T) {
mechanism := &Mechanism{
Type: "some type",
Expand Down
20 changes: 13 additions & 7 deletions stacktrace.go
Expand Up @@ -166,13 +166,7 @@ type Frame struct {
Symbol string `json:"symbol,omitempty"`
// Module is, despite the name, the Sentry protocol equivalent of a Go
// package's import path.
Module string `json:"module,omitempty"`
// Package is not used for Go stack trace frames. In other platforms it
// refers to a container where the Module can be found. For example, a
// Java JAR, a .NET Assembly, or a native dynamic library.
// It exists for completeness, allowing the construction and reporting
// of custom event payloads.
Package string `json:"package,omitempty"`
Module string `json:"module,omitempty"`
Filename string `json:"filename,omitempty"`
AbsPath string `json:"abs_path,omitempty"`
Lineno int `json:"lineno,omitempty"`
Expand All @@ -182,6 +176,18 @@ type Frame struct {
PostContext []string `json:"post_context,omitempty"`
InApp bool `json:"in_app,omitempty"`
Vars map[string]interface{} `json:"vars,omitempty"`
// Package and the below are not used for Go stack trace frames. In
// other platforms it refers to a container where the Module can be
// found. For example, a Java JAR, a .NET Assembly, or a native
// dynamic library. They exists for completeness, allowing the
// construction and reporting of custom event payloads.
Package string `json:"package,omitempty"`
InstructionAddr string `json:"instruction_addr,omitempty"`
AddrMode string `json:"addr_mode,omitempty"`
SymbolAddr string `json:"symbol_addr,omitempty"`
ImageAddr string `json:"image_addr,omitempty"`
Platform string `json:"platform,omitempty"`
StackStart bool `json:"stack_start,omitempty"`
}

// NewFrame assembles a stacktrace frame out of runtime.Frame.
Expand Down
73 changes: 73 additions & 0 deletions stacktrace_test.go
@@ -1,6 +1,7 @@
package sentry

import (
"encoding/json"
"errors"
"testing"

Expand Down Expand Up @@ -169,3 +170,75 @@ func TestExtractXErrorsPC(t *testing.T) {
t.Errorf("got %#v, want nil", got)
}
}

func TestEventWithExceptionStacktraceMarshalJSON(t *testing.T) {
event := NewEvent()
event.Exception = []Exception{
{
Stacktrace: &Stacktrace{
Frames: []Frame{
{
Function: "gofunc",
Symbol: "gosym",
Module: "gopkg/gopath",
Filename: "foo.go",
AbsPath: "/something/foo.go",
Lineno: 35,
Colno: 72,
PreContext: []string{"pre", "context"},
ContextLine: "contextline",
PostContext: []string{"post", "context"},
InApp: true,
Vars: map[string]interface{}{
"foostr": "bar",
"fooint": 25,
},
},
{
Symbol: "nativesym",
Package: "my.dylib",
InstructionAddr: "0xabcd0010",
AddrMode: "abs",
SymbolAddr: "0xabcd0000",
ImageAddr: "0xabc00000",
Platform: "native",
StackStart: false,
},
},
},
},
}

got, err := json.Marshal(event)
if err != nil {
t.Fatal(err)
}

want := `{"sdk":{},"user":{},` +
`"exception":[{"stacktrace":{"frames":[` +
`{"function":"gofunc",` +
`"symbol":"gosym",` +
`"module":"gopkg/gopath",` +
`"filename":"foo.go",` +
`"abs_path":"/something/foo.go",` +
`"lineno":35,` +
`"colno":72,` +
`"pre_context":["pre","context"],` +
`"context_line":"contextline",` +
`"post_context":["post","context"],` +
`"in_app":true,` +
`"vars":{"fooint":25,"foostr":"bar"}` +
`},{` +
`"symbol":"nativesym",` +
`"package":"my.dylib",` +
`"instruction_addr":"0xabcd0010",` +
`"addr_mode":"abs",` +
`"symbol_addr":"0xabcd0000",` +
`"image_addr":"0xabc00000",` +
`"platform":"native"` +
`}]}}]}`

if diff := cmp.Diff(want, string(got)); diff != "" {
t.Errorf("Event mismatch (-want +got):\n%s", diff)
}
}

0 comments on commit 8fdc323

Please sign in to comment.