func prefixFieldClashes(data log.Fields, fieldMap FieldMap, reportCaller bool) {
	timeKey := fieldMap.resolve(FieldKeyTime)
	if t, ok := data[timeKey]; ok {
		data["fields."+timeKey] = t
		delete(data, timeKey)
	}
	msgKey := fieldMap.resolve(FieldKeyMsg)
	if m, ok := data[msgKey]; ok {
		data["fields."+msgKey] = m
		delete(data, msgKey)
	}
	levelKey := fieldMap.resolve(FieldKeyLevel)
	if l, ok := data[levelKey]; ok {
		data["fields."+levelKey] = l
		delete(data, levelKey)
	}
	logrusErrKey := fieldMap.resolve(FieldKeyLogrusError)
	if l, ok := data[logrusErrKey]; ok {
		data["fields."+logrusErrKey] = l
		delete(data, logrusErrKey)
	}
	if reportCaller {
		funcKey := fieldMap.resolve(FieldKeyFunc)
		if l, ok := data[funcKey]; ok {
			data["fields."+funcKey] = l
		}
		fileKey := fieldMap.resolve(FieldKeyFile)
		if l, ok := data[fileKey]; ok {
			data["fields."+fileKey] = l
		}
	}
}
This is to not silently overwrite `time`, `msg`, `func` and `level` fields when
dumping it. If this code wasn't there doing:

logrus.WithField("level", 1).Info("hello")

Would just silently drop the user provided level. Instead with this code
it'll logged as:

{"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}

It's not exported because it's still using Data in an opinionated way. It's to
avoid code duplication between the two default formatters.
package json
import (
	"bytes"
	"encoding/json"
	"fmt"
	"runtime"
	"time"
	"github.com/eolinker/eosc/log"
)
const (
	defaultTimestampFormat = time.RFC3339
	FieldKeyMsg            = "msg"
	FieldKeyLevel          = "level"
	FieldKeyTime           = "time"
	FieldKeyLogrusError    = "logrus_error"
	FieldKeyFunc           = "func"
	FieldKeyFile           = "file"
)
type fieldKey string
type FieldMap map[fieldKey]string
func (f FieldMap) resolve(key fieldKey) string {
	if k, ok := f[key]; ok {
		return k
	}
	return string(key)
}
type JSONFormatter struct {
	TimestampFormat string
	DisableTimestamp bool
	DataKey string
	Fields  []string
	FieldMap FieldMap
	CallerPrettyfier func(*runtime.Frame) (function string, file string)
	PrettyPrint bool
}
func (f *JSONFormatter) Format(entry *log.Entry) ([]byte, error) {
	data := make(log.Fields, len(entry.Data)+4)
	if len(f.Fields) > 0 {
		for _, f := range f.Fields {
			if v, has := entry.Data[f]; has {
				switch v := v.(type) {
				case error:
					data[f] = v.Error()
				default:
					data[f] = v
				}
			}
		}
	} else {
		for k, v := range entry.Data {
			switch v := v.(type) {
			case error:
				data[k] = v.Error()
			default:
				data[k] = v
			}
		}
	}
	if f.DataKey != "" {
		newData := make(log.Fields, 4)
		newData[f.DataKey] = data
		data = newData
	}
	prefixFieldClashes(data, f.FieldMap, entry.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 entry.HasCaller() {
		funcVal := entry.Caller.Function
		fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
		if f.CallerPrettyfier != nil {
			funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
		}
		if funcVal != "" {
			data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
		}
		if fileVal != "" {
			data[f.FieldMap.resolve(FieldKeyFile)] = fileVal
		}
	}
	b := &bytes.Buffer{}
	encoder := json.NewEncoder(b)
	if f.PrettyPrint {
		encoder.SetIndent("", "  ")
	}
	if err := encoder.Encode(data); err != nil {
		return nil, fmt.Errorf("failed to marshal fields to JSON, %v", err)
	}
	return b.Bytes(), nil
}