@ -0,0 +1,76 @@ | |||||
package log | |||||
import ( | |||||
"fmt" | |||||
"github.com/pkg/errors" | |||||
) | |||||
// NewTracingLogger enables tracing by wrapping all errors (if they | |||||
// implement stackTracer interface) in tracedError. | |||||
// | |||||
// All errors returned by https://github.com/pkg/errors implement stackTracer | |||||
// interface. | |||||
// | |||||
// For debugging purposes only as it doubles the amount of allocations. | |||||
func NewTracingLogger(next Logger) Logger { | |||||
return &tracingLogger{ | |||||
next: next, | |||||
} | |||||
} | |||||
type stackTracer interface { | |||||
error | |||||
StackTrace() errors.StackTrace | |||||
} | |||||
type tracingLogger struct { | |||||
next Logger | |||||
} | |||||
func (l *tracingLogger) Info(msg string, keyvals ...interface{}) error { | |||||
return l.next.Info(msg, formatErrors(keyvals)...) | |||||
} | |||||
func (l *tracingLogger) Debug(msg string, keyvals ...interface{}) error { | |||||
return l.next.Debug(msg, formatErrors(keyvals)...) | |||||
} | |||||
func (l *tracingLogger) Error(msg string, keyvals ...interface{}) error { | |||||
return l.next.Error(msg, formatErrors(keyvals)...) | |||||
} | |||||
func (l *tracingLogger) With(keyvals ...interface{}) Logger { | |||||
return &tracingLogger{next: l.next.With(formatErrors(keyvals)...)} | |||||
} | |||||
func formatErrors(keyvals []interface{}) []interface{} { | |||||
newKeyvals := make([]interface{}, len(keyvals)) | |||||
copy(newKeyvals, keyvals) | |||||
for i := 0; i < len(newKeyvals)-1; i += 2 { | |||||
if err, ok := newKeyvals[i+1].(stackTracer); ok { | |||||
newKeyvals[i+1] = tracedError{err} | |||||
} | |||||
} | |||||
return newKeyvals | |||||
} | |||||
// tracedError wraps a stackTracer and just makes the Error() result | |||||
// always return a full stack trace. | |||||
type tracedError struct { | |||||
wrapped stackTracer | |||||
} | |||||
var _ stackTracer = tracedError{} | |||||
func (t tracedError) StackTrace() errors.StackTrace { | |||||
return t.wrapped.StackTrace() | |||||
} | |||||
func (t tracedError) Cause() error { | |||||
return t.wrapped | |||||
} | |||||
func (t tracedError) Error() string { | |||||
return fmt.Sprintf("%+v", t.wrapped) | |||||
} |
@ -0,0 +1,42 @@ | |||||
package log_test | |||||
import ( | |||||
"bytes" | |||||
stderr "errors" | |||||
"fmt" | |||||
"strings" | |||||
"testing" | |||||
"github.com/pkg/errors" | |||||
"github.com/tendermint/tmlibs/log" | |||||
) | |||||
func TestTracingLogger(t *testing.T) { | |||||
var buf bytes.Buffer | |||||
var logger log.Logger | |||||
logger = log.NewTMJSONLogger(&buf) | |||||
logger1 := log.NewTracingLogger(logger) | |||||
err1 := errors.New("Courage is grace under pressure.") | |||||
err2 := errors.New("It does not matter how slowly you go, so long as you do not stop.") | |||||
logger1.With("err1", err1).Info("foo", "err2", err2) | |||||
have := strings.Replace(strings.Replace(strings.TrimSpace(buf.String()), "\\n", "", -1), "\\t", "", -1) | |||||
if want := strings.Replace(strings.Replace(`{"_msg":"foo","err1":"`+fmt.Sprintf("%+v", err1)+`","err2":"`+fmt.Sprintf("%+v", err2)+`","level":"info"}`, "\t", "", -1), "\n", "", -1); want != have { | |||||
t.Errorf("\nwant '%s'\nhave '%s'", want, have) | |||||
} | |||||
buf.Reset() | |||||
logger.With("err1", stderr.New("Opportunities don't happen. You create them.")).Info("foo", "err2", stderr.New("Once you choose hope, anything's possible.")) | |||||
if want, have := `{"_msg":"foo","err1":"Opportunities don't happen. You create them.","err2":"Once you choose hope, anything's possible.","level":"info"}`, strings.TrimSpace(buf.String()); want != have { | |||||
t.Errorf("\nwant '%s'\nhave '%s'", want, have) | |||||
} | |||||
buf.Reset() | |||||
logger.With("user", "Sam").With("context", "value").Info("foo", "bar", "baz") | |||||
if want, have := `{"_msg":"foo","bar":"baz","context":"value","level":"info","user":"Sam"}`, strings.TrimSpace(buf.String()); want != have { | |||||
t.Errorf("\nwant '%s'\nhave '%s'", want, have) | |||||
} | |||||
} |