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	// 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.
29	TimestampFormat string
30
31	// DisableTimestamp allows disabling automatic timestamps in output
32	DisableTimestamp bool
33
34	// DisableHTMLEscape allows disabling html escaping in output
35	DisableHTMLEscape bool
36
37	// 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)
119	encoder.SetEscapeHTML(!f.DisableHTMLEscape)
120	if f.PrettyPrint {
121		encoder.SetIndent("", "  ")
122	}
123	if err := encoder.Encode(data); err != nil {
124		return nil, fmt.Errorf("failed to marshal fields to JSON, %w", err)
125	}
126
127	return b.Bytes(), nil
128}
129