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

Compare commits

..

6 Commits

Author SHA1 Message Date
Mike Camp 651d361cfe Add CallerWithSkipFrameCount to the Context (#98) (#135)
Add the ability to skip a specified number of stack frames
on a per context basis. Before this toe CallerSkipFrameCount
could only be set globally.
2019-03-04 16:41:18 -08:00
Soloman Weng 8e5449ab35 Allow using custom level field format (#136) 2019-03-01 16:08:23 -08:00
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
10 changed files with 177 additions and 26 deletions
+2
View File
@@ -4,6 +4,8 @@ go:
- "1.8"
- "1.9"
- "1.10"
- "1.11"
- "1.12"
- "master"
matrix:
allow_failures:
+6 -8
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 (
@@ -299,7 +297,7 @@ func consoleDefaultFormatTimestamp(timeFormat string, noColor bool) Formatter {
case json.Number:
t = tt.String()
}
return colorize(t, colorFaint, noColor)
return colorize(t, colorDarkGray, noColor)
}
}
@@ -342,7 +340,7 @@ func consoleDefaultFormatCaller(noColor bool) Formatter {
c = strings.TrimPrefix(c, cwd)
c = strings.TrimPrefix(c, "/")
}
c = colorize(c, colorBold, noColor) + colorize(" >", colorFaint, noColor)
c = colorize(c, colorBold, noColor) + colorize(" >", colorCyan, noColor)
}
return c
}
@@ -354,7 +352,7 @@ func consoleDefaultFormatMessage(i interface{}) string {
func consoleDefaultFormatFieldName(noColor bool) Formatter {
return func(i interface{}) string {
return colorize(fmt.Sprintf("%s=", i), colorFaint, noColor)
return colorize(fmt.Sprintf("%s=", i), colorCyan, 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}
+32 -6
View File
@@ -2,6 +2,7 @@ package zerolog
import (
"io/ioutil"
"math"
"net"
"time"
)
@@ -347,14 +348,31 @@ func (c Context) Interface(key string, i interface{}) Context {
return c
}
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)
type callerHook struct {
callerSkipFrameCount int
}
var ch = callerHook{}
func newCallerHook(skipFrameCount int) callerHook {
return callerHook{callerSkipFrameCount: skipFrameCount}
}
func (ch callerHook) Run(e *Event, level Level, msg string) {
switch ch.callerSkipFrameCount {
case useGlobalSkipFrameCount:
// Extra frames to skip (added by hook infra).
e.caller(CallerSkipFrameCount + contextCallerSkipFrameCount)
default:
// Extra frames to skip (added by hook infra).
e.caller(ch.callerSkipFrameCount + contextCallerSkipFrameCount)
}
}
// useGlobalSkipFrameCount acts as a flag to informat callerHook.Run
// to use the global CallerSkipFrameCount.
const useGlobalSkipFrameCount = math.MinInt32
// ch is the default caller hook using the global CallerSkipFrameCount.
var ch = newCallerHook(useGlobalSkipFrameCount)
// Caller adds the file:line of the caller with the zerolog.CallerFieldName key.
func (c Context) Caller() Context {
@@ -362,6 +380,14 @@ func (c Context) Caller() Context {
return c
}
// CallerWithSkipFrameCount adds the file:line of the caller with the zerolog.CallerFieldName key.
// The specified skipFrameCount int will override the global CallerSkipFrameCount for this context's respective logger.
// If set to -1 the global CallerSkipFrameCount will be used.
func (c Context) CallerWithSkipFrameCount(skipFrameCount int) Context {
c.l = c.l.Hook(newCallerHook(skipFrameCount))
return c
}
type stackTraceHook struct{}
func (sh stackTraceHook) Run(e *Event, level Level, msg string) {
+1 -2
View File
@@ -5,7 +5,6 @@ import (
"net"
"os"
"runtime"
"strconv"
"sync"
"time"
)
@@ -673,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
}
+14 -1
View File
@@ -1,6 +1,9 @@
package zerolog
import "time"
import (
"strconv"
"time"
)
import "sync/atomic"
var (
@@ -10,6 +13,11 @@ var (
// LevelFieldName is the field name used for the level field.
LevelFieldName = "level"
// LevelFieldMarshalFunc allows customization of global level field marshaling
LevelFieldMarshalFunc = func(l Level) string {
return l.String()
}
// MessageFieldName is the field name used for the message field.
MessageFieldName = "message"
@@ -22,6 +30,11 @@ 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"
+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
+8 -8
View File
@@ -152,19 +152,19 @@ func (l Level) String() string {
// returns an error if the input string does not match known values.
func ParseLevel(levelStr string) (Level, error) {
switch levelStr {
case DebugLevel.String():
case LevelFieldMarshalFunc(DebugLevel):
return DebugLevel, nil
case InfoLevel.String():
case LevelFieldMarshalFunc(InfoLevel):
return InfoLevel, nil
case WarnLevel.String():
case LevelFieldMarshalFunc(WarnLevel):
return WarnLevel, nil
case ErrorLevel.String():
case LevelFieldMarshalFunc(ErrorLevel):
return ErrorLevel, nil
case FatalLevel.String():
case LevelFieldMarshalFunc(FatalLevel):
return FatalLevel, nil
case PanicLevel.String():
case LevelFieldMarshalFunc(PanicLevel):
return PanicLevel, nil
case NoLevel.String():
case LevelFieldMarshalFunc(NoLevel):
return NoLevel, nil
}
return NoLevel, fmt.Errorf("Unknown Level String: '%s', defaulting to NoLevel", levelStr)
@@ -380,7 +380,7 @@ func (l *Logger) newEvent(level Level, done func(string)) *Event {
e.done = done
e.ch = l.hooks
if level != NoLevel {
e.Str(LevelFieldName, level.String())
e.Str(LevelFieldName, LevelFieldMarshalFunc(level))
}
if l.context != nil && len(l.context) > 0 {
e.buf = enc.AppendObjectData(e.buf, l.context)
+85
View File
@@ -7,6 +7,8 @@ import (
"net"
"reflect"
"runtime"
"strconv"
"strings"
"testing"
"time"
)
@@ -105,6 +107,20 @@ func TestWith(t *testing.T) {
if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":"foo","bytes":"bar","hex":"12ef","json":{"some":"json"},"error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11.101,"float64":12.30303,"time":"0001-01-01T00:00:00Z","caller":"`+caller+`"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
// Validate CallerWithSkipFrameCount.
out.Reset()
_, file, line, _ = runtime.Caller(0)
caller = fmt.Sprintf("%s:%d", file, line+5)
log = ctx.CallerWithSkipFrameCount(3).Logger()
func() {
log.Log().Msg("")
}()
// The above line is a little contrived, but the line above should be the line due
// to the extra frame skip.
if got, want := decodeIfBinaryToString(out.Bytes()), `{"string":"foo","bytes":"bar","hex":"12ef","json":{"some":"json"},"error":"some error","bool":true,"int":1,"int8":2,"int16":3,"int32":4,"int64":5,"uint":6,"uint8":7,"uint16":8,"uint32":9,"uint64":10,"float32":11.101,"float64":12.30303,"time":"0001-01-01T00:00:00Z","caller":"`+caller+`"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}
func TestFieldsMap(t *testing.T) {
@@ -641,6 +657,75 @@ func TestErrorMarshalFunc(t *testing.T) {
}
}
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)
}
}
func TestLevelFieldMarshalFunc(t *testing.T) {
origLevelFieldMarshalFunc := LevelFieldMarshalFunc
LevelFieldMarshalFunc = func(l Level) string {
return strings.ToUpper(l.String())
}
defer func() {
LevelFieldMarshalFunc = origLevelFieldMarshalFunc
}()
out := &bytes.Buffer{}
log := New(out)
log.Debug().Msg("test")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"DEBUG","message":"test"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
out.Reset()
log.Info().Msg("test")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"INFO","message":"test"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
out.Reset()
log.Warn().Msg("test")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"WARN","message":"test"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
out.Reset()
log.Error().Msg("test")
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"ERROR","message":"test"}`+"\n"; got != want {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
out.Reset()
}
type errWriter struct {
error
}
+5
View File
@@ -0,0 +1,5 @@
// +build !go1.12
package zerolog
const contextCallerSkipFrameCount = 3