Mercurial > yakumo_izuru > aya
comparison vendor/github.com/sirupsen/logrus/text_formatter.go @ 66:787b5ee0289d draft
Use vendored modules
Signed-off-by: Izuru Yakumo <yakumo.izuru@chaotic.ninja>
author | yakumo.izuru |
---|---|
date | Sun, 23 Jul 2023 13:18:53 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
65:6d985efa0f7a | 66:787b5ee0289d |
---|---|
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 } |