1
0
mirror of https://github.com/rs/zerolog synced 2026-06-08 17:13:30 +00:00

Compare commits

..

15 Commits

Author SHA1 Message Date
Olivier Poitrey 9f1f25d2fa pkgerrors: quick refactoring for better readibility 2019-01-02 17:07:20 -08:00
Olivier Poitrey 6d2153805b Revert change on go.mod 2019-01-02 17:02:58 -08:00
Olivier Poitrey 2988c1e444 pkgerrors: remove fmt dep 2019-01-02 13:19:14 -08:00
Olivier Poitrey adb19ff852 Merge branch 'master' into stacktrace 2019-01-02 12:11:57 -08:00
Ingmar Stein c482b20623 ConsoleWriter: fix race condition (#120)
Alternative approach to #118: fix the race condition by replacing the package-level customization settings with curried functions.
2018-12-07 09:59:01 -08:00
Ingmar Stein 7179aeef58 ConsoleWriter: reset buffer before returning it to the pool (#119)
The buffers put back into the pool should be equivalent to those generated by the `New` function.
2018-12-05 07:46:12 +00:00
Olivier Poitrey 8747b7b3a5 Add ErrorHandler global to allow handling of write errors 2018-11-20 10:56:21 -08:00
Olivier Poitrey 848482bc3d Fix "could not write" error missing carriage return (again) 2018-11-12 17:50:30 -08:00
Yongzheng Lai 7bcaa1a99e fix: console ts with json number (#115) 2018-11-12 00:50:17 -08:00
Olivier Poitrey 8e30c71369 Revert the wrapping of write errors
All errors generated by Go libraries on stderr won't be json encoded
anyway, so it does not make sense to have such a treatment for this one.
2018-11-08 14:49:44 -08:00
Olivier Poitrey 3f112dae87 Fix "could not write" error missing carriage return 2018-11-07 15:21:38 -08:00
Karel Minarik a4c54e5d8b Fix the ConsoleWriter default parts order (#113)
In order to prevent incorrect output when somebody uses a different name eg. for the "MessageFieldName",
the `consoleDefaultPartsOrder` variable has been switched to a function, which is called in `Write()`,
in order to pick up the custom name.

Related: rs/zerolog#92
2018-11-07 09:39:38 -08:00
Karel Minarik 96f91bb4f5 Refactored zerolog.ConsoleWriter to allow customization (#92)
* Added a simple benchmarking test for the ConsoleWriter
* Refactored `zerolog.ConsoleWriter` to allow customization

Closes #84
2018-11-05 02:15:13 -08:00
Olivier Poitrey 51c79ca476 Fix com typo 2018-11-02 13:06:29 -07:00
Olivier Poitrey 3786fbfa73 Add support for stack trace extration of error fields 2018-02-11 17:55:34 -08:00
12 changed files with 773 additions and 125 deletions
+1
View File
@@ -6,6 +6,7 @@
# Folders
_obj
_test
tmp
# Architecture specific extensions/prefixes
*.[568vq]
+31 -6
View File
@@ -8,7 +8,7 @@ Zerolog's API is designed to provide both a great developer experience and stunn
Uber's [zap](https://godoc.org/go.uber.org/zap) library pioneered this approach. Zerolog is taking this concept to the next level with a simpler to use API and even better performance.
To keep the code base and the API simple, zerolog focuses on efficient structured logging only. Pretty logging on the console is made possible using the provided (but inefficient) `zerolog.ConsoleWriter`.
To keep the code base and the API simple, zerolog focuses on efficient structured logging only. Pretty logging on the console is made possible using the provided (but inefficient) [`zerolog.ConsoleWriter`](#pretty-logging).
![Pretty Logging Image](pretty.png)
@@ -60,7 +60,7 @@ func main() {
// Output: {"time":1516134303,"level":"debug","message":"hello world"}
```
> Note: By default log writes to `os.Stderr`
> Note: By default log writes to `os.Stderr`
> Note: The default log level for `log.Print` is *debug*
### Contextual Logging
@@ -252,14 +252,38 @@ sublogger.Info().Msg("hello world")
### Pretty logging
To log a human-friendly, colorized output, use `zerolog.ConsoleWriter`:
```go
if isConsole {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
}
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
log.Info().Str("foo", "bar").Msg("Hello world")
// Output: 1494567715 |INFO| Hello world foo=bar
// Output: 3:04PM INF Hello World foo=bar
```
To customize the configuration and formatting:
```go
output := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339}
output.FormatLevel = func(i interface{}) string {
return strings.ToUpper(fmt.Sprintf("| %-6s|", i))
}
output.FormatMessage = func(i interface{}) string {
return fmt.Sprintf("***%s****", i)
}
output.FormatFieldName = func(i interface{}) string {
return fmt.Sprintf("%s:", i)
}
output.FormatFieldValue = func(i interface{}) string {
return strings.ToUpper(fmt.Sprintf("%s", i))
}
log := zerolog.New(output).With().Timestamp().Logger()
log.Info().Str("foo", "bar").Msg("Hello World")
// Output: 2006-01-02T15:04:05Z07:00 | INFO | ***Hello World**** foo:BAR
```
### Sub dictionary
@@ -455,6 +479,7 @@ Some settings can be changed and will by applied to all loggers:
// using the Dur method.
* `DurationFieldUnit`: Sets the unit of the fields added by `Dur` (default: `time.Millisecond`).
* `DurationFieldInteger`: If set to true, `Dur` fields are formatted as integers instead of floats.
* `ErrorHandler`: Called whenever zerolog fails to write an event on its output. If not set, an error is printed on the stderr. This handler must be thread safe and non-blocking.
## Field Types
+319 -94
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"io"
"os"
"sort"
"strconv"
"strings"
@@ -13,133 +14,246 @@ import (
)
const (
cReset = 0
cBold = 1
cRed = 31
cGreen = 32
cYellow = 33
cBlue = 34
cMagenta = 35
cCyan = 36
cGray = 37
cDarkGray = 90
colorBold = iota + 1
colorFaint
)
var consoleBufPool = sync.Pool{
New: func() interface{} {
return bytes.NewBuffer(make([]byte, 0, 100))
},
}
const (
colorBlack = iota + 30
colorRed
colorGreen
colorYellow
colorBlue
colorMagenta
colorCyan
colorWhite
)
// LevelWidth defines the desired character width of the log level column.
// Default 0 does not trim or pad (variable width based level text, e.g. "INFO" or "ERROR")
var LevelWidth = 0
var (
consoleBufPool = sync.Pool{
New: func() interface{} {
return bytes.NewBuffer(make([]byte, 0, 100))
},
}
)
// ConsoleWriter reads a JSON object per write operation and output an
// optionally colored human readable version on the Out writer.
const (
consoleDefaultTimeFormat = time.Kitchen
)
// Formatter transforms the input into a formatted string.
type Formatter func(interface{}) string
// ConsoleWriter parses the JSON input and writes it in an
// (optionally) colorized, human-friendly format to Out.
type ConsoleWriter struct {
Out io.Writer
// Out is the output destination.
Out io.Writer
// NoColor disables the colorized output.
NoColor bool
// TimeFormat specifies the format for timestamp in output.
TimeFormat string
// PartsOrder defines the order of parts in output.
PartsOrder []string
FormatTimestamp Formatter
FormatLevel Formatter
FormatCaller Formatter
FormatMessage Formatter
FormatFieldName Formatter
FormatFieldValue Formatter
FormatErrFieldName Formatter
FormatErrFieldValue Formatter
}
// NewConsoleWriter creates and initializes a new ConsoleWriter.
func NewConsoleWriter(options ...func(w *ConsoleWriter)) ConsoleWriter {
w := ConsoleWriter{
Out: os.Stdout,
TimeFormat: consoleDefaultTimeFormat,
PartsOrder: consoleDefaultPartsOrder(),
}
for _, opt := range options {
opt(&w)
}
return w
}
// Write transforms the JSON input with formatters and appends to w.Out.
func (w ConsoleWriter) Write(p []byte) (n int, err error) {
var event map[string]interface{}
if w.PartsOrder == nil {
w.PartsOrder = consoleDefaultPartsOrder()
}
var buf = consoleBufPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
consoleBufPool.Put(buf)
}()
var evt map[string]interface{}
p = decodeIfBinaryToBytes(p)
d := json.NewDecoder(bytes.NewReader(p))
d.UseNumber()
err = d.Decode(&event)
err = d.Decode(&evt)
if err != nil {
return
return n, fmt.Errorf("cannot decode event: %s", err)
}
buf := consoleBufPool.Get().(*bytes.Buffer)
defer consoleBufPool.Put(buf)
lvlColor := cReset
level := "????"
if l, ok := event[LevelFieldName].(string); ok {
if !w.NoColor {
lvlColor = levelColor(l)
}
level = strings.ToUpper(l)
if LevelWidth > 0 {
if padding := LevelWidth - len(level); padding > 0 {
level += strings.Repeat(" ", padding)
} else {
level = level[0:LevelWidth]
}
}
for _, p := range w.PartsOrder {
w.writePart(buf, evt, p)
}
fmt.Fprintf(buf, "%s |%s| %s",
colorize(formatTime(event[TimestampFieldName]), cDarkGray, !w.NoColor),
colorize(level, lvlColor, !w.NoColor),
colorize(event[MessageFieldName], cReset, !w.NoColor))
fields := make([]string, 0, len(event))
for field := range event {
w.writeFields(evt, buf)
err = buf.WriteByte('\n')
if err != nil {
return n, err
}
_, err = buf.WriteTo(w.Out)
return len(p), err
}
// writeFields appends formatted key-value pairs to buf.
func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer) {
var fields = make([]string, 0, len(evt))
for field := range evt {
switch field {
case LevelFieldName, TimestampFieldName, MessageFieldName:
case LevelFieldName, TimestampFieldName, MessageFieldName, CallerFieldName:
continue
}
fields = append(fields, field)
}
sort.Strings(fields)
for _, field := range fields {
fmt.Fprintf(buf, " %s=", colorize(field, cCyan, !w.NoColor))
switch value := event[field].(type) {
case string:
if needsQuote(value) {
buf.WriteString(strconv.Quote(value))
} else {
buf.WriteString(value)
if len(fields) > 0 {
buf.WriteByte(' ')
}
// Move the "error" field to the front
ei := sort.Search(len(fields), func(i int) bool { return fields[i] >= ErrorFieldName })
if ei < len(fields) && fields[ei] == ErrorFieldName {
fields[ei] = ""
fields = append([]string{ErrorFieldName}, fields...)
var xfields = make([]string, 0, len(fields))
for _, field := range fields {
if field == "" { // Skip empty fields
continue
}
case json.Number:
fmt.Fprint(buf, value)
default:
b, err := json.Marshal(value)
if err != nil {
fmt.Fprintf(buf, "[error: %v]", err)
xfields = append(xfields, field)
}
fields = xfields
}
for i, field := range fields {
var fn Formatter
var fv Formatter
if field == ErrorFieldName {
if w.FormatErrFieldName == nil {
fn = consoleDefaultFormatErrFieldName(w.NoColor)
} else {
fmt.Fprint(buf, string(b))
fn = w.FormatErrFieldName
}
if w.FormatErrFieldValue == nil {
fv = consoleDefaultFormatErrFieldValue(w.NoColor)
} else {
fv = w.FormatErrFieldValue
}
} else {
if w.FormatFieldName == nil {
fn = consoleDefaultFormatFieldName(w.NoColor)
} else {
fn = w.FormatFieldName
}
if w.FormatFieldValue == nil {
fv = consoleDefaultFormatFieldValue
} else {
fv = w.FormatFieldValue
}
}
buf.WriteString(fn(field))
switch fValue := evt[field].(type) {
case string:
if needsQuote(fValue) {
buf.WriteString(fv(strconv.Quote(fValue)))
} else {
buf.WriteString(fv(fValue))
}
case json.Number:
buf.WriteString(fv(fValue))
default:
b, err := json.Marshal(fValue)
if err != nil {
fmt.Fprintf(buf, colorize("[error: %v]", colorRed, w.NoColor), err)
} else {
fmt.Fprint(buf, fv(b))
}
}
if i < len(fields)-1 { // Skip space for last field
buf.WriteByte(' ')
}
}
buf.WriteByte('\n')
buf.WriteTo(w.Out)
n = len(p)
return
}
func formatTime(t interface{}) string {
switch t := t.(type) {
case string:
return t
case json.Number:
u, _ := t.Int64()
return time.Unix(u, 0).Format(time.RFC3339)
}
return "<nil>"
}
// writePart appends a formatted part to buf.
func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{}, p string) {
var f Formatter
func colorize(s interface{}, color int, enabled bool) string {
if !enabled {
return fmt.Sprintf("%v", s)
}
return fmt.Sprintf("\x1b[%dm%v\x1b[0m", color, s)
}
func levelColor(level string) int {
switch level {
case "debug":
return cMagenta
case "info":
return cGreen
case "warn":
return cYellow
case "error", "fatal", "panic":
return cRed
switch p {
case LevelFieldName:
if w.FormatLevel == nil {
f = consoleDefaultFormatLevel(w.NoColor)
} else {
f = w.FormatLevel
}
case TimestampFieldName:
if w.FormatTimestamp == nil {
f = consoleDefaultFormatTimestamp(w.TimeFormat, w.NoColor)
} else {
f = w.FormatTimestamp
}
case MessageFieldName:
if w.FormatMessage == nil {
f = consoleDefaultFormatMessage
} else {
f = w.FormatMessage
}
case CallerFieldName:
if w.FormatCaller == nil {
f = consoleDefaultFormatCaller(w.NoColor)
} else {
f = w.FormatCaller
}
default:
return cReset
if w.FormatFieldValue == nil {
f = consoleDefaultFormatFieldValue
} else {
f = w.FormatFieldValue
}
}
var s = f(evt[p])
if len(s) > 0 {
buf.WriteString(s)
if p != w.PartsOrder[len(w.PartsOrder)-1] { // Skip space for last part
buf.WriteByte(' ')
}
}
}
// needsQuote returns true when the string s should be quoted in output.
func needsQuote(s string) bool {
for i := range s {
if s[i] < 0x20 || s[i] > 0x7e || s[i] == ' ' || s[i] == '\\' || s[i] == '"' {
@@ -148,3 +262,114 @@ func needsQuote(s string) bool {
}
return false
}
// colorize returns the string s wrapped in ANSI code c, unless disabled is true.
func colorize(s interface{}, c int, disabled bool) string {
if disabled {
return fmt.Sprintf("%s", s)
}
return fmt.Sprintf("\x1b[%dm%v\x1b[0m", c, s)
}
// ----- DEFAULT FORMATTERS ---------------------------------------------------
func consoleDefaultPartsOrder() []string {
return []string{
TimestampFieldName,
LevelFieldName,
CallerFieldName,
MessageFieldName,
}
}
func consoleDefaultFormatTimestamp(timeFormat string, noColor bool) Formatter {
if timeFormat == "" {
timeFormat = consoleDefaultTimeFormat
}
return func(i interface{}) string {
t := "<nil>"
switch tt := i.(type) {
case string:
ts, err := time.Parse(TimeFieldFormat, tt)
if err != nil {
t = tt
} else {
t = ts.Format(timeFormat)
}
case json.Number:
t = tt.String()
}
return colorize(t, colorFaint, noColor)
}
}
func consoleDefaultFormatLevel(noColor bool) Formatter {
return func(i interface{}) string {
var l string
if ll, ok := i.(string); ok {
switch ll {
case "debug":
l = colorize("DBG", colorYellow, noColor)
case "info":
l = colorize("INF", colorGreen, noColor)
case "warn":
l = colorize("WRN", colorRed, noColor)
case "error":
l = colorize(colorize("ERR", colorRed, noColor), colorBold, noColor)
case "fatal":
l = colorize(colorize("FTL", colorRed, noColor), colorBold, noColor)
case "panic":
l = colorize(colorize("PNC", colorRed, noColor), colorBold, noColor)
default:
l = colorize("???", colorBold, noColor)
}
} else {
l = strings.ToUpper(fmt.Sprintf("%s", i))[0:3]
}
return l
}
}
func consoleDefaultFormatCaller(noColor bool) Formatter {
return func(i interface{}) string {
var c string
if cc, ok := i.(string); ok {
c = cc
}
if len(c) > 0 {
cwd, err := os.Getwd()
if err == nil {
c = strings.TrimPrefix(c, cwd)
c = strings.TrimPrefix(c, "/")
}
c = colorize(c, colorBold, noColor) + colorize(" >", colorFaint, noColor)
}
return c
}
}
func consoleDefaultFormatMessage(i interface{}) string {
return fmt.Sprintf("%s", i)
}
func consoleDefaultFormatFieldName(noColor bool) Formatter {
return func(i interface{}) string {
return colorize(fmt.Sprintf("%s=", i), colorFaint, noColor)
}
}
func consoleDefaultFormatFieldValue(i interface{}) string {
return fmt.Sprintf("%s", i)
}
func consoleDefaultFormatErrFieldName(noColor bool) Formatter {
return func(i interface{}) string {
return colorize(fmt.Sprintf("%s=", i), colorRed, noColor)
}
}
func consoleDefaultFormatErrFieldValue(noColor bool) Formatter {
return func(i interface{}) string {
return colorize(fmt.Sprintf("%s", i), colorRed, noColor)
}
}
+221 -13
View File
@@ -2,29 +2,237 @@ package zerolog_test
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
"time"
"github.com/rs/zerolog"
)
func ExampleConsoleWriter_Write() {
func ExampleConsoleWriter() {
log := zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, NoColor: true})
log.Info().Msg("hello world")
// Output: <nil> |INFO| hello world
log.Info().Str("foo", "bar").Msg("Hello World")
// Output: <nil> INF Hello World foo=bar
}
func TestConsoleWriterNumbers(t *testing.T) {
buf := &bytes.Buffer{}
log := zerolog.New(zerolog.ConsoleWriter{Out: buf, NoColor: true})
log.Info().
Float64("float", 1.23).
Uint64("small", 123).
Uint64("big", 1152921504606846976).
Msg("msg")
if got, want := strings.TrimSpace(buf.String()), "<nil> |INFO| msg big=1152921504606846976 float=1.23 small=123"; got != want {
t.Errorf("\ngot:\n%s\nwant:\n%s", got, want)
func ExampleConsoleWriter_customFormatters() {
out := zerolog.ConsoleWriter{Out: os.Stdout, NoColor: true}
out.FormatLevel = func(i interface{}) string { return strings.ToUpper(fmt.Sprintf("%-6s|", i)) }
out.FormatFieldName = func(i interface{}) string { return fmt.Sprintf("%s:", i) }
out.FormatFieldValue = func(i interface{}) string { return strings.ToUpper(fmt.Sprintf("%s", i)) }
log := zerolog.New(out)
log.Info().Str("foo", "bar").Msg("Hello World")
// Output: <nil> INFO | Hello World foo:BAR
}
func ExampleNewConsoleWriter() {
out := zerolog.NewConsoleWriter()
out.NoColor = true // For testing purposes only
log := zerolog.New(out)
log.Debug().Str("foo", "bar").Msg("Hello World")
// Output: <nil> DBG Hello World foo=bar
}
func ExampleNewConsoleWriter_customFormatters() {
out := zerolog.NewConsoleWriter(
func(w *zerolog.ConsoleWriter) {
// Customize time format
w.TimeFormat = time.RFC822
// Customize level formatting
w.FormatLevel = func(i interface{}) string { return strings.ToUpper(fmt.Sprintf("[%-5s]", i)) }
},
)
out.NoColor = true // For testing purposes only
log := zerolog.New(out)
log.Info().Str("foo", "bar").Msg("Hello World")
// Output: <nil> [INFO ] Hello World foo=bar
}
func TestConsoleLogger(t *testing.T) {
t.Run("Numbers", func(t *testing.T) {
buf := &bytes.Buffer{}
log := zerolog.New(zerolog.ConsoleWriter{Out: buf, NoColor: true})
log.Info().
Float64("float", 1.23).
Uint64("small", 123).
Uint64("big", 1152921504606846976).
Msg("msg")
if got, want := strings.TrimSpace(buf.String()), "<nil> INF msg big=1152921504606846976 float=1.23 small=123"; got != want {
t.Errorf("\ngot:\n%s\nwant:\n%s", got, want)
}
})
}
func TestConsoleWriter(t *testing.T) {
t.Run("Default field formatter", func(t *testing.T) {
buf := &bytes.Buffer{}
w := zerolog.ConsoleWriter{Out: buf, NoColor: true, PartsOrder: []string{"foo"}}
_, err := w.Write([]byte(`{"foo" : "DEFAULT"}`))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "DEFAULT foo=DEFAULT\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
t.Run("Write colorized", func(t *testing.T) {
buf := &bytes.Buffer{}
w := zerolog.ConsoleWriter{Out: buf, NoColor: false}
_, err := w.Write([]byte(`{"level" : "warn", "message" : "Foobar"}`))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "\x1b[2m<nil>\x1b[0m \x1b[31mWRN\x1b[0m Foobar\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
t.Run("Write fields", func(t *testing.T) {
buf := &bytes.Buffer{}
w := zerolog.ConsoleWriter{Out: buf, NoColor: true}
d := time.Unix(0, 0).UTC().Format(time.RFC3339)
_, err := w.Write([]byte(`{"time" : "` + d + `", "level" : "debug", "message" : "Foobar", "foo" : "bar"}`))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "12:00AM DBG Foobar foo=bar\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
t.Run("Write error field", func(t *testing.T) {
buf := &bytes.Buffer{}
w := zerolog.ConsoleWriter{Out: buf, NoColor: true}
d := time.Unix(0, 0).UTC().Format(time.RFC3339)
evt := `{"time" : "` + d + `", "level" : "error", "message" : "Foobar", "aaa" : "bbb", "error" : "Error"}`
// t.Log(evt)
_, err := w.Write([]byte(evt))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "12:00AM ERR Foobar error=Error aaa=bbb\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
t.Run("Write caller field", func(t *testing.T) {
buf := &bytes.Buffer{}
w := zerolog.ConsoleWriter{Out: buf, NoColor: true}
cwd, err := os.Getwd()
if err != nil {
t.Fatalf("Cannot get working directory: %s", err)
}
d := time.Unix(0, 0).UTC().Format(time.RFC3339)
evt := `{"time" : "` + d + `", "level" : "debug", "message" : "Foobar", "foo" : "bar", "caller" : "` + cwd + `/foo/bar.go"}`
// t.Log(evt)
_, err = w.Write([]byte(evt))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "12:00AM DBG foo/bar.go > Foobar foo=bar\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
t.Run("Write JSON field", func(t *testing.T) {
buf := &bytes.Buffer{}
w := zerolog.ConsoleWriter{Out: buf, NoColor: true}
evt := `{"level" : "debug", "message" : "Foobar", "foo" : [1, 2, 3], "bar" : true}`
// t.Log(evt)
_, err := w.Write([]byte(evt))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "<nil> DBG Foobar bar=true foo=[1,2,3]\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
}
func TestConsoleWriterConfiguration(t *testing.T) {
t.Run("Sets TimeFormat", func(t *testing.T) {
buf := &bytes.Buffer{}
w := zerolog.ConsoleWriter{Out: buf, NoColor: true, TimeFormat: time.RFC3339}
d := time.Unix(0, 0).UTC().Format(time.RFC3339)
evt := `{"time" : "` + d + `", "level" : "info", "message" : "Foobar"}`
_, err := w.Write([]byte(evt))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "1970-01-01T00:00:00Z INF Foobar\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
t.Run("Sets PartsOrder", func(t *testing.T) {
buf := &bytes.Buffer{}
w := zerolog.ConsoleWriter{Out: buf, NoColor: true, PartsOrder: []string{"message", "level"}}
evt := `{"level" : "info", "message" : "Foobar"}`
_, err := w.Write([]byte(evt))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "Foobar INF\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
}
})
}
func BenchmarkConsoleWriter(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
var msg = []byte(`{"level" : "info", "foo" : "bar", "message" : "HELLO", "time" : "1990-01-01"}`)
w := zerolog.ConsoleWriter{Out: ioutil.Discard, NoColor: false}
for i := 0; i < b.N; i++ {
w.Write(msg)
}
}
+14
View File
@@ -362,6 +362,20 @@ func (c Context) Caller() Context {
return c
}
type stackTraceHook struct{}
func (sh stackTraceHook) Run(e *Event, level Level, msg string) {
e.Stack()
}
var sh = stackTraceHook{}
// Stack enables stack trace printing for the error passed to Err().
func (c Context) Stack() Context {
c.l = c.l.Hook(sh)
return c
}
// IPAddr adds IPv4 or IPv6 Address to the context
func (c Context) IPAddr(key string, ip net.IP) Context {
c.l.context = enc.AppendIPAddr(enc.AppendKey(c.l.context, key), ip)
+1 -1
View File
@@ -44,7 +44,7 @@ type Writer struct {
// })
// log := zerolog.New(wr)
//
// If poolInterval is greater than 0, a poller is used otherwise a waiter is
// If pollInterval is greater than 0, a poller is used otherwise a waiter is
// used.
//
// See code.cloudfoundry.org/go-diodes for more info on diode.
+43 -11
View File
@@ -18,11 +18,6 @@ var eventPool = &sync.Pool{
},
}
// ErrorMarshalFunc allows customization of global error marshaling
var ErrorMarshalFunc = func(err error) interface{} {
return err
}
// Event represents a log event. It is instanced by one of the level method of
// Logger and finalized by the Msg or Msgf method.
type Event struct {
@@ -30,6 +25,7 @@ type Event struct {
w LevelWriter
level Level
done func(msg string)
stack bool // enable error stack trace
ch []Hook // hooks from context
}
@@ -137,7 +133,11 @@ func (e *Event) msg(msg string) {
defer e.done(msg)
}
if err := e.write(); err != nil {
fmt.Fprintf(os.Stderr, "zerolog: could not write event: %v", err)
if ErrorHandler != nil {
ErrorHandler(err)
} else {
fmt.Fprintf(os.Stderr, "zerolog: could not write event: %v\n", err)
}
}
}
@@ -267,8 +267,10 @@ func (e *Event) RawJSON(key string, b []byte) *Event {
// AnErr adds the field key with serialized err to the *Event context.
// If err is nil, no field is added.
func (e *Event) AnErr(key string, err error) *Event {
marshaled := ErrorMarshalFunc(err)
switch m := marshaled.(type) {
if e == nil {
return e
}
switch m := ErrorMarshalFunc(err).(type) {
case nil:
return e
case LogObjectMarshaler:
@@ -288,11 +290,9 @@ func (e *Event) Errs(key string, errs []error) *Event {
if e == nil {
return e
}
arr := Arr()
for _, err := range errs {
marshaled := ErrorMarshalFunc(err)
switch m := marshaled.(type) {
switch m := ErrorMarshalFunc(err).(type) {
case LogObjectMarshaler:
arr = arr.Object(m)
case error:
@@ -310,10 +310,42 @@ func (e *Event) Errs(key string, errs []error) *Event {
// Err adds the field "error" with serialized err to the *Event context.
// If err is nil, no field is added.
// To customize the key name, change zerolog.ErrorFieldName.
//
// To customize the key name, change zerolog.ErrorFieldName.
//
// If Stack() has been called before and zerolog.ErrorStackMarshaler is defined,
// the err is passed to ErrorStackMarshaler and the result is appended to the
// zerolog.ErrorStackFieldName.
func (e *Event) Err(err error) *Event {
if e == nil {
return e
}
if e.stack && ErrorStackMarshaler != nil {
switch m := ErrorStackMarshaler(err).(type) {
case nil:
case LogObjectMarshaler:
e.Object(ErrorStackFieldName, m)
case error:
e.Str(ErrorStackFieldName, m.Error())
case string:
e.Str(ErrorStackFieldName, m)
default:
e.Interface(ErrorStackFieldName, m)
}
}
return e.AnErr(ErrorFieldName, err)
}
// Stack enables stack trace printing for the error passed to Err().
//
// ErrorStackMarshaler must be set for this method to do something.
func (e *Event) Stack() *Event {
if e != nil {
e.stack = true
}
return e
}
// Bool adds the field key with val as a bool to the *Event context.
func (e *Event) Bool(key string, b bool) *Event {
if e == nil {
+16
View File
@@ -22,6 +22,17 @@ var (
// CallerSkipFrameCount is the number of stack frames to skip to find the caller.
CallerSkipFrameCount = 2
// ErrorStackFieldName is the field name used for error stacks.
ErrorStackFieldName = "stack"
// ErrorStackMarshaler extract the stack from err if any.
ErrorStackMarshaler func(err error) interface{}
// ErrorMarshalFunc allows customization of global error marshaling
ErrorMarshalFunc = func(err error) interface{} {
return err
}
// TimeFieldFormat defines the time format of the Time field type.
// If set to an empty string, the time is formatted as an UNIX timestamp
// as integer.
@@ -37,6 +48,11 @@ var (
// DurationFieldInteger renders Dur fields as integer instead of float if
// set to true.
DurationFieldInteger = false
// ErrorHandler is called whenever zerolog fails to write an event on its
// output. If not set, an error is printed on the stderr. This handler must
// be thread safe and non-blocking.
ErrorHandler func(err error)
)
var (
+21
View File
@@ -640,3 +640,24 @@ func TestErrorMarshalFunc(t *testing.T) {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
type errWriter struct {
error
}
func (w errWriter) Write(p []byte) (n int, err error) {
return 0, w.error
}
func TestErrorHandler(t *testing.T) {
var got error
want := errors.New("write error")
ErrorHandler = func(err error) {
got = err
}
log := New(errWriter{want})
log.Log().Msg("test")
if got != want {
t.Errorf("ErrorHandler err = %#v, want %#v", got, want)
}
}
+65
View File
@@ -0,0 +1,65 @@
package pkgerrors
import (
"github.com/pkg/errors"
)
var (
StackSourceFileName = "source"
StackSourceLineName = "line"
StackSourceFunctionName = "func"
)
type state struct {
b []byte
}
// Write implement fmt.Formatter interface.
func (s *state) Write(b []byte) (n int, err error) {
s.b = b
return len(b), nil
}
// Width implement fmt.Formatter interface.
func (s *state) Width() (wid int, ok bool) {
return 0, false
}
// Precision implement fmt.Formatter interface.
func (s *state) Precision() (prec int, ok bool) {
return 0, false
}
// Flag implement fmt.Formatter interface.
func (s *state) Flag(c int) bool {
return false
}
func frameField(f errors.Frame, s *state, c rune) string {
f.Format(s, c)
return string(s.b)
}
// MarshalStack implements pkg/errors stack trace marshaling.
//
// zerolog.ErrorStackMarshaler = MarshalStack
func MarshalStack(err error) interface{} {
type stackTracer interface {
StackTrace() errors.StackTrace
}
sterr, ok := err.(stackTracer)
if !ok {
return nil
}
st := sterr.StackTrace()
s := &state{}
out := make([]map[string]string, 0, len(st))
for _, frame := range st {
out = append(out, map[string]string{
StackSourceFileName: frameField(frame, s, 's'),
StackSourceLineName: frameField(frame, s, 'd'),
StackSourceFunctionName: frameField(frame, s, 'n'),
})
}
return out
}
+41
View File
@@ -0,0 +1,41 @@
// +build !binary_log
package pkgerrors
import (
"bytes"
"regexp"
"testing"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
func TestLogStack(t *testing.T) {
zerolog.ErrorStackMarshaler = MarshalStack
out := &bytes.Buffer{}
log := zerolog.New(out)
err := errors.Wrap(errors.New("error message"), "from error")
log.Log().Stack().Err(err).Msg("")
got := out.String()
want := `\{"stack":\[\{"func":"TestLogStack","line":"20","source":"stacktrace_test.go"\},.*\],"error":"from error: error message"\}\n`
if ok, _ := regexp.MatchString(want, got); !ok {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
func BenchmarkLogStack(b *testing.B) {
zerolog.ErrorStackMarshaler = MarshalStack
out := &bytes.Buffer{}
log := zerolog.New(out)
err := errors.Wrap(errors.New("error message"), "from error")
b.ReportAllocs()
for i := 0; i < b.N; i++ {
log.Log().Stack().Err(err).Msg("")
out.Reset()
}
}
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 377 KiB

After

Width:  |  Height:  |  Size: 141 KiB