Add support for stack trace extration of error fields

This commit is contained in:
Olivier Poitrey 2018-02-07 21:55:53 -08:00
parent 27e0a22cbc
commit 3786fbfa73
5 changed files with 135 additions and 0 deletions

View File

@ -316,3 +316,17 @@ func (c Context) Caller() Context {
c.l = c.l.Hook(ch) c.l = c.l.Hook(ch)
return c return c
} }
type stackTraceHook struct{}
func (sh stackTraceHook) Run(e *Event, level Level, msg string) {
e.Stack()
}
var sh = callerHook{}
// Stack enables stack trace printing for the error passed to Err().
func (c Context) Stack() Context {
c.l = c.l.Hook(sh)
return c
}

View File

@ -27,6 +27,7 @@ type Event struct {
w LevelWriter w LevelWriter
level Level level Level
done func(msg string) done func(msg string)
stack bool // enable error stack trace
ch []Hook // hooks from context ch []Hook // hooks from context
h []Hook h []Hook
} }
@ -242,17 +243,38 @@ func (e *Event) Errs(key string, errs []error) *Event {
// Err adds the field "error" with err as a string to the *Event context. // Err adds the field "error" with err as a string to the *Event context.
// If err is nil, no field is added. // 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 { func (e *Event) Err(err error) *Event {
if e == nil { if e == nil {
return e return e
} }
if e.stack && ErrorStackMarshaler != nil {
s := ErrorStackMarshaler(err)
if len(s) > 0 {
e.buf = append(json.AppendKey(e.buf, ErrorStackFieldName), s...)
}
}
if err != nil { if err != nil {
e.buf = json.AppendError(json.AppendKey(e.buf, ErrorFieldName), err) e.buf = json.AppendError(json.AppendKey(e.buf, ErrorFieldName), err)
} }
return e return e
} }
// 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. // Bool adds the field key with val as a bool to the *Event context.
func (e *Event) Bool(key string, b bool) *Event { func (e *Event) Bool(key string, b bool) *Event {
if e == nil { if e == nil {

View File

@ -19,6 +19,13 @@ var (
// CallerFieldName is the field name used for caller field. // CallerFieldName is the field name used for caller field.
CallerFieldName = "caller" CallerFieldName = "caller"
// ErrorStackFieldName is the field name used for error stacks.
ErrorStackFieldName = "stack"
// ErrorStackMarshaler extract the stack from err if any, and returns it as
// a marshaled JSON.
ErrorStackMarshaler func(err error) []byte
// TimeFieldFormat defines the time format of the Time field type. // TimeFieldFormat defines the time format of the Time field type.
// If set to an empty string, the time is formatted as an UNIX timestamp // If set to an empty string, the time is formatted as an UNIX timestamp
// as integer. // as integer.

66
pkgerrors/stacktrace.go Normal file
View File

@ -0,0 +1,66 @@
package pkgerrors
import (
"bytes"
"fmt"
"github.com/pkg/errors"
"github.com/rs/zerolog/internal/json"
)
var (
StackSourceFileName = "source"
StackSourceLineName = "line"
StackSourceFunctionName = "func"
)
// MarshalStack implements pkg/errors stack trace marshaling.
//
// zerolog.ErrorStackMarshaler = MarshalStack
func MarshalStack(err error) []byte {
type stackTracer interface {
StackTrace() errors.StackTrace
}
var st errors.StackTrace
if err, ok := err.(stackTracer); ok {
st = err.StackTrace()
} else {
return nil
}
return appendJSONStack(make([]byte, 0, 500), st)
}
func appendJSONStack(dst []byte, st errors.StackTrace) []byte {
buf := bytes.NewBuffer(make([]byte, 0, 100))
dst = append(dst, '[')
for i, frame := range st {
if i > 0 {
dst = append(dst, ',')
}
dst = append(dst, '{')
fmt.Fprintf(buf, "%s", frame)
dst = json.AppendString(dst, StackSourceFileName)
dst = append(dst, ':')
dst = json.AppendBytes(dst, buf.Bytes())
dst = append(dst, ',')
buf.Reset()
fmt.Fprintf(buf, "%d", frame)
dst = json.AppendString(dst, StackSourceLineName)
dst = append(dst, ':')
dst = json.AppendBytes(dst, buf.Bytes())
dst = append(dst, ',')
buf.Reset()
fmt.Fprintf(buf, "%n", frame)
dst = json.AppendString(dst, StackSourceFunctionName)
dst = append(dst, ':')
dst = json.AppendBytes(dst, buf.Bytes())
dst = append(dst, '}')
}
dst = append(dst, ']')
return dst
}

View File

@ -0,0 +1,26 @@
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":\[\{"source":"stacktrace_test.go","line":"18","func":"TestLogStack"\},.*\],"error":"from error: error message"\}\n`
if ok, _ := regexp.MatchString(want, got); !ok {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}
}