1package bugsnag 2 3import ( 4 "fmt" 5 "reflect" 6 "strings" 7) 8 9// MetaData is added to the Bugsnag dashboard in tabs. Each tab is 10// a map of strings -> values. You can pass MetaData to Notify, Recover 11// and AutoNotify as rawData. 12type MetaData map[string]map[string]interface{} 13 14// Update the meta-data with more information. Tabs are merged together such 15// that unique keys from both sides are preserved, and duplicate keys end up 16// with the provided values. 17func (meta MetaData) Update(other MetaData) { 18 for name, tab := range other { 19 20 if meta[name] == nil { 21 meta[name] = make(map[string]interface{}) 22 } 23 24 for key, value := range tab { 25 meta[name][key] = value 26 } 27 } 28} 29 30// Add creates a tab of Bugsnag meta-data. 31// If the tab doesn't yet exist it will be created. 32// If the key already exists, it will be overwritten. 33func (meta MetaData) Add(tab string, key string, value interface{}) { 34 if meta[tab] == nil { 35 meta[tab] = make(map[string]interface{}) 36 } 37 38 meta[tab][key] = value 39} 40 41// AddStruct creates a tab of Bugsnag meta-data. 42// The struct will be converted to an Object using the 43// reflect library so any private fields will not be exported. 44// As a safety measure, if you pass a non-struct the value will be 45// sent to Bugsnag under the "Extra data" tab. 46func (meta MetaData) AddStruct(tab string, obj interface{}) { 47 val := sanitizer{}.Sanitize(obj) 48 content, ok := val.(map[string]interface{}) 49 if ok { 50 meta[tab] = content 51 } else { 52 // Wasn't a struct 53 meta.Add("Extra data", tab, obj) 54 } 55 56} 57 58// Remove any values from meta-data that have keys matching the filters, 59// and any that are recursive data-structures 60func (meta MetaData) sanitize(filters []string) interface{} { 61 return sanitizer{ 62 Filters: filters, 63 Seen: make([]interface{}, 0), 64 }.Sanitize(meta) 65 66} 67 68// The sanitizer is used to remove filtered params and recursion from meta-data. 69type sanitizer struct { 70 Filters []string 71 Seen []interface{} 72} 73 74func (s sanitizer) Sanitize(data interface{}) interface{} { 75 for _, s := range s.Seen { 76 // TODO: we don't need deep equal here, just type-ignoring equality 77 if reflect.DeepEqual(data, s) { 78 return "[RECURSION]" 79 } 80 } 81 82 // Sanitizers are passed by value, so we can modify s and it only affects 83 // s.Seen for nested calls. 84 s.Seen = append(s.Seen, data) 85 86 t := reflect.TypeOf(data) 87 v := reflect.ValueOf(data) 88 89 switch t.Kind() { 90 case reflect.Bool, 91 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 92 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, 93 reflect.Float32, reflect.Float64: 94 return data 95 96 case reflect.String: 97 return data 98 99 case reflect.Interface, reflect.Ptr: 100 return s.Sanitize(v.Elem().Interface()) 101 102 case reflect.Array, reflect.Slice: 103 ret := make([]interface{}, v.Len()) 104 for i := 0; i < v.Len(); i++ { 105 ret[i] = s.Sanitize(v.Index(i).Interface()) 106 } 107 return ret 108 109 case reflect.Map: 110 return s.sanitizeMap(v) 111 112 case reflect.Struct: 113 return s.sanitizeStruct(v, t) 114 115 // Things JSON can't serialize: 116 // case t.Chan, t.Func, reflect.Complex64, reflect.Complex128, reflect.UnsafePointer: 117 default: 118 return "[" + t.String() + "]" 119 120 } 121 122} 123 124func (s sanitizer) sanitizeMap(v reflect.Value) interface{} { 125 ret := make(map[string]interface{}) 126 127 for _, key := range v.MapKeys() { 128 val := s.Sanitize(v.MapIndex(key).Interface()) 129 newKey := fmt.Sprintf("%v", key.Interface()) 130 131 if s.shouldRedact(newKey) { 132 val = "[REDACTED]" 133 } 134 135 ret[newKey] = val 136 } 137 138 return ret 139} 140 141func (s sanitizer) sanitizeStruct(v reflect.Value, t reflect.Type) interface{} { 142 ret := make(map[string]interface{}) 143 144 for i := 0; i < v.NumField(); i++ { 145 146 val := v.Field(i) 147 // Don't export private fields 148 if !val.CanInterface() { 149 continue 150 } 151 152 name := t.Field(i).Name 153 var opts tagOptions 154 155 // Parse JSON tags. Supports name and "omitempty" 156 if jsonTag := t.Field(i).Tag.Get("json"); len(jsonTag) != 0 { 157 name, opts = parseTag(jsonTag) 158 } 159 160 if s.shouldRedact(name) { 161 ret[name] = "[REDACTED]" 162 } else { 163 sanitized := s.Sanitize(val.Interface()) 164 if str, ok := sanitized.(string); ok { 165 if !(opts.Contains("omitempty") && len(str) == 0) { 166 ret[name] = str 167 } 168 } else { 169 ret[name] = sanitized 170 } 171 172 } 173 } 174 175 return ret 176} 177 178func (s sanitizer) shouldRedact(key string) bool { 179 for _, filter := range s.Filters { 180 if strings.Contains(strings.ToLower(filter), strings.ToLower(key)) { 181 return true 182 } 183 } 184 return false 185} 186