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