blob: 098a21a0679583c51f935e6f40907637f11e48c4 [file] [log] [blame]
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -07001package logrus
2
3import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "runtime"
8)
9
10type fieldKey string
11
12// FieldMap allows customization of the key names for default fields.
13type FieldMap map[fieldKey]string
14
15func (f FieldMap) resolve(key fieldKey) string {
16 if k, ok := f[key]; ok {
17 return k
18 }
19
20 return string(key)
21}
22
23// JSONFormatter formats logs into parsable json
24type JSONFormatter struct {
25 // TimestampFormat sets the format used for marshaling timestamps.
26 TimestampFormat string
27
28 // DisableTimestamp allows disabling automatic timestamps in output
29 DisableTimestamp bool
30
31 // DataKey allows users to put all the log entry parameters into a nested dictionary at a given key.
32 DataKey string
33
34 // FieldMap allows users to customize the names of keys for default fields.
35 // As an example:
36 // formatter := &JSONFormatter{
37 // FieldMap: FieldMap{
38 // FieldKeyTime: "@timestamp",
39 // FieldKeyLevel: "@level",
40 // FieldKeyMsg: "@message",
41 // FieldKeyFunc: "@caller",
42 // },
43 // }
44 FieldMap FieldMap
45
46 // CallerPrettyfier can be set by the user to modify the content
47 // of the function and file keys in the json data when ReportCaller is
48 // activated. If any of the returned value is the empty string the
49 // corresponding key will be removed from json fields.
50 CallerPrettyfier func(*runtime.Frame) (function string, file string)
51
52 // PrettyPrint will indent all json logs
53 PrettyPrint bool
54}
55
56// Format renders a single log entry
57func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
58 data := make(Fields, len(entry.Data)+4)
59 for k, v := range entry.Data {
60 switch v := v.(type) {
61 case error:
62 // Otherwise errors are ignored by `encoding/json`
63 // https://github.com/sirupsen/logrus/issues/137
64 data[k] = v.Error()
65 default:
66 data[k] = v
67 }
68 }
69
70 if f.DataKey != "" {
71 newData := make(Fields, 4)
72 newData[f.DataKey] = data
73 data = newData
74 }
75
76 prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
77
78 timestampFormat := f.TimestampFormat
79 if timestampFormat == "" {
80 timestampFormat = defaultTimestampFormat
81 }
82
83 if entry.err != "" {
84 data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err
85 }
86 if !f.DisableTimestamp {
87 data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
88 }
89 data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
90 data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
91 if entry.HasCaller() {
92 funcVal := entry.Caller.Function
93 fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
94 if f.CallerPrettyfier != nil {
95 funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
96 }
97 if funcVal != "" {
98 data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
99 }
100 if fileVal != "" {
101 data[f.FieldMap.resolve(FieldKeyFile)] = fileVal
102 }
103 }
104
105 var b *bytes.Buffer
106 if entry.Buffer != nil {
107 b = entry.Buffer
108 } else {
109 b = &bytes.Buffer{}
110 }
111
112 encoder := json.NewEncoder(b)
113 if f.PrettyPrint {
114 encoder.SetIndent("", " ")
115 }
116 if err := encoder.Encode(data); err != nil {
117 return nil, fmt.Errorf("failed to marshal fields to JSON, %v", err)
118 }
119
120 return b.Bytes(), nil
121}