metadata.go 4.4 KB

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