| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- package bugsnag
- import (
- "fmt"
- "reflect"
- "strings"
- )
- // MetaData is added to the Bugsnag dashboard in tabs. Each tab is
- // a map of strings -> values. You can pass MetaData to Notify, Recover
- // and AutoNotify as rawData.
- type MetaData map[string]map[string]interface{}
- // Update the meta-data with more information. Tabs are merged together such
- // that unique keys from both sides are preserved, and duplicate keys end up
- // with the provided values.
- func (meta MetaData) Update(other MetaData) {
- for name, tab := range other {
- if meta[name] == nil {
- meta[name] = make(map[string]interface{})
- }
- for key, value := range tab {
- meta[name][key] = value
- }
- }
- }
- // Add creates a tab of Bugsnag meta-data.
- // If the tab doesn't yet exist it will be created.
- // If the key already exists, it will be overwritten.
- func (meta MetaData) Add(tab string, key string, value interface{}) {
- if meta[tab] == nil {
- meta[tab] = make(map[string]interface{})
- }
- meta[tab][key] = value
- }
- // AddStruct creates a tab of Bugsnag meta-data.
- // The struct will be converted to an Object using the
- // reflect library so any private fields will not be exported.
- // 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)
- content, ok := val.(map[string]interface{})
- if ok {
- meta[tab] = content
- } else {
- // Wasn't a struct
- meta.Add("Extra data", tab, obj)
- }
- }
- // 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{
- Filters: filters,
- Seen: make([]interface{}, 0),
- }.Sanitize(meta)
- }
- // The 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{} {
- for _, s := range s.Seen {
- // TODO: we don't need deep equal here, just type-ignoring equality
- if reflect.DeepEqual(data, s) {
- return "[RECURSION]"
- }
- }
- // Sanitizers are passed by value, so we can modify s and it only affects
- // s.Seen for nested calls.
- s.Seen = append(s.Seen, data)
- t := reflect.TypeOf(data)
- v := reflect.ValueOf(data)
- if t == nil {
- return "<nil>"
- }
- switch t.Kind() {
- case reflect.Bool,
- reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
- reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
- reflect.Float32, reflect.Float64:
- return data
- case reflect.String:
- return data
- case reflect.Interface, reflect.Ptr:
- if v.IsNil() {
- return "<nil>"
- }
- return s.Sanitize(v.Elem().Interface())
- case reflect.Array, reflect.Slice:
- ret := make([]interface{}, v.Len())
- for i := 0; i < v.Len(); i++ {
- ret[i] = s.Sanitize(v.Index(i).Interface())
- }
- return ret
- case reflect.Map:
- return s.sanitizeMap(v)
- case reflect.Struct:
- return s.sanitizeStruct(v, t)
- // Things JSON can't serialize:
- // case t.Chan, t.Func, reflect.Complex64, reflect.Complex128, reflect.UnsafePointer:
- default:
- return "[" + t.String() + "]"
- }
- }
- func (s sanitizer) sanitizeMap(v reflect.Value) interface{} {
- ret := make(map[string]interface{})
- for _, key := range v.MapKeys() {
- val := s.Sanitize(v.MapIndex(key).Interface())
- newKey := fmt.Sprintf("%v", key.Interface())
- if s.shouldRedact(newKey) {
- val = "[FILTERED]"
- }
- ret[newKey] = val
- }
- return ret
- }
- func (s sanitizer) sanitizeStruct(v reflect.Value, t reflect.Type) interface{} {
- ret := make(map[string]interface{})
- for i := 0; i < v.NumField(); i++ {
- val := v.Field(i)
- // Don't export private fields
- if !val.CanInterface() {
- continue
- }
- name := t.Field(i).Name
- var opts tagOptions
- // Parse JSON tags. Supports name and "omitempty"
- if jsonTag := t.Field(i).Tag.Get("json"); len(jsonTag) != 0 {
- name, opts = parseTag(jsonTag)
- }
- if s.shouldRedact(name) {
- ret[name] = "[FILTERED]"
- } else {
- sanitized := s.Sanitize(val.Interface())
- if str, ok := sanitized.(string); ok {
- if !(opts.Contains("omitempty") && len(str) == 0) {
- ret[name] = str
- }
- } else {
- ret[name] = sanitized
- }
- }
- }
- return ret
- }
- func (s sanitizer) shouldRedact(key string) bool {
- for _, filter := range s.Filters {
- if strings.Contains(strings.ToLower(key), strings.ToLower(filter)) {
- return true
- }
- }
- return false
- }
|