cloudflare/cloudflared
Publicmirrored from https://github.com/cloudflare/cloudflaredAvailable
log/log.go
85lines · modecode
| 1 | // this forks the logrus json formatter to rename msg -> message as that's the |
| 2 | // expected field. Ideally the logger should make it easier for us. |
| 3 | package log |
| 4 | |
| 5 | import ( |
| 6 | "encoding/json" |
| 7 | "fmt" |
| 8 | "runtime" |
| 9 | "time" |
| 10 | |
| 11 | "github.com/mattn/go-colorable" |
| 12 | "github.com/sirupsen/logrus" |
| 13 | ) |
| 14 | |
| 15 | var ( |
| 16 | DefaultTimestampFormat = time.RFC3339Nano |
| 17 | ) |
| 18 | |
| 19 | type JSONFormatter struct { |
| 20 | // TimestampFormat sets the format used for marshaling timestamps. |
| 21 | TimestampFormat string |
| 22 | } |
| 23 | |
| 24 | func CreateLogger() *logrus.Logger { |
| 25 | logger := logrus.New() |
| 26 | logger.Out = colorable.NewColorableStderr() |
| 27 | logger.Formatter = &logrus.TextFormatter{ForceColors: runtime.GOOS == "windows"} |
| 28 | return logger |
| 29 | } |
| 30 | |
| 31 | func (f *JSONFormatter) Format(entry *logrus.Entry) ([]byte, error) { |
| 32 | data := make(logrus.Fields, len(entry.Data)+3) |
| 33 | for k, v := range entry.Data { |
| 34 | switch v := v.(type) { |
| 35 | case error: |
| 36 | // Otherwise errors are ignored by `encoding/json` |
| 37 | // https://github.com/sirupsen/logrus/issues/137 |
| 38 | data[k] = v.Error() |
| 39 | default: |
| 40 | data[k] = v |
| 41 | } |
| 42 | } |
| 43 | prefixFieldClashes(data) |
| 44 | |
| 45 | timestampFormat := f.TimestampFormat |
| 46 | if timestampFormat == "" { |
| 47 | timestampFormat = DefaultTimestampFormat |
| 48 | } |
| 49 | |
| 50 | data["time"] = entry.Time.Format(timestampFormat) |
| 51 | data["message"] = entry.Message |
| 52 | data["level"] = entry.Level.String() |
| 53 | |
| 54 | serialized, err := json.Marshal(data) |
| 55 | if err != nil { |
| 56 | return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) |
| 57 | } |
| 58 | return append(serialized, '\n'), nil |
| 59 | } |
| 60 | |
| 61 | // This is to not silently overwrite `time`, `msg` and `level` fields when |
| 62 | // dumping it. If this code wasn't there doing: |
| 63 | // |
| 64 | // logrus.WithField("level", 1).Info("hello") |
| 65 | // |
| 66 | // Would just silently drop the user provided level. Instead with this code |
| 67 | // it'll logged as: |
| 68 | // |
| 69 | // {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."} |
| 70 | // |
| 71 | // It's not exported because it's still using Data in an opinionated way. It's to |
| 72 | // avoid code duplication between the two default formatters. |
| 73 | func prefixFieldClashes(data logrus.Fields) { |
| 74 | if t, ok := data["time"]; ok { |
| 75 | data["fields.time"] = t |
| 76 | } |
| 77 | |
| 78 | if m, ok := data["msg"]; ok { |
| 79 | data["fields.msg"] = m |
| 80 | } |
| 81 | |
| 82 | if l, ok := data["level"]; ok { |
| 83 | data["fields.level"] = l |
| 84 | } |
| 85 | } |