/
key_suffix_hook.go
executable file
·133 lines (120 loc) · 3.69 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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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 {
newFields := log.Fields{}
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)
mapString, err := json.Marshal(value)
if err != nil {
return err
}
newFields[newKey] = string(mapString)
continue
} else {
return err
}
}
newKey := fmt.Sprintf("%s_%s", key, typ)
newFields[newKey] = value
}
entry.Data = newFields
return nil
}
const stringType = "string"
// getTypeForValue returns a string representation of the type of the given val.
func getTypeForValue(val interface{}) (string, error) {
if val == nil {
return "null", nil
}
if _, ok := val.(json.Marshaler); ok {
// If val implements json.Marshaler, 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 stringType, nil
}
if _, ok := val.(error); ok {
// The json package always encodes values that implement
// error as a string.
return stringType, nil
}
underlyingType := getUnderlyingType(reflect.TypeOf(val))
switch kind := underlyingType.Kind(); kind {
case reflect.Ptr:
reflectVal := reflect.ValueOf(val)
if !reflectVal.IsNil() {
return getTypeForValue(reflectVal.Elem())
} else {
return "null", nil
}
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 stringType, 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
}