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