1package 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	// DisableHTMLEscape allows disabling html escaping in output
32	DisableHTMLEscape bool
33
34	// 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
49	// 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
55	// 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() {
95		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		}
106	}
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)
116	encoder.SetEscapeHTML(!f.DisableHTMLEscape)
117	if f.PrettyPrint {
118		encoder.SetIndent("", "  ")
119	}
120	if err := encoder.Encode(data); err != nil {
121		return nil, fmt.Errorf("failed to marshal fields to JSON, %v", err)
122	}
123
124	return b.Bytes(), nil
125}
126