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

Compare commits

...

9 Commits

Author SHA1 Message Date
Olivier Poitrey 6d6350a511 Fix go1.12 test regression
Breakage is due to this change in go 1.12:

Tracebacks, runtime.Caller, and runtime.Callers no longer include compiler-generated initialization functions. Doing a traceback during the initialization of a global variable will now show a function named PKG.init.ializers.

Fix #137
2019-02-27 12:02:51 -08:00
mikeyrcamp 299ff038c1 Add support for customizing caller field format (#133) (#134) 2019-02-20 11:39:29 -08:00
Kevin McConnell 4daee2b758 Don't use faint text in colorized console output (#131)
The faint text style doesn't seem to be supported by all terminals,
which means the default console output loses most of its coloring on
them.

This commit changes the color scheme to more widely-supported colors.
This also matches how it looked in earlier versions of this library.
2019-02-07 07:45:02 -08:00
Olivier Poitrey aa55558e4c Add support for stack trace extration of error fields (#35) 2019-01-03 11:04:23 -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
12 changed files with 354 additions and 85 deletions
+2
View File
@@ -4,6 +4,8 @@ go:
- "1.8"
- "1.9"
- "1.10"
- "1.11"
- "1.12"
- "master"
matrix:
allow_failures:
+1
View File
@@ -479,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
+75 -68
View File
@@ -13,11 +13,6 @@ import (
"time"
)
const (
colorBold = iota + 1
colorFaint
)
const (
colorBlack = iota + 30
colorRed
@@ -27,6 +22,9 @@ const (
colorMagenta
colorCyan
colorWhite
colorBold = 1
colorDarkGray = 90
)
var (
@@ -35,20 +33,10 @@ var (
return bytes.NewBuffer(make([]byte, 0, 100))
},
}
)
const (
consoleDefaultTimeFormat = time.Kitchen
consoleDefaultFormatter = func(i interface{}) string { return fmt.Sprintf("%s", i) }
consoleDefaultPartsOrder = func() []string {
return []string{
TimestampFieldName,
LevelFieldName,
CallerFieldName,
MessageFieldName,
}
}
consoleNoColor = false
consoleTimeFormat = consoleDefaultTimeFormat
)
// Formatter transforms the input into a formatted string.
@@ -99,21 +87,12 @@ func (w ConsoleWriter) Write(p []byte) (n int, err error) {
if w.PartsOrder == nil {
w.PartsOrder = consoleDefaultPartsOrder()
}
if w.TimeFormat == "" && consoleTimeFormat != consoleDefaultTimeFormat {
consoleTimeFormat = consoleDefaultTimeFormat
}
if w.TimeFormat != "" && consoleTimeFormat != w.TimeFormat {
consoleTimeFormat = w.TimeFormat
}
if w.NoColor == false && consoleNoColor != false {
consoleNoColor = false
}
if w.NoColor == true && consoleNoColor != w.NoColor {
consoleNoColor = w.NoColor
}
var buf = consoleBufPool.Get().(*bytes.Buffer)
defer consoleBufPool.Put(buf)
defer func() {
buf.Reset()
consoleBufPool.Put(buf)
}()
var evt map[string]interface{}
p = decodeIfBinaryToBytes(p)
@@ -130,9 +109,12 @@ func (w ConsoleWriter) Write(p []byte) (n int, err error) {
w.writeFields(evt, buf)
buf.WriteByte('\n')
buf.WriteTo(w.Out)
return len(p), nil
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.
@@ -172,19 +154,19 @@ func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer
if field == ErrorFieldName {
if w.FormatErrFieldName == nil {
fn = consoleDefaultFormatErrFieldName
fn = consoleDefaultFormatErrFieldName(w.NoColor)
} else {
fn = w.FormatErrFieldName
}
if w.FormatErrFieldValue == nil {
fv = consoleDefaultFormatErrFieldValue
fv = consoleDefaultFormatErrFieldValue(w.NoColor)
} else {
fv = w.FormatErrFieldValue
}
} else {
if w.FormatFieldName == nil {
fn = consoleDefaultFormatFieldName
fn = consoleDefaultFormatFieldName(w.NoColor)
} else {
fn = w.FormatFieldName
}
@@ -229,13 +211,13 @@ func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{},
switch p {
case LevelFieldName:
if w.FormatLevel == nil {
f = consoleDefaultFormatLevel
f = consoleDefaultFormatLevel(w.NoColor)
} else {
f = w.FormatLevel
}
case TimestampFieldName:
if w.FormatTimestamp == nil {
f = consoleDefaultFormatTimestamp
f = consoleDefaultFormatTimestamp(w.TimeFormat, w.NoColor)
} else {
f = w.FormatTimestamp
}
@@ -247,7 +229,7 @@ func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{},
}
case CallerFieldName:
if w.FormatCaller == nil {
f = consoleDefaultFormatCaller
f = consoleDefaultFormatCaller(w.NoColor)
} else {
f = w.FormatCaller
}
@@ -289,46 +271,65 @@ func colorize(s interface{}, c int, disabled bool) string {
// ----- DEFAULT FORMATTERS ---------------------------------------------------
var (
consoleDefaultFormatTimestamp = func(i interface{}) string {
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>"
if tt, ok := i.(string); ok {
ts, err := time.Parse(time.RFC3339, tt)
switch tt := i.(type) {
case string:
ts, err := time.Parse(TimeFieldFormat, tt)
if err != nil {
t = tt
} else {
t = ts.Format(consoleTimeFormat)
t = ts.Format(timeFormat)
}
case json.Number:
t = tt.String()
}
return colorize(t, colorFaint, consoleNoColor)
return colorize(t, colorDarkGray, noColor)
}
}
consoleDefaultFormatLevel = func(i interface{}) string {
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, consoleNoColor)
l = colorize("DBG", colorYellow, noColor)
case "info":
l = colorize("INF", colorGreen, consoleNoColor)
l = colorize("INF", colorGreen, noColor)
case "warn":
l = colorize("WRN", colorRed, consoleNoColor)
l = colorize("WRN", colorRed, noColor)
case "error":
l = colorize(colorize("ERR", colorRed, consoleNoColor), colorBold, consoleNoColor)
l = colorize(colorize("ERR", colorRed, noColor), colorBold, noColor)
case "fatal":
l = colorize(colorize("FTL", colorRed, consoleNoColor), colorBold, consoleNoColor)
l = colorize(colorize("FTL", colorRed, noColor), colorBold, noColor)
case "panic":
l = colorize(colorize("PNC", colorRed, consoleNoColor), colorBold, consoleNoColor)
l = colorize(colorize("PNC", colorRed, noColor), colorBold, noColor)
default:
l = colorize("???", colorBold, consoleNoColor)
l = colorize("???", colorBold, noColor)
}
} else {
l = strings.ToUpper(fmt.Sprintf("%s", i))[0:3]
}
return l
}
}
consoleDefaultFormatCaller = func(i interface{}) string {
func consoleDefaultFormatCaller(noColor bool) Formatter {
return func(i interface{}) string {
var c string
if cc, ok := i.(string); ok {
c = cc
@@ -339,28 +340,34 @@ var (
c = strings.TrimPrefix(c, cwd)
c = strings.TrimPrefix(c, "/")
}
c = colorize(c, colorBold, consoleNoColor) + colorize(" >", colorFaint, consoleNoColor)
c = colorize(c, colorBold, noColor) + colorize(" >", colorCyan, noColor)
}
return c
}
}
consoleDefaultFormatMessage = func(i interface{}) string {
return fmt.Sprintf("%s", i)
}
func consoleDefaultFormatMessage(i interface{}) string {
return fmt.Sprintf("%s", i)
}
consoleDefaultFormatFieldName = func(i interface{}) string {
return colorize(fmt.Sprintf("%s=", i), colorFaint, consoleNoColor)
func consoleDefaultFormatFieldName(noColor bool) Formatter {
return func(i interface{}) string {
return colorize(fmt.Sprintf("%s=", i), colorCyan, noColor)
}
}
consoleDefaultFormatFieldValue = func(i interface{}) string {
return fmt.Sprintf("%s", i)
}
func consoleDefaultFormatFieldValue(i interface{}) string {
return fmt.Sprintf("%s", i)
}
consoleDefaultFormatErrFieldName = func(i interface{}) string {
return colorize(fmt.Sprintf("%s=", i), colorRed, consoleNoColor)
func consoleDefaultFormatErrFieldName(noColor bool) Formatter {
return func(i interface{}) string {
return colorize(fmt.Sprintf("%s=", i), colorRed, noColor)
}
}
consoleDefaultFormatErrFieldValue = func(i interface{}) string {
return colorize(fmt.Sprintf("%s", i), colorRed, consoleNoColor)
func consoleDefaultFormatErrFieldValue(noColor bool) Formatter {
return func(i interface{}) string {
return colorize(fmt.Sprintf("%s", i), colorRed, noColor)
}
)
}
+17 -1
View File
@@ -97,7 +97,7 @@ func TestConsoleWriter(t *testing.T) {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "\x1b[2m<nil>\x1b[0m \x1b[31mWRN\x1b[0m Foobar\n"
expectedOutput := "\x1b[90m<nil>\x1b[0m \x1b[31mWRN\x1b[0m Foobar\n"
actualOutput := buf.String()
if actualOutput != expectedOutput {
t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
@@ -121,6 +121,22 @@ func TestConsoleWriter(t *testing.T) {
}
})
t.Run("Write colorized fields", func(t *testing.T) {
buf := &bytes.Buffer{}
w := zerolog.ConsoleWriter{Out: buf, NoColor: false}
_, err := w.Write([]byte(`{"level" : "warn", "message" : "Foobar", "foo": "bar"}`))
if err != nil {
t.Errorf("Unexpected error when writing output: %s", err)
}
expectedOutput := "\x1b[90m<nil>\x1b[0m \x1b[31mWRN\x1b[0m Foobar \x1b[36mfoo=\x1b[0mbar\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}
+16 -2
View File
@@ -350,8 +350,8 @@ func (c Context) Interface(key string, i interface{}) Context {
type callerHook struct{}
func (ch callerHook) Run(e *Event, level Level, msg string) {
// Three extra frames to skip (added by hook infra).
e.caller(CallerSkipFrameCount + 3)
// Extra frames to skip (added by hook infra).
e.caller(CallerSkipFrameCount + contextCallerSkipFrameCount)
}
var ch = callerHook{}
@@ -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)
+44 -13
View File
@@ -5,7 +5,6 @@ import (
"net"
"os"
"runtime"
"strconv"
"sync"
"time"
)
@@ -18,11 +17,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 +24,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 +132,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 +266,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 +289,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 +309,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 {
@@ -641,7 +672,7 @@ func (e *Event) caller(skip int) *Event {
if !ok {
return e
}
e.buf = enc.AppendString(enc.AppendKey(e.buf, CallerFieldName), file+":"+strconv.Itoa(line))
e.buf = enc.AppendString(enc.AppendKey(e.buf, CallerFieldName), CallerMarshalFunc(file, line))
return e
}
+25 -1
View File
@@ -1,6 +1,9 @@
package zerolog
import "time"
import (
"strconv"
"time"
)
import "sync/atomic"
var (
@@ -22,6 +25,22 @@ var (
// CallerSkipFrameCount is the number of stack frames to skip to find the caller.
CallerSkipFrameCount = 2
// CallerMarshalFunc allows customization of global caller marshaling
CallerMarshalFunc = func(file string, line int) string {
return file+":"+strconv.Itoa(line)
}
// 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 +56,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 (
+7
View File
@@ -0,0 +1,7 @@
// +build go1.12
package zerolog
// Since go 1.12, some auto generated init functions are hidden from
// runtime.Caller.
const contextCallerSkipFrameCount = 2
+56
View File
@@ -7,6 +7,8 @@ import (
"net"
"reflect"
"runtime"
"strconv"
"strings"
"testing"
"time"
)
@@ -640,3 +642,57 @@ func TestErrorMarshalFunc(t *testing.T) {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
func TestCallerMarshalFunc(t *testing.T) {
out := &bytes.Buffer{}
log := New(out)
// test default behaviour this is really brittle due to the line numbers
// actually mattering for validation
_, file, line, _ := runtime.Caller(0)
caller := fmt.Sprintf("%s:%d", file, line + 2)
log.Log().Caller().Msg("msg")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"` + caller + `","message":"msg"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
out.Reset()
// test custom behavior. In this case we'll take just the last directory
origCallerMarshalFunc := CallerMarshalFunc
defer func() { CallerMarshalFunc = origCallerMarshalFunc }()
CallerMarshalFunc = func(file string, line int) string {
parts := strings.Split(file, "/")
if len(parts) > 1 {
return strings.Join(parts[len(parts)-2:], "/")+":"+strconv.Itoa(line)
} else {
return file+":"+strconv.Itoa(line)
}
}
_, file, line, _ = runtime.Caller(0)
caller = CallerMarshalFunc(file, line+2)
log.Log().Caller().Msg("msg")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"caller":"` + caller + `","message":"msg"}`+"\n"; got != want {
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)
}
}
+5
View File
@@ -0,0 +1,5 @@
// +build !go1.12
package zerolog
const contextCallerSkipFrameCount = 3
+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()
}
}