blob: 076de5daed713bfe2638d8929583cdf72d76cb05 [file] [log] [blame]
Jonathan Hartf86817b2018-08-17 10:35:54 -07001package logrus
2
3import (
4 "bytes"
5 "fmt"
6 "runtime"
7 "sort"
8 "strings"
9 "time"
10)
11
12const (
13 nocolor = 0
14 red = 31
15 green = 32
16 yellow = 33
17 blue = 34
18 gray = 37
19)
20
21var (
22 baseTimestamp time.Time
23 isTerminal bool
24)
25
26func init() {
27 baseTimestamp = time.Now()
28 isTerminal = IsTerminal()
29}
30
31type TextFormatter struct {
32 // Set to true to bypass checking for a TTY before outputting colors.
33 ForceColors bool
34
35 // Force disabling colors.
36 DisableColors bool
37
38 // Disable timestamp logging. useful when output is redirected to logging
39 // system that already adds timestamps.
40 DisableTimestamp bool
41
42 // Enable logging the full timestamp when a TTY is attached instead of just
43 // the time passed since beginning of execution.
44 FullTimestamp bool
45
46 // TimestampFormat to use for display when a full timestamp is printed
47 TimestampFormat string
48
49 // The fields are sorted by default for a consistent output. For applications
50 // that log extremely frequently and don't use the JSON formatter this may not
51 // be desired.
52 DisableSorting bool
53}
54
55func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
56 var b *bytes.Buffer
57 keys := make([]string, 0, len(entry.Data))
58 for k := range entry.Data {
59 keys = append(keys, k)
60 }
61
62 if !f.DisableSorting {
63 sort.Strings(keys)
64 }
65 if entry.Buffer != nil {
66 b = entry.Buffer
67 } else {
68 b = &bytes.Buffer{}
69 }
70
71 prefixFieldClashes(entry.Data)
72
73 isColorTerminal := isTerminal && (runtime.GOOS != "windows")
74 isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors
75
76 timestampFormat := f.TimestampFormat
77 if timestampFormat == "" {
78 timestampFormat = DefaultTimestampFormat
79 }
80 if isColored {
81 f.printColored(b, entry, keys, timestampFormat)
82 } else {
83 if !f.DisableTimestamp {
84 f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
85 }
86 f.appendKeyValue(b, "level", entry.Level.String())
87 if entry.Message != "" {
88 f.appendKeyValue(b, "msg", entry.Message)
89 }
90 for _, key := range keys {
91 f.appendKeyValue(b, key, entry.Data[key])
92 }
93 }
94
95 b.WriteByte('\n')
96 return b.Bytes(), nil
97}
98
99func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
100 var levelColor int
101 switch entry.Level {
102 case DebugLevel:
103 levelColor = gray
104 case WarnLevel:
105 levelColor = yellow
106 case ErrorLevel, FatalLevel, PanicLevel:
107 levelColor = red
108 default:
109 levelColor = blue
110 }
111
112 levelText := strings.ToUpper(entry.Level.String())[0:4]
113
114 if f.DisableTimestamp {
115 fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m %-44s ", levelColor, levelText, entry.Message)
116 } else if !f.FullTimestamp {
117 fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), entry.Message)
118 } else {
119 fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
120 }
121 for _, k := range keys {
122 v := entry.Data[k]
123 fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
124 f.appendValue(b, v)
125 }
126}
127
128func needsQuoting(text string) bool {
129 for _, ch := range text {
130 if !((ch >= 'a' && ch <= 'z') ||
131 (ch >= 'A' && ch <= 'Z') ||
132 (ch >= '0' && ch <= '9') ||
133 ch == '-' || ch == '.') {
134 return true
135 }
136 }
137 return false
138}
139
140func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
141
142 b.WriteString(key)
143 b.WriteByte('=')
144 f.appendValue(b, value)
145 b.WriteByte(' ')
146}
147
148func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
149 switch value := value.(type) {
150 case string:
151 if !needsQuoting(value) {
152 b.WriteString(value)
153 } else {
154 fmt.Fprintf(b, "%q", value)
155 }
156 case error:
157 errmsg := value.Error()
158 if !needsQuoting(errmsg) {
159 b.WriteString(errmsg)
160 } else {
161 fmt.Fprintf(b, "%q", errmsg)
162 }
163 default:
164 fmt.Fprint(b, value)
165 }
166}