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