From bf8d8ab0ecaed21a8abd19275f52a10308dcb184 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Tue, 5 Dec 2023 17:32:04 -0700 Subject: [PATCH 1/4] veqryn/export-sanitizer-and-handle-time-errors-stringers --- v2/metadata.go | 40 +++++++++++++++++++++++++++++----------- v2/metadata_test.go | 2 +- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/v2/metadata.go b/v2/metadata.go index 1c9929e2..7b24e247 100644 --- a/v2/metadata.go +++ b/v2/metadata.go @@ -1,9 +1,11 @@ package bugsnag import ( + "encoding" "fmt" "reflect" "strings" + "time" ) // MetaData is added to the Bugsnag dashboard in tabs. Each tab is @@ -44,7 +46,7 @@ func (meta MetaData) Add(tab string, key string, value interface{}) { // As a safety measure, if you pass a non-struct the value will be // sent to Bugsnag under the "Extra data" tab. func (meta MetaData) AddStruct(tab string, obj interface{}) { - val := sanitizer{}.Sanitize(obj) + val := Sanitizer{}.Sanitize(obj) content, ok := val.(map[string]interface{}) if ok { meta[tab] = content @@ -58,20 +60,20 @@ func (meta MetaData) AddStruct(tab string, obj interface{}) { // Remove any values from meta-data that have keys matching the filters, // and any that are recursive data-structures func (meta MetaData) sanitize(filters []string) interface{} { - return sanitizer{ + return Sanitizer{ Filters: filters, Seen: make([]interface{}, 0), }.Sanitize(meta) } -// The sanitizer is used to remove filtered params and recursion from meta-data. -type sanitizer struct { +// Sanitizer is used to remove filtered params and recursion from meta-data. +type Sanitizer struct { Filters []string Seen []interface{} } -func (s sanitizer) Sanitize(data interface{}) interface{} { +func (s Sanitizer) Sanitize(data interface{}) interface{} { for _, s := range s.Seen { // TODO: we don't need deep equal here, just type-ignoring equality if reflect.DeepEqual(data, s) { @@ -83,6 +85,25 @@ func (s sanitizer) Sanitize(data interface{}) interface{} { // s.Seen for nested calls. s.Seen = append(s.Seen, data) + // Handle certain well known interfaces and types + switch data := data.(type) { + case error: + return data.Error() + + case time.Time: + return data.Format(time.RFC3339Nano) + + case fmt.Stringer: + // This also covers time.Duration + return data.String() + + case encoding.TextUnmarshaler: + var b []byte + if err := data.UnmarshalText(b); err == nil { + return string(b) + } + } + t := reflect.TypeOf(data) v := reflect.ValueOf(data) @@ -123,12 +144,10 @@ func (s sanitizer) Sanitize(data interface{}) interface{} { // case t.Chan, t.Func, reflect.Complex64, reflect.Complex128, reflect.UnsafePointer: default: return "[" + t.String() + "]" - } - } -func (s sanitizer) sanitizeMap(v reflect.Value) interface{} { +func (s Sanitizer) sanitizeMap(v reflect.Value) interface{} { ret := make(map[string]interface{}) for _, key := range v.MapKeys() { @@ -145,7 +164,7 @@ func (s sanitizer) sanitizeMap(v reflect.Value) interface{} { return ret } -func (s sanitizer) sanitizeStruct(v reflect.Value, t reflect.Type) interface{} { +func (s Sanitizer) sanitizeStruct(v reflect.Value, t reflect.Type) interface{} { ret := make(map[string]interface{}) for i := 0; i < v.NumField(); i++ { @@ -175,14 +194,13 @@ func (s sanitizer) sanitizeStruct(v reflect.Value, t reflect.Type) interface{} { } else { ret[name] = sanitized } - } } return ret } -func (s sanitizer) shouldRedact(key string) bool { +func (s Sanitizer) shouldRedact(key string) bool { for _, filter := range s.Filters { if strings.Contains(strings.ToLower(key), strings.ToLower(filter)) { return true diff --git a/v2/metadata_test.go b/v2/metadata_test.go index a6d509ce..f90c004e 100644 --- a/v2/metadata_test.go +++ b/v2/metadata_test.go @@ -185,7 +185,7 @@ func TestSanitizerSanitize(t *testing.T) { {nilPointer, ""}, {nilInterface, ""}, } { - s := &sanitizer{} + s := &Sanitizer{} gotValue := s.Sanitize(tc.input) if got, want := gotValue, tc.want; got != want { From 76448ff6f3c50dc34fa3238adff163dd69841cfd Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Wed, 6 Dec 2023 23:28:44 -0700 Subject: [PATCH 2/4] Add to tests --- v2/metadata.go | 5 ++--- v2/metadata_test.go | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/v2/metadata.go b/v2/metadata.go index 7b24e247..4aa7d563 100644 --- a/v2/metadata.go +++ b/v2/metadata.go @@ -97,9 +97,8 @@ func (s Sanitizer) Sanitize(data interface{}) interface{} { // This also covers time.Duration return data.String() - case encoding.TextUnmarshaler: - var b []byte - if err := data.UnmarshalText(b); err == nil { + case encoding.TextMarshaler: + if b, err := data.MarshalText(); err == nil { return string(b) } } diff --git a/v2/metadata_test.go b/v2/metadata_test.go index f90c004e..591d1d3d 100644 --- a/v2/metadata_test.go +++ b/v2/metadata_test.go @@ -1,8 +1,10 @@ package bugsnag import ( + stderrors "errors" "reflect" "testing" + "time" "unsafe" "github.com/bugsnag/bugsnag-go/v2/errors" @@ -26,6 +28,12 @@ type _broken struct { Data string } +type _textMarshaller struct{} + +func (_textMarshaller) MarshalText() ([]byte, error) { + return []byte("marshalled text"), nil +} + var account = _account{} var notifier = New(Configuration{}) @@ -123,6 +131,10 @@ func TestMetaDataSanitize(t *testing.T) { "unsafe": unsafe.Pointer(broken.Me), "string": "string", "password": "secret", + "error": stderrors.New("some error"), + "time": time.Date(2023, 12, 5, 23, 59, 59, 123456789, time.UTC), + "duration": 105567462 * time.Millisecond, + "text": _textMarshaller{}, "array": []hash{{ "creditcard": "1234567812345678", "broken": broken, @@ -144,6 +156,10 @@ func TestMetaDataSanitize(t *testing.T) { "unsafe": "[unsafe.Pointer]", "func": "[func()]", "password": "[FILTERED]", + "error": "some error", + "time": "2023-12-05T23:59:59.123456789Z", + "duration": "29h19m27.462s", + "text": "marshalled text", "array": []interface{}{map[string]interface{}{ "creditcard": "[FILTERED]", "broken": map[string]interface{}{ From 0d9f3ad4c104eebe87d21e1d56c9ee6eb56ff3f9 Mon Sep 17 00:00:00 2001 From: Daria Bialobrzeska Date: Wed, 28 Feb 2024 21:58:44 +0100 Subject: [PATCH 3/4] Make sanitizer class private again --- v2/metadata.go | 16 ++++++++-------- v2/metadata_test.go | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/v2/metadata.go b/v2/metadata.go index 4aa7d563..e5f62798 100644 --- a/v2/metadata.go +++ b/v2/metadata.go @@ -46,7 +46,7 @@ func (meta MetaData) Add(tab string, key string, value interface{}) { // As a safety measure, if you pass a non-struct the value will be // sent to Bugsnag under the "Extra data" tab. func (meta MetaData) AddStruct(tab string, obj interface{}) { - val := Sanitizer{}.Sanitize(obj) + val := sanitizer{}.Sanitize(obj) content, ok := val.(map[string]interface{}) if ok { meta[tab] = content @@ -60,20 +60,20 @@ func (meta MetaData) AddStruct(tab string, obj interface{}) { // Remove any values from meta-data that have keys matching the filters, // and any that are recursive data-structures func (meta MetaData) sanitize(filters []string) interface{} { - return Sanitizer{ + return sanitizer{ Filters: filters, Seen: make([]interface{}, 0), }.Sanitize(meta) } -// Sanitizer is used to remove filtered params and recursion from meta-data. -type Sanitizer struct { +// sanitizer is used to remove filtered params and recursion from meta-data. +type sanitizer struct { Filters []string Seen []interface{} } -func (s Sanitizer) Sanitize(data interface{}) interface{} { +func (s sanitizer) Sanitize(data interface{}) interface{} { for _, s := range s.Seen { // TODO: we don't need deep equal here, just type-ignoring equality if reflect.DeepEqual(data, s) { @@ -146,7 +146,7 @@ func (s Sanitizer) Sanitize(data interface{}) interface{} { } } -func (s Sanitizer) sanitizeMap(v reflect.Value) interface{} { +func (s sanitizer) sanitizeMap(v reflect.Value) interface{} { ret := make(map[string]interface{}) for _, key := range v.MapKeys() { @@ -163,7 +163,7 @@ func (s Sanitizer) sanitizeMap(v reflect.Value) interface{} { return ret } -func (s Sanitizer) sanitizeStruct(v reflect.Value, t reflect.Type) interface{} { +func (s sanitizer) sanitizeStruct(v reflect.Value, t reflect.Type) interface{} { ret := make(map[string]interface{}) for i := 0; i < v.NumField(); i++ { @@ -199,7 +199,7 @@ func (s Sanitizer) sanitizeStruct(v reflect.Value, t reflect.Type) interface{} { return ret } -func (s Sanitizer) shouldRedact(key string) bool { +func (s sanitizer) shouldRedact(key string) bool { for _, filter := range s.Filters { if strings.Contains(strings.ToLower(key), strings.ToLower(filter)) { return true diff --git a/v2/metadata_test.go b/v2/metadata_test.go index 591d1d3d..112ad6aa 100644 --- a/v2/metadata_test.go +++ b/v2/metadata_test.go @@ -201,7 +201,7 @@ func TestSanitizerSanitize(t *testing.T) { {nilPointer, ""}, {nilInterface, ""}, } { - s := &Sanitizer{} + s := &sanitizer{} gotValue := s.Sanitize(tc.input) if got, want := gotValue, tc.want; got != want { From 69f19e461b81ba787e29b4f8ff19fc1f21d3d622 Mon Sep 17 00:00:00 2001 From: Daria Bialobrzeska Date: Fri, 1 Mar 2024 10:27:38 +0100 Subject: [PATCH 4/4] Update Changelog for handling complex metadata change --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 576f3905..4d2d2ed7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ * Start showing inlined functions in stack trace [#208](https://github.com/bugsnag/bugsnag-go/pull/208) +* Handle complex structs in metadata + [#215](https://github.com/bugsnag/bugsnag-go/pull/215) + [Chris Duncan](https://github.com/veqryn) + ## 2.2.0 (2022-10-12) ### Enhancements