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 if t == nil { 90 return "<nil>" 91 } 92 93 switch t.Kind() { 94 case reflect.Bool, 95 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 96 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, 97 reflect.Float32, reflect.Float64: 98 return data 99 100 case reflect.String: 101 return data 102 103 case reflect.Interface, reflect.Ptr: 104 return s.Sanitize(v.Elem().Interface()) 105 106 case reflect.Array, reflect.Slice: 107 ret := make([]interface{}, v.Len()) 108 for i := 0; i < v.Len(); i++ { 109 ret[i] = s.Sanitize(v.Index(i).Interface()) 110 } 111 return ret 112 113 case reflect.Map: 114 return s.sanitizeMap(v) 115 116 case reflect.Struct: 117 return s.sanitizeStruct(v, t) 118 119 // Things JSON can't serialize: 120 // case t.Chan, t.Func, reflect.Complex64, reflect.Complex128, reflect.UnsafePointer: 121 default: 122 return "[" + t.String() + "]" 123 124 } 125 126} 127 128func (s sanitizer) sanitizeMap(v reflect.Value) interface{} { 129 ret := make(map[string]interface{}) 130 131 for _, key := range v.MapKeys() { 132 val := s.Sanitize(v.MapIndex(key).Interface()) 133 newKey := fmt.Sprintf("%v", key.Interface()) 134 135 if s.shouldRedact(newKey) { 136 val = "[REDACTED]" 137 } 138 139 ret[newKey] = val 140 } 141 142 return ret 143} 144 145func (s sanitizer) sanitizeStruct(v reflect.Value, t reflect.Type) interface{} { 146 ret := make(map[string]interface{}) 147 148 for i := 0; i < v.NumField(); i++ { 149 150 val := v.Field(i) 151 // Don't export private fields 152 if !val.CanInterface() { 153 continue 154 } 155 156 name := t.Field(i).Name 157 var opts tagOptions 158 159 // Parse JSON tags. Supports name and "omitempty" 160 if jsonTag := t.Field(i).Tag.Get("json"); len(jsonTag) != 0 { 161 name, opts = parseTag(jsonTag) 162 } 163 164 if s.shouldRedact(name) { 165 ret[name] = "[REDACTED]" 166 } else { 167 sanitized := s.Sanitize(val.Interface()) 168 if str, ok := sanitized.(string); ok { 169 if !(opts.Contains("omitempty") && len(str) == 0) { 170 ret[name] = str 171 } 172 } else { 173 ret[name] = sanitized 174 } 175 176 } 177 } 178 179 return ret 180} 181 182func (s sanitizer) shouldRedact(key string) bool { 183 for _, filter := range s.Filters { 184 if strings.Contains(strings.ToLower(filter), strings.ToLower(key)) { 185 return true 186 } 187 } 188 return false 189} 190