blob: ba7f237112bdd50bb05f4ddea8a0dda205d7161f [file] [log] [blame]
khenaidooab1f7bd2019-11-14 14:00:27 -05001package logrus
2
3import (
4 "bytes"
5 "encoding/json"
6 "fmt"
khenaidood948f772021-08-11 17:49:24 -04007 "runtime"
khenaidooab1f7bd2019-11-14 14:00:27 -05008)
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
khenaidood948f772021-08-11 17:49:24 -040031 // DisableHTMLEscape allows disabling html escaping in output
32 DisableHTMLEscape bool
33
khenaidooab1f7bd2019-11-14 14:00:27 -050034 // DataKey allows users to put all the log entry parameters into a nested dictionary at a given key.
35 DataKey string
36
37 // FieldMap allows users to customize the names of keys for default fields.
38 // As an example:
39 // formatter := &JSONFormatter{
40 // FieldMap: FieldMap{
41 // FieldKeyTime: "@timestamp",
42 // FieldKeyLevel: "@level",
43 // FieldKeyMsg: "@message",
44 // FieldKeyFunc: "@caller",
45 // },
46 // }
47 FieldMap FieldMap
48
khenaidood948f772021-08-11 17:49:24 -040049 // CallerPrettyfier can be set by the user to modify the content
50 // of the function and file keys in the json data when ReportCaller is
51 // activated. If any of the returned value is the empty string the
52 // corresponding key will be removed from json fields.
53 CallerPrettyfier func(*runtime.Frame) (function string, file string)
54
khenaidooab1f7bd2019-11-14 14:00:27 -050055 // PrettyPrint will indent all json logs
56 PrettyPrint bool
57}
58
59// Format renders a single log entry
60func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
61 data := make(Fields, len(entry.Data)+4)
62 for k, v := range entry.Data {
63 switch v := v.(type) {
64 case error:
65 // Otherwise errors are ignored by `encoding/json`
66 // https://github.com/sirupsen/logrus/issues/137
67 data[k] = v.Error()
68 default:
69 data[k] = v
70 }
71 }
72
73 if f.DataKey != "" {
74 newData := make(Fields, 4)
75 newData[f.DataKey] = data
76 data = newData
77 }
78
79 prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
80
81 timestampFormat := f.TimestampFormat
82 if timestampFormat == "" {
83 timestampFormat = defaultTimestampFormat
84 }
85
86 if entry.err != "" {
87 data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err
88 }
89 if !f.DisableTimestamp {
90 data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
91 }
92 data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
93 data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
94 if entry.HasCaller() {
khenaidood948f772021-08-11 17:49:24 -040095 funcVal := entry.Caller.Function
96 fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
97 if f.CallerPrettyfier != nil {
98 funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
99 }
100 if funcVal != "" {
101 data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
102 }
103 if fileVal != "" {
104 data[f.FieldMap.resolve(FieldKeyFile)] = fileVal
105 }
khenaidooab1f7bd2019-11-14 14:00:27 -0500106 }
107
108 var b *bytes.Buffer
109 if entry.Buffer != nil {
110 b = entry.Buffer
111 } else {
112 b = &bytes.Buffer{}
113 }
114
115 encoder := json.NewEncoder(b)
khenaidood948f772021-08-11 17:49:24 -0400116 encoder.SetEscapeHTML(!f.DisableHTMLEscape)
khenaidooab1f7bd2019-11-14 14:00:27 -0500117 if f.PrettyPrint {
118 encoder.SetIndent("", " ")
119 }
120 if err := encoder.Encode(data); err != nil {
khenaidood948f772021-08-11 17:49:24 -0400121 return nil, fmt.Errorf("failed to marshal fields to JSON, %v", err)
khenaidooab1f7bd2019-11-14 14:00:27 -0500122 }
123
124 return b.Bytes(), nil
125}