blob: c96dc5636bf0494761604f6db012c55bebe93027 [file] [log] [blame]
kesavand2cde6582020-06-22 04:56:23 -04001package 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.
kesavandc71914f2022-03-25 11:19:03 +053026 // The format to use is the same than for time.Format or time.Parse from the standard
27 // library.
28 // The standard Library already provides a set of predefined format.
kesavand2cde6582020-06-22 04:56:23 -040029 TimestampFormat string
30
31 // DisableTimestamp allows disabling automatic timestamps in output
32 DisableTimestamp bool
33
kesavandc71914f2022-03-25 11:19:03 +053034 // DisableHTMLEscape allows disabling html escaping in output
35 DisableHTMLEscape bool
36
kesavand2cde6582020-06-22 04:56:23 -040037 // DataKey allows users to put all the log entry parameters into a nested dictionary at a given key.
38 DataKey string
39
40 // FieldMap allows users to customize the names of keys for default fields.
41 // As an example:
42 // formatter := &JSONFormatter{
43 // FieldMap: FieldMap{
44 // FieldKeyTime: "@timestamp",
45 // FieldKeyLevel: "@level",
46 // FieldKeyMsg: "@message",
47 // FieldKeyFunc: "@caller",
48 // },
49 // }
50 FieldMap FieldMap
51
52 // CallerPrettyfier can be set by the user to modify the content
53 // of the function and file keys in the json data when ReportCaller is
54 // activated. If any of the returned value is the empty string the
55 // corresponding key will be removed from json fields.
56 CallerPrettyfier func(*runtime.Frame) (function string, file string)
57
58 // PrettyPrint will indent all json logs
59 PrettyPrint bool
60}
61
62// Format renders a single log entry
63func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
64 data := make(Fields, len(entry.Data)+4)
65 for k, v := range entry.Data {
66 switch v := v.(type) {
67 case error:
68 // Otherwise errors are ignored by `encoding/json`
69 // https://github.com/sirupsen/logrus/issues/137
70 data[k] = v.Error()
71 default:
72 data[k] = v
73 }
74 }
75
76 if f.DataKey != "" {
77 newData := make(Fields, 4)
78 newData[f.DataKey] = data
79 data = newData
80 }
81
82 prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
83
84 timestampFormat := f.TimestampFormat
85 if timestampFormat == "" {
86 timestampFormat = defaultTimestampFormat
87 }
88
89 if entry.err != "" {
90 data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err
91 }
92 if !f.DisableTimestamp {
93 data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
94 }
95 data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
96 data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
97 if entry.HasCaller() {
98 funcVal := entry.Caller.Function
99 fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
100 if f.CallerPrettyfier != nil {
101 funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
102 }
103 if funcVal != "" {
104 data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
105 }
106 if fileVal != "" {
107 data[f.FieldMap.resolve(FieldKeyFile)] = fileVal
108 }
109 }
110
111 var b *bytes.Buffer
112 if entry.Buffer != nil {
113 b = entry.Buffer
114 } else {
115 b = &bytes.Buffer{}
116 }
117
118 encoder := json.NewEncoder(b)
kesavandc71914f2022-03-25 11:19:03 +0530119 encoder.SetEscapeHTML(!f.DisableHTMLEscape)
kesavand2cde6582020-06-22 04:56:23 -0400120 if f.PrettyPrint {
121 encoder.SetIndent("", " ")
122 }
123 if err := encoder.Encode(data); err != nil {
kesavandc71914f2022-03-25 11:19:03 +0530124 return nil, fmt.Errorf("failed to marshal fields to JSON, %w", err)
kesavand2cde6582020-06-22 04:56:23 -0400125 }
126
127 return b.Bytes(), nil
128}