1 package logrus
2
3 import (
4 "bytes"
5 "fmt"
6 "os"
7 "runtime"
8 "sort"
9 "strings"
10 "sync"
11 "time"
12 )
13
14 const (
15 nocolor = 0
16 red = 31
17 green = 32
18 yellow = 33
19 blue = 36
20 gray = 37
21 )
22
23 var (
24 baseTimestamp time.Time
25 emptyFieldMap FieldMap
26 )
27
28 func init() {
29 baseTimestamp = time.Now()
30 }
31
32
33 type TextFormatter struct {
34
35 ForceColors bool
36
37
38 DisableColors bool
39
40
41 EnvironmentOverrideColors bool
42
43
44
45 DisableTimestamp bool
46
47
48
49 FullTimestamp bool
50
51
52 TimestampFormat string
53
54
55
56
57 DisableSorting bool
58
59
60 SortingFunc func([]string)
61
62
63 DisableLevelTruncation bool
64
65
66 QuoteEmptyFields bool
67
68
69 isTerminal bool
70
71
72
73
74
75
76
77
78 FieldMap FieldMap
79
80 terminalInitOnce sync.Once
81 }
82
83 func (f *TextFormatter) init(entry *Entry) {
84 if entry.Logger != nil {
85 f.isTerminal = checkIfTerminal(entry.Logger.Out)
86
87 if f.isTerminal {
88 initTerminal(entry.Logger.Out)
89 }
90 }
91 }
92
93 func (f *TextFormatter) isColored() bool {
94 isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows"))
95
96 if f.EnvironmentOverrideColors {
97 if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok && force != "0" {
98 isColored = true
99 } else if ok && force == "0" {
100 isColored = false
101 } else if os.Getenv("CLICOLOR") == "0" {
102 isColored = false
103 }
104 }
105
106 return isColored && !f.DisableColors
107 }
108
109
110 func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
111 data := make(Fields)
112 for k, v := range entry.Data {
113 data[k] = v
114 }
115 prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
116 keys := make([]string, 0, len(data))
117 for k := range data {
118 keys = append(keys, k)
119 }
120
121 fixedKeys := make([]string, 0, 4+len(data))
122 if !f.DisableTimestamp {
123 fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime))
124 }
125 fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel))
126 if entry.Message != "" {
127 fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg))
128 }
129 if entry.err != "" {
130 fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError))
131 }
132 if entry.HasCaller() {
133 fixedKeys = append(fixedKeys,
134 f.FieldMap.resolve(FieldKeyFunc), f.FieldMap.resolve(FieldKeyFile))
135 }
136
137 if !f.DisableSorting {
138 if f.SortingFunc == nil {
139 sort.Strings(keys)
140 fixedKeys = append(fixedKeys, keys...)
141 } else {
142 if !f.isColored() {
143 fixedKeys = append(fixedKeys, keys...)
144 f.SortingFunc(fixedKeys)
145 } else {
146 f.SortingFunc(keys)
147 }
148 }
149 } else {
150 fixedKeys = append(fixedKeys, keys...)
151 }
152
153 var b *bytes.Buffer
154 if entry.Buffer != nil {
155 b = entry.Buffer
156 } else {
157 b = &bytes.Buffer{}
158 }
159
160 f.terminalInitOnce.Do(func() { f.init(entry) })
161
162 timestampFormat := f.TimestampFormat
163 if timestampFormat == "" {
164 timestampFormat = defaultTimestampFormat
165 }
166 if f.isColored() {
167 f.printColored(b, entry, keys, data, timestampFormat)
168 } else {
169 for _, key := range fixedKeys {
170 var value interface{}
171 switch {
172 case key == f.FieldMap.resolve(FieldKeyTime):
173 value = entry.Time.Format(timestampFormat)
174 case key == f.FieldMap.resolve(FieldKeyLevel):
175 value = entry.Level.String()
176 case key == f.FieldMap.resolve(FieldKeyMsg):
177 value = entry.Message
178 case key == f.FieldMap.resolve(FieldKeyLogrusError):
179 value = entry.err
180 case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller():
181 value = entry.Caller.Function
182 case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller():
183 value = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
184 default:
185 value = data[key]
186 }
187 f.appendKeyValue(b, key, value)
188 }
189 }
190
191 b.WriteByte('\n')
192 return b.Bytes(), nil
193 }
194
195 func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) {
196 var levelColor int
197 switch entry.Level {
198 case DebugLevel, TraceLevel:
199 levelColor = gray
200 case WarnLevel:
201 levelColor = yellow
202 case ErrorLevel, FatalLevel, PanicLevel:
203 levelColor = red
204 default:
205 levelColor = blue
206 }
207
208 levelText := strings.ToUpper(entry.Level.String())
209 if !f.DisableLevelTruncation {
210 levelText = levelText[0:4]
211 }
212
213
214
215 entry.Message = strings.TrimSuffix(entry.Message, "\n")
216
217 caller := ""
218
219 if entry.HasCaller() {
220 caller = fmt.Sprintf("%s:%d %s()",
221 entry.Caller.File, entry.Caller.Line, entry.Caller.Function)
222 }
223
224 if f.DisableTimestamp {
225 fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message)
226 } else if !f.FullTimestamp {
227 fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message)
228 } else {
229 fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message)
230 }
231 for _, k := range keys {
232 v := data[k]
233 fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
234 f.appendValue(b, v)
235 }
236 }
237
238 func (f *TextFormatter) needsQuoting(text string) bool {
239 if f.QuoteEmptyFields && len(text) == 0 {
240 return true
241 }
242 for _, ch := range text {
243 if !((ch >= 'a' && ch <= 'z') ||
244 (ch >= 'A' && ch <= 'Z') ||
245 (ch >= '0' && ch <= '9') ||
246 ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
247 return true
248 }
249 }
250 return false
251 }
252
253 func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
254 if b.Len() > 0 {
255 b.WriteByte(' ')
256 }
257 b.WriteString(key)
258 b.WriteByte('=')
259 f.appendValue(b, value)
260 }
261
262 func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
263 stringVal, ok := value.(string)
264 if !ok {
265 stringVal = fmt.Sprint(value)
266 }
267
268 if !f.needsQuoting(stringVal) {
269 b.WriteString(stringVal)
270 } else {
271 b.WriteString(fmt.Sprintf("%q", stringVal))
272 }
273 }
274
View as plain text