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