diff options
Diffstat (limited to 'vendor/github.com/sirupsen/logrus/text_formatter.go')
-rw-r--r-- | vendor/github.com/sirupsen/logrus/text_formatter.go | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/vendor/github.com/sirupsen/logrus/text_formatter.go b/vendor/github.com/sirupsen/logrus/text_formatter.go new file mode 100644 index 0000000..be2c6ef --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/text_formatter.go | |||
@@ -0,0 +1,339 @@ | |||
1 | package logrus | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | "fmt" | ||
6 | "os" | ||
7 | "runtime" | ||
8 | "sort" | ||
9 | "strconv" | ||
10 | "strings" | ||
11 | "sync" | ||
12 | "time" | ||
13 | "unicode/utf8" | ||
14 | ) | ||
15 | |||
16 | const ( | ||
17 | red = 31 | ||
18 | yellow = 33 | ||
19 | blue = 36 | ||
20 | gray = 37 | ||
21 | ) | ||
22 | |||
23 | var baseTimestamp time.Time | ||
24 | |||
25 | func init() { | ||
26 | baseTimestamp = time.Now() | ||
27 | } | ||
28 | |||
29 | // TextFormatter formats logs into text | ||
30 | type TextFormatter struct { | ||
31 | // Set to true to bypass checking for a TTY before outputting colors. | ||
32 | ForceColors bool | ||
33 | |||
34 | // Force disabling colors. | ||
35 | DisableColors bool | ||
36 | |||
37 | // Force quoting of all values | ||
38 | ForceQuote bool | ||
39 | |||
40 | // DisableQuote disables quoting for all values. | ||
41 | // DisableQuote will have a lower priority than ForceQuote. | ||
42 | // If both of them are set to true, quote will be forced on all values. | ||
43 | DisableQuote bool | ||
44 | |||
45 | // Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/ | ||
46 | EnvironmentOverrideColors bool | ||
47 | |||
48 | // Disable timestamp logging. useful when output is redirected to logging | ||
49 | // system that already adds timestamps. | ||
50 | DisableTimestamp bool | ||
51 | |||
52 | // Enable logging the full timestamp when a TTY is attached instead of just | ||
53 | // the time passed since beginning of execution. | ||
54 | FullTimestamp bool | ||
55 | |||
56 | // TimestampFormat to use for display when a full timestamp is printed. | ||
57 | // The format to use is the same than for time.Format or time.Parse from the standard | ||
58 | // library. | ||
59 | // The standard Library already provides a set of predefined format. | ||
60 | TimestampFormat string | ||
61 | |||
62 | // The fields are sorted by default for a consistent output. For applications | ||
63 | // that log extremely frequently and don't use the JSON formatter this may not | ||
64 | // be desired. | ||
65 | DisableSorting bool | ||
66 | |||
67 | // The keys sorting function, when uninitialized it uses sort.Strings. | ||
68 | SortingFunc func([]string) | ||
69 | |||
70 | // Disables the truncation of the level text to 4 characters. | ||
71 | DisableLevelTruncation bool | ||
72 | |||
73 | // PadLevelText Adds padding the level text so that all the levels output at the same length | ||
74 | // PadLevelText is a superset of the DisableLevelTruncation option | ||
75 | PadLevelText bool | ||
76 | |||
77 | // QuoteEmptyFields will wrap empty fields in quotes if true | ||
78 | QuoteEmptyFields bool | ||
79 | |||
80 | // Whether the logger's out is to a terminal | ||
81 | isTerminal bool | ||
82 | |||
83 | // FieldMap allows users to customize the names of keys for default fields. | ||
84 | // As an example: | ||
85 | // formatter := &TextFormatter{ | ||
86 | // FieldMap: FieldMap{ | ||
87 | // FieldKeyTime: "@timestamp", | ||
88 | // FieldKeyLevel: "@level", | ||
89 | // FieldKeyMsg: "@message"}} | ||
90 | FieldMap FieldMap | ||
91 | |||
92 | // CallerPrettyfier can be set by the user to modify the content | ||
93 | // of the function and file keys in the data when ReportCaller is | ||
94 | // activated. If any of the returned value is the empty string the | ||
95 | // corresponding key will be removed from fields. | ||
96 | CallerPrettyfier func(*runtime.Frame) (function string, file string) | ||
97 | |||
98 | terminalInitOnce sync.Once | ||
99 | |||
100 | // The max length of the level text, generated dynamically on init | ||
101 | levelTextMaxLength int | ||
102 | } | ||
103 | |||
104 | func (f *TextFormatter) init(entry *Entry) { | ||
105 | if entry.Logger != nil { | ||
106 | f.isTerminal = checkIfTerminal(entry.Logger.Out) | ||
107 | } | ||
108 | // Get the max length of the level text | ||
109 | for _, level := range AllLevels { | ||
110 | levelTextLength := utf8.RuneCount([]byte(level.String())) | ||
111 | if levelTextLength > f.levelTextMaxLength { | ||
112 | f.levelTextMaxLength = levelTextLength | ||
113 | } | ||
114 | } | ||
115 | } | ||
116 | |||
117 | func (f *TextFormatter) isColored() bool { | ||
118 | isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows")) | ||
119 | |||
120 | if f.EnvironmentOverrideColors { | ||
121 | switch force, ok := os.LookupEnv("CLICOLOR_FORCE"); { | ||
122 | case ok && force != "0": | ||
123 | isColored = true | ||
124 | case ok && force == "0", os.Getenv("CLICOLOR") == "0": | ||
125 | isColored = false | ||
126 | } | ||
127 | } | ||
128 | |||
129 | return isColored && !f.DisableColors | ||
130 | } | ||
131 | |||
132 | // Format renders a single log entry | ||
133 | func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { | ||
134 | data := make(Fields) | ||
135 | for k, v := range entry.Data { | ||
136 | data[k] = v | ||
137 | } | ||
138 | prefixFieldClashes(data, f.FieldMap, entry.HasCaller()) | ||
139 | keys := make([]string, 0, len(data)) | ||
140 | for k := range data { | ||
141 | keys = append(keys, k) | ||
142 | } | ||
143 | |||
144 | var funcVal, fileVal string | ||
145 | |||
146 | fixedKeys := make([]string, 0, 4+len(data)) | ||
147 | if !f.DisableTimestamp { | ||
148 | fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime)) | ||
149 | } | ||
150 | fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel)) | ||
151 | if entry.Message != "" { | ||
152 | fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg)) | ||
153 | } | ||
154 | if entry.err != "" { | ||
155 | fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError)) | ||
156 | } | ||
157 | if entry.HasCaller() { | ||
158 | if f.CallerPrettyfier != nil { | ||
159 | funcVal, fileVal = f.CallerPrettyfier(entry.Caller) | ||
160 | } else { | ||
161 | funcVal = entry.Caller.Function | ||
162 | fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) | ||
163 | } | ||
164 | |||
165 | if funcVal != "" { | ||
166 | fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFunc)) | ||
167 | } | ||
168 | if fileVal != "" { | ||
169 | fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFile)) | ||
170 | } | ||
171 | } | ||
172 | |||
173 | if !f.DisableSorting { | ||
174 | if f.SortingFunc == nil { | ||
175 | sort.Strings(keys) | ||
176 | fixedKeys = append(fixedKeys, keys...) | ||
177 | } else { | ||
178 | if !f.isColored() { | ||
179 | fixedKeys = append(fixedKeys, keys...) | ||
180 | f.SortingFunc(fixedKeys) | ||
181 | } else { | ||
182 | f.SortingFunc(keys) | ||
183 | } | ||
184 | } | ||
185 | } else { | ||
186 | fixedKeys = append(fixedKeys, keys...) | ||
187 | } | ||
188 | |||
189 | var b *bytes.Buffer | ||
190 | if entry.Buffer != nil { | ||
191 | b = entry.Buffer | ||
192 | } else { | ||
193 | b = &bytes.Buffer{} | ||
194 | } | ||
195 | |||
196 | f.terminalInitOnce.Do(func() { f.init(entry) }) | ||
197 | |||
198 | timestampFormat := f.TimestampFormat | ||
199 | if timestampFormat == "" { | ||
200 | timestampFormat = defaultTimestampFormat | ||
201 | } | ||
202 | if f.isColored() { | ||
203 | f.printColored(b, entry, keys, data, timestampFormat) | ||
204 | } else { | ||
205 | |||
206 | for _, key := range fixedKeys { | ||
207 | var value interface{} | ||
208 | switch { | ||
209 | case key == f.FieldMap.resolve(FieldKeyTime): | ||
210 | value = entry.Time.Format(timestampFormat) | ||
211 | case key == f.FieldMap.resolve(FieldKeyLevel): | ||
212 | value = entry.Level.String() | ||
213 | case key == f.FieldMap.resolve(FieldKeyMsg): | ||
214 | value = entry.Message | ||
215 | case key == f.FieldMap.resolve(FieldKeyLogrusError): | ||
216 | value = entry.err | ||
217 | case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller(): | ||
218 | value = funcVal | ||
219 | case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller(): | ||
220 | value = fileVal | ||
221 | default: | ||
222 | value = data[key] | ||
223 | } | ||
224 | f.appendKeyValue(b, key, value) | ||
225 | } | ||
226 | } | ||
227 | |||
228 | b.WriteByte('\n') | ||
229 | return b.Bytes(), nil | ||
230 | } | ||
231 | |||
232 | func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) { | ||
233 | var levelColor int | ||
234 | switch entry.Level { | ||
235 | case DebugLevel, TraceLevel: | ||
236 | levelColor = gray | ||
237 | case WarnLevel: | ||
238 | levelColor = yellow | ||
239 | case ErrorLevel, FatalLevel, PanicLevel: | ||
240 | levelColor = red | ||
241 | case InfoLevel: | ||
242 | levelColor = blue | ||
243 | default: | ||
244 | levelColor = blue | ||
245 | } | ||
246 | |||
247 | levelText := strings.ToUpper(entry.Level.String()) | ||
248 | if !f.DisableLevelTruncation && !f.PadLevelText { | ||
249 | levelText = levelText[0:4] | ||
250 | } | ||
251 | if f.PadLevelText { | ||
252 | // Generates the format string used in the next line, for example "%-6s" or "%-7s". | ||
253 | // Based on the max level text length. | ||
254 | formatString := "%-" + strconv.Itoa(f.levelTextMaxLength) + "s" | ||
255 | // Formats the level text by appending spaces up to the max length, for example: | ||
256 | // - "INFO " | ||
257 | // - "WARNING" | ||
258 | levelText = fmt.Sprintf(formatString, levelText) | ||
259 | } | ||
260 | |||
261 | // Remove a single newline if it already exists in the message to keep | ||
262 | // the behavior of logrus text_formatter the same as the stdlib log package | ||
263 | entry.Message = strings.TrimSuffix(entry.Message, "\n") | ||
264 | |||
265 | caller := "" | ||
266 | if entry.HasCaller() { | ||
267 | funcVal := fmt.Sprintf("%s()", entry.Caller.Function) | ||
268 | fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) | ||
269 | |||
270 | if f.CallerPrettyfier != nil { | ||
271 | funcVal, fileVal = f.CallerPrettyfier(entry.Caller) | ||
272 | } | ||
273 | |||
274 | if fileVal == "" { | ||
275 | caller = funcVal | ||
276 | } else if funcVal == "" { | ||
277 | caller = fileVal | ||
278 | } else { | ||
279 | caller = fileVal + " " + funcVal | ||
280 | } | ||
281 | } | ||
282 | |||
283 | switch { | ||
284 | case f.DisableTimestamp: | ||
285 | fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message) | ||
286 | case !f.FullTimestamp: | ||
287 | fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message) | ||
288 | default: | ||
289 | fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message) | ||
290 | } | ||
291 | for _, k := range keys { | ||
292 | v := data[k] | ||
293 | fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k) | ||
294 | f.appendValue(b, v) | ||
295 | } | ||
296 | } | ||
297 | |||
298 | func (f *TextFormatter) needsQuoting(text string) bool { | ||
299 | if f.ForceQuote { | ||
300 | return true | ||
301 | } | ||
302 | if f.QuoteEmptyFields && len(text) == 0 { | ||
303 | return true | ||
304 | } | ||
305 | if f.DisableQuote { | ||
306 | return false | ||
307 | } | ||
308 | for _, ch := range text { | ||
309 | if !((ch >= 'a' && ch <= 'z') || | ||
310 | (ch >= 'A' && ch <= 'Z') || | ||
311 | (ch >= '0' && ch <= '9') || | ||
312 | ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') { | ||
313 | return true | ||
314 | } | ||
315 | } | ||
316 | return false | ||
317 | } | ||
318 | |||
319 | func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) { | ||
320 | if b.Len() > 0 { | ||
321 | b.WriteByte(' ') | ||
322 | } | ||
323 | b.WriteString(key) | ||
324 | b.WriteByte('=') | ||
325 | f.appendValue(b, value) | ||
326 | } | ||
327 | |||
328 | func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) { | ||
329 | stringVal, ok := value.(string) | ||
330 | if !ok { | ||
331 | stringVal = fmt.Sprint(value) | ||
332 | } | ||
333 | |||
334 | if !f.needsQuoting(stringVal) { | ||
335 | b.WriteString(stringVal) | ||
336 | } else { | ||
337 | b.WriteString(fmt.Sprintf("%q", stringVal)) | ||
338 | } | ||
339 | } | ||