1 package zerolog
2
3 import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "io"
8 "os"
9 "path/filepath"
10 "sort"
11 "strconv"
12 "strings"
13 "sync"
14 "time"
15
16 "github.com/mattn/go-colorable"
17 )
18
19 const (
20 colorBlack = iota + 30
21 colorRed
22 colorGreen
23 colorYellow
24 colorBlue
25 colorMagenta
26 colorCyan
27 colorWhite
28
29 colorBold = 1
30 colorDarkGray = 90
31 )
32
33 var (
34 consoleBufPool = sync.Pool{
35 New: func() interface{} {
36 return bytes.NewBuffer(make([]byte, 0, 100))
37 },
38 }
39 )
40
41 const (
42 consoleDefaultTimeFormat = time.Kitchen
43 )
44
45
46 type Formatter func(interface{}) string
47
48
49
50 type ConsoleWriter struct {
51
52 Out io.Writer
53
54
55 NoColor bool
56
57
58 TimeFormat string
59
60
61 PartsOrder []string
62
63
64 PartsExclude []string
65
66
67 FieldsExclude []string
68
69 FormatTimestamp Formatter
70 FormatLevel Formatter
71 FormatCaller Formatter
72 FormatMessage Formatter
73 FormatFieldName Formatter
74 FormatFieldValue Formatter
75 FormatErrFieldName Formatter
76 FormatErrFieldValue Formatter
77
78 FormatExtra func(map[string]interface{}, *bytes.Buffer) error
79 }
80
81
82 func NewConsoleWriter(options ...func(w *ConsoleWriter)) ConsoleWriter {
83 w := ConsoleWriter{
84 Out: os.Stdout,
85 TimeFormat: consoleDefaultTimeFormat,
86 PartsOrder: consoleDefaultPartsOrder(),
87 }
88
89 for _, opt := range options {
90 opt(&w)
91 }
92
93
94 if w.Out == os.Stdout || w.Out == os.Stderr {
95 w.Out = colorable.NewColorable(w.Out.(*os.File))
96 }
97
98 return w
99 }
100
101
102 func (w ConsoleWriter) Write(p []byte) (n int, err error) {
103
104 if w.Out == os.Stdout || w.Out == os.Stderr {
105 w.Out = colorable.NewColorable(w.Out.(*os.File))
106 }
107
108 if w.PartsOrder == nil {
109 w.PartsOrder = consoleDefaultPartsOrder()
110 }
111
112 var buf = consoleBufPool.Get().(*bytes.Buffer)
113 defer func() {
114 buf.Reset()
115 consoleBufPool.Put(buf)
116 }()
117
118 var evt map[string]interface{}
119 p = decodeIfBinaryToBytes(p)
120 d := json.NewDecoder(bytes.NewReader(p))
121 d.UseNumber()
122 err = d.Decode(&evt)
123 if err != nil {
124 return n, fmt.Errorf("cannot decode event: %s", err)
125 }
126
127 for _, p := range w.PartsOrder {
128 w.writePart(buf, evt, p)
129 }
130
131 w.writeFields(evt, buf)
132
133 if w.FormatExtra != nil {
134 err = w.FormatExtra(evt, buf)
135 if err != nil {
136 return n, err
137 }
138 }
139
140 err = buf.WriteByte('\n')
141 if err != nil {
142 return n, err
143 }
144
145 _, err = buf.WriteTo(w.Out)
146 return len(p), err
147 }
148
149
150 func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer) {
151 var fields = make([]string, 0, len(evt))
152 for field := range evt {
153 var isExcluded bool
154 for _, excluded := range w.FieldsExclude {
155 if field == excluded {
156 isExcluded = true
157 break
158 }
159 }
160 if isExcluded {
161 continue
162 }
163
164 switch field {
165 case LevelFieldName, TimestampFieldName, MessageFieldName, CallerFieldName:
166 continue
167 }
168 fields = append(fields, field)
169 }
170 sort.Strings(fields)
171
172
173 if buf.Len() > 0 && len(fields) > 0 {
174 buf.WriteByte(' ')
175 }
176
177
178 ei := sort.Search(len(fields), func(i int) bool { return fields[i] >= ErrorFieldName })
179 if ei < len(fields) && fields[ei] == ErrorFieldName {
180 fields[ei] = ""
181 fields = append([]string{ErrorFieldName}, fields...)
182 var xfields = make([]string, 0, len(fields))
183 for _, field := range fields {
184 if field == "" {
185 continue
186 }
187 xfields = append(xfields, field)
188 }
189 fields = xfields
190 }
191
192 for i, field := range fields {
193 var fn Formatter
194 var fv Formatter
195
196 if field == ErrorFieldName {
197 if w.FormatErrFieldName == nil {
198 fn = consoleDefaultFormatErrFieldName(w.NoColor)
199 } else {
200 fn = w.FormatErrFieldName
201 }
202
203 if w.FormatErrFieldValue == nil {
204 fv = consoleDefaultFormatErrFieldValue(w.NoColor)
205 } else {
206 fv = w.FormatErrFieldValue
207 }
208 } else {
209 if w.FormatFieldName == nil {
210 fn = consoleDefaultFormatFieldName(w.NoColor)
211 } else {
212 fn = w.FormatFieldName
213 }
214
215 if w.FormatFieldValue == nil {
216 fv = consoleDefaultFormatFieldValue
217 } else {
218 fv = w.FormatFieldValue
219 }
220 }
221
222 buf.WriteString(fn(field))
223
224 switch fValue := evt[field].(type) {
225 case string:
226 if needsQuote(fValue) {
227 buf.WriteString(fv(strconv.Quote(fValue)))
228 } else {
229 buf.WriteString(fv(fValue))
230 }
231 case json.Number:
232 buf.WriteString(fv(fValue))
233 default:
234 b, err := InterfaceMarshalFunc(fValue)
235 if err != nil {
236 fmt.Fprintf(buf, colorize("[error: %v]", colorRed, w.NoColor), err)
237 } else {
238 fmt.Fprint(buf, fv(b))
239 }
240 }
241
242 if i < len(fields)-1 {
243 buf.WriteByte(' ')
244 }
245 }
246 }
247
248
249 func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{}, p string) {
250 var f Formatter
251
252 if w.PartsExclude != nil && len(w.PartsExclude) > 0 {
253 for _, exclude := range w.PartsExclude {
254 if exclude == p {
255 return
256 }
257 }
258 }
259
260 switch p {
261 case LevelFieldName:
262 if w.FormatLevel == nil {
263 f = consoleDefaultFormatLevel(w.NoColor)
264 } else {
265 f = w.FormatLevel
266 }
267 case TimestampFieldName:
268 if w.FormatTimestamp == nil {
269 f = consoleDefaultFormatTimestamp(w.TimeFormat, w.NoColor)
270 } else {
271 f = w.FormatTimestamp
272 }
273 case MessageFieldName:
274 if w.FormatMessage == nil {
275 f = consoleDefaultFormatMessage
276 } else {
277 f = w.FormatMessage
278 }
279 case CallerFieldName:
280 if w.FormatCaller == nil {
281 f = consoleDefaultFormatCaller(w.NoColor)
282 } else {
283 f = w.FormatCaller
284 }
285 default:
286 if w.FormatFieldValue == nil {
287 f = consoleDefaultFormatFieldValue
288 } else {
289 f = w.FormatFieldValue
290 }
291 }
292
293 var s = f(evt[p])
294
295 if len(s) > 0 {
296 if buf.Len() > 0 {
297 buf.WriteByte(' ')
298 }
299 buf.WriteString(s)
300 }
301 }
302
303
304 func needsQuote(s string) bool {
305 for i := range s {
306 if s[i] < 0x20 || s[i] > 0x7e || s[i] == ' ' || s[i] == '\\' || s[i] == '"' {
307 return true
308 }
309 }
310 return false
311 }
312
313
314 func colorize(s interface{}, c int, disabled bool) string {
315 if disabled {
316 return fmt.Sprintf("%s", s)
317 }
318 return fmt.Sprintf("\x1b[%dm%v\x1b[0m", c, s)
319 }
320
321
322
323 func consoleDefaultPartsOrder() []string {
324 return []string{
325 TimestampFieldName,
326 LevelFieldName,
327 CallerFieldName,
328 MessageFieldName,
329 }
330 }
331
332 func consoleDefaultFormatTimestamp(timeFormat string, noColor bool) Formatter {
333 if timeFormat == "" {
334 timeFormat = consoleDefaultTimeFormat
335 }
336 return func(i interface{}) string {
337 t := "<nil>"
338 switch tt := i.(type) {
339 case string:
340 ts, err := time.Parse(TimeFieldFormat, tt)
341 if err != nil {
342 t = tt
343 } else {
344 t = ts.Local().Format(timeFormat)
345 }
346 case json.Number:
347 i, err := tt.Int64()
348 if err != nil {
349 t = tt.String()
350 } else {
351 var sec, nsec int64 = i, 0
352 switch TimeFieldFormat {
353 case TimeFormatUnixMs:
354 nsec = int64(time.Duration(i) * time.Millisecond)
355 sec = 0
356 case TimeFormatUnixMicro:
357 nsec = int64(time.Duration(i) * time.Microsecond)
358 sec = 0
359 }
360 ts := time.Unix(sec, nsec)
361 t = ts.Format(timeFormat)
362 }
363 }
364 return colorize(t, colorDarkGray, noColor)
365 }
366 }
367
368 func consoleDefaultFormatLevel(noColor bool) Formatter {
369 return func(i interface{}) string {
370 var l string
371 if ll, ok := i.(string); ok {
372 switch ll {
373 case LevelTraceValue:
374 l = colorize("TRC", colorMagenta, noColor)
375 case LevelDebugValue:
376 l = colorize("DBG", colorYellow, noColor)
377 case LevelInfoValue:
378 l = colorize("INF", colorGreen, noColor)
379 case LevelWarnValue:
380 l = colorize("WRN", colorRed, noColor)
381 case LevelErrorValue:
382 l = colorize(colorize("ERR", colorRed, noColor), colorBold, noColor)
383 case LevelFatalValue:
384 l = colorize(colorize("FTL", colorRed, noColor), colorBold, noColor)
385 case LevelPanicValue:
386 l = colorize(colorize("PNC", colorRed, noColor), colorBold, noColor)
387 default:
388 l = colorize("???", colorBold, noColor)
389 }
390 } else {
391 if i == nil {
392 l = colorize("???", colorBold, noColor)
393 } else {
394 l = strings.ToUpper(fmt.Sprintf("%s", i))[0:3]
395 }
396 }
397 return l
398 }
399 }
400
401 func consoleDefaultFormatCaller(noColor bool) Formatter {
402 return func(i interface{}) string {
403 var c string
404 if cc, ok := i.(string); ok {
405 c = cc
406 }
407 if len(c) > 0 {
408 if cwd, err := os.Getwd(); err == nil {
409 if rel, err := filepath.Rel(cwd, c); err == nil {
410 c = rel
411 }
412 }
413 c = colorize(c, colorBold, noColor) + colorize(" >", colorCyan, noColor)
414 }
415 return c
416 }
417 }
418
419 func consoleDefaultFormatMessage(i interface{}) string {
420 if i == nil {
421 return ""
422 }
423 return fmt.Sprintf("%s", i)
424 }
425
426 func consoleDefaultFormatFieldName(noColor bool) Formatter {
427 return func(i interface{}) string {
428 return colorize(fmt.Sprintf("%s=", i), colorCyan, noColor)
429 }
430 }
431
432 func consoleDefaultFormatFieldValue(i interface{}) string {
433 return fmt.Sprintf("%s", i)
434 }
435
436 func consoleDefaultFormatErrFieldName(noColor bool) Formatter {
437 return func(i interface{}) string {
438 return colorize(fmt.Sprintf("%s=", i), colorCyan, noColor)
439 }
440 }
441
442 func consoleDefaultFormatErrFieldValue(noColor bool) Formatter {
443 return func(i interface{}) string {
444 return colorize(fmt.Sprintf("%s", i), colorRed, noColor)
445 }
446 }
447
View as plain text