package logrus import ( "bytes" "encoding/json" "fmt" "runtime" "strconv" ) type fieldKey string // FieldMap allows customization of the key names for default fields. type FieldMap map[fieldKey]string func (f FieldMap) resolve(key fieldKey) string { if k, ok := f[key]; ok { return k } return string(key) } // JSONFormatter formats logs into parsable JSON. // // Fields from [Entry.Data] are included in the JSON object together with the // standard fields derived from the entry. If a field conflicts with a standard // field, it is prefixed with "fields.". Standard field names can be customized // through FieldMap. When DataKey is set, fields from [Entry.Data] are nested // under that key instead. type JSONFormatter struct { // TimestampFormat sets the format used for marshaling timestamps. // The format to use is the same than for time.Format or time.Parse from the standard // library. // The standard Library already provides a set of predefined format. TimestampFormat string // DisableTimestamp allows disabling automatic timestamps in output DisableTimestamp bool // DisableHTMLEscape allows disabling html escaping in output DisableHTMLEscape bool // DataKey allows users to put all the log entry parameters into a nested dictionary at a given key. DataKey string // FieldMap allows users to customize the names of keys for default fields. // As an example: // formatter := &JSONFormatter{ // FieldMap: FieldMap{ // FieldKeyTime: "@timestamp", // FieldKeyLevel: "@level", // FieldKeyMsg: "@message", // FieldKeyFunc: "@caller", // }, // } FieldMap FieldMap // CallerPrettyfier can be set by the user to modify the content // of the function and file keys in the json data when ReportCaller is // activated. If any of the returned value is the empty string the // corresponding key will be removed from json fields. CallerPrettyfier func(*runtime.Frame) (function string, file string) // PrettyPrint will indent all json logs PrettyPrint bool } // Format renders a single log entry func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { caller := entry.Caller data := make(Fields, len(entry.Data)+defaultFields) for k, v := range entry.Data { switch v := v.(type) { case error: // Otherwise errors are ignored by `encoding/json` // https://github.com/sirupsen/logrus/issues/137 data[k] = v.Error() default: data[k] = v } } if f.DataKey != "" && len(entry.Data) > 0 { newData := make(Fields, defaultFields+1) newData[f.DataKey] = data data = newData } hasCaller := caller != nil prefixFieldClashes(data, f.FieldMap, hasCaller) timestampFormat := f.TimestampFormat if timestampFormat == "" { timestampFormat = defaultTimestampFormat } if entry.err != "" { data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err } if !f.DisableTimestamp { data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat) } data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String() if caller != nil { var funcVal, fileVal string if f.CallerPrettyfier != nil { funcVal, fileVal = f.CallerPrettyfier(caller) } else { funcVal = caller.Function fileVal = caller.File + ":" + strconv.FormatInt(int64(caller.Line), 10) } if funcVal != "" { data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal } if fileVal != "" { data[f.FieldMap.resolve(FieldKeyFile)] = fileVal } } b := entry.Buffer if b == nil { b = new(bytes.Buffer) } encoder := json.NewEncoder(b) encoder.SetEscapeHTML(!f.DisableHTMLEscape) if f.PrettyPrint { encoder.SetIndent("", " ") } if err := encoder.Encode(data); err != nil { return nil, fmt.Errorf("failed to marshal fields to JSON, %w", err) } return b.Bytes(), nil }