Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added multistack logging #144

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
module github.com/rs/zerolog

go 1.12

require (
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
github.com/pkg/errors v0.8.1
github.com/rs/xid v1.2.1
github.com/zenazn/goji v0.9.0
golang.org/x/tools v0.0.0-20190321154406-ae772f11d294
)
71 changes: 68 additions & 3 deletions pkgerrors/stacktrace.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package pkgerrors

import (
"encoding/json"

"github.com/pkg/errors"
)

Expand Down Expand Up @@ -40,13 +42,14 @@ func frameField(f errors.Frame, s *state, c rune) string {
return string(s.b)
}

type stackTracer interface {
StackTrace() errors.StackTrace
}

// 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
Expand All @@ -63,3 +66,65 @@ func MarshalStack(err error) interface{} {
}
return out
}

type multiStack struct {
StackTraces []stackTrace
}

type stackTrace struct {
Frames []frame `json:"stacktrace"`
}

type frame struct {
StackSourceFileName string `json:"source"`
StackSourceLineName string `json:"line"`
StackSourceFuncName string `json:"func"`
}

// MarshalMultiStack properly implements pkg/errors stack trace marshaling by unwrapping the error stack.
//
// zerolog.ErrorStackMarshaler = MarshalMultiStack
func MarshalMultiStack(err error) interface{} {
multiStack := multiStack{}
currentErr := err
for currentErr != nil {
stack, ok := currentErr.(stackTracer)
if !ok {
// Unwrap again because errors.Wrap actually adds two
// layers of wrapping.
currentErr = unwrapErr(currentErr)
continue
}
st := stack.StackTrace()
s := &state{}
stackTrace := stackTrace{}
for _, f := range st {
frame := frame{
StackSourceFileName: frameField(f, s, 's'),
StackSourceLineName: frameField(f, s, 'd'),
StackSourceFuncName: frameField(f, s, 'n'),
}
stackTrace.Frames = append(stackTrace.Frames, frame)
}
multiStack.StackTraces = append(multiStack.StackTraces, stackTrace)

currentErr = unwrapErr(currentErr)
}
marshalled, err := json.Marshal(multiStack.StackTraces)
if err != nil {
return ""
}
return string(marshalled)
}

type causer interface {
Cause() error
}

func unwrapErr(err error) error {
cause, ok := err.(causer)
if !ok {
return nil
}
return cause.Cause()
}
76 changes: 76 additions & 0 deletions pkgerrors/stacktrace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,82 @@ func TestLogStack(t *testing.T) {
}
}

func TestLogMultiStack(t *testing.T) {
zerolog.ErrorStackMarshaler = MarshalMultiStack

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":"\[\{\"stacktrace\":\[\{\"source\":\"stacktrace_test.go\",\"line\":\"36\",\"func\":\"TestLogMultiStack\"\},.*\{\"stacktrace\".*\],"error":"from error: error message"\}`
mec07 marked this conversation as resolved.
Show resolved Hide resolved
if ok, _ := regexp.MatchString(want, got); !ok {
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
}

}

// Some methods of wrapping cause more layers of wrapping than other layers,
// e.g. errors.New doesn't cause any wrapping, errors.WithStack and
// errors.WithMessage add one layer of wrapping, whereas errors.Wrap adds
// two layers of wrapping.
func TestUnwrapErr(t *testing.T) {
table := []struct {
name string
err error
numberOfWrapLevels int
}{
{
name: "pass in nil error",
err: nil,
numberOfWrapLevels: 0,
},
{
name: "fundamental error",
err: errors.New("error message"),
numberOfWrapLevels: 0,
},
{
name: "singly wrapped error",
err: errors.Wrap(errors.New("error message"), "from error"),
numberOfWrapLevels: 2,
},
{
name: "doubly wrapped error",
err: errors.Wrap(errors.Wrap(errors.New("error message"), "first wrapper"), "second wrapper"),
numberOfWrapLevels: 4,
},
{
name: "wrap with WithStack",
err: errors.WithStack(errors.New("error message")),
numberOfWrapLevels: 1,
},
{
name: "wrap with WithMessage",
err: errors.WithMessage(errors.New("error message"), "first wrapper"),
numberOfWrapLevels: 1,
},
{
name: "wrap with WithMessage and Wrap",
err: errors.Wrap(errors.WithMessage(errors.New("error message"), "first wrapper"), "second wrapper"),
numberOfWrapLevels: 3,
},
}
for _, test := range table {
t.Run(test.name, func(t *testing.T) {
currentErr := test.err
for i := 0; i <= test.numberOfWrapLevels; i++ {
currentErr = unwrapErr(currentErr)
}
if currentErr != nil {
t.Fatal("Expected to have finished unwrapping by this point")
}
})
}
}

func BenchmarkLogStack(b *testing.B) {
zerolog.ErrorStackMarshaler = MarshalStack
out := &bytes.Buffer{}
Expand Down