186 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			186 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
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)
 | 
						|
 | 
						|
	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:
 | 
						|
		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 = "[REDACTED]"
 | 
						|
		}
 | 
						|
 | 
						|
		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] = "[REDACTED]"
 | 
						|
		} 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(filter), strings.ToLower(key)) {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 |