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