-
Notifications
You must be signed in to change notification settings - Fork 111
/
key_suffix_hook.go
116 lines (104 loc) · 3.32 KB
/
key_suffix_hook.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package loghooks
import (
"bytes"
"encoding"
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
log "github.com/sirupsen/logrus"
)
var errNestedMapType = errors.New("nested map types are not supported")
// KeySuffixHook is a logger hook that adds suffixes to all keys based on their
// type.
type KeySuffixHook struct{}
// NewKeySuffixHook creates and returns a new KeySuffixHook.
func NewKeySuffixHook() *KeySuffixHook {
return &KeySuffixHook{}
}
// Ensure that KeySuffixHook implements log.Hook.
var _ log.Hook = &KeySuffixHook{}
func (h *KeySuffixHook) Levels() []log.Level {
return log.AllLevels
}
func (h *KeySuffixHook) Fire(entry *log.Entry) error {
for key, value := range entry.Data {
typ, err := getTypeForValue(value)
if err != nil {
if err == errNestedMapType {
// We can't safely log nested map types, so replace the value with a
// string.
newKey := fmt.Sprintf("%s_json_string", key)
delete(entry.Data, key)
mapString, err := json.Marshal(value)
if err != nil {
return err
}
entry.Data[newKey] = string(mapString)
continue
} else {
return err
}
}
newKey := fmt.Sprintf("%s_%s", key, typ)
delete(entry.Data, key)
entry.Data[newKey] = value
}
return nil
}
// getTypeForValue returns a string representation of the type of the given val.
func getTypeForValue(val interface{}) (string, error) {
if _, ok := val.(json.Marshaler); ok {
// If val implements json.Marhsler, return the type of json.Marshal(val)
// instead of the type of val.
buf := &bytes.Buffer{}
if err := json.NewEncoder(buf).Encode(val); err != nil {
return "", err
}
var holder interface{}
if err := json.NewDecoder(buf).Decode(&holder); err != nil {
return "", err
}
return getTypeForValue(holder)
}
if _, ok := val.(encoding.TextMarshaler); ok {
// The json package always encodes values that implement
// encoding.TextMarshaler as a string.
return "string", nil
}
underlyingType := getUnderlyingType(reflect.TypeOf(val))
switch kind := underlyingType.Kind(); kind {
case reflect.Bool:
return "bool", nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
return "number", nil
case reflect.String, reflect.Complex64, reflect.Complex128, reflect.Func, reflect.Chan:
return "string", nil
case reflect.Array, reflect.Slice:
return "array", nil
case reflect.Map:
// Nested map types can't be efficiently indexed because they allow for
// arbitrary keys. We don't allow them.
return "", errNestedMapType
case reflect.Struct:
return getSafeStructTypeName(underlyingType)
default:
return "", fmt.Errorf("cannot determine type suffix for kind: %s", kind)
}
}
// getUnderlyingType returns the underlying type for the given type by
// recursively dereferencing pointer types.
func getUnderlyingType(typ reflect.Type) reflect.Type {
if typ.Kind() == reflect.Ptr {
return getUnderlyingType(typ.Elem())
}
return typ
}
// getSafeStructTypeName replaces dots in the name of the given type with
// underscores. Elasticsearch does not allow dots in key names.
func getSafeStructTypeName(typ reflect.Type) (string, error) {
unsafeTypeName := typ.String()
safeTypeName := strings.ReplaceAll(unsafeTypeName, ".", "_")
return safeTypeName, nil
}