Skip to content

Commit

Permalink
Add support for github.com/pkg/errors interfaces (#23)
Browse files Browse the repository at this point in the history
* Spelling correction in variable name

* Add support for github.com/pkg/errors.causer interface

* Add support for github.com/pkg/errors.stackTracer interface

When this interface is satisfied, we can now fetch the stack trace from the
error, same as the Stacktracer interface.
  • Loading branch information
flimzy authored and evalphobia committed Nov 27, 2016
1 parent 16401a0 commit be14ffa
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 7 deletions.
56 changes: 53 additions & 3 deletions sentry.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"encoding/json"
"fmt"
"net/http"
"runtime"
"time"

"github.com/Sirupsen/logrus"
"github.com/getsentry/raven-go"
"github.com/pkg/errors"
)

var (
Expand Down Expand Up @@ -36,10 +38,19 @@ type SentryHook struct {
extraFilters map[string]func(interface{}) interface{}
}

// The Stacktracer interface allows an error type to return a raven.Stacktrace.
type Stacktracer interface {
GetStacktrace() *raven.Stacktrace
}

type causer interface {
Cause() error
}

type pkgErrorStackTracer interface {
StackTrace() errors.StackTrace
}

// StackTraceConfiguration allows for configuring stacktraces
type StackTraceConfiguration struct {
// whether stacktraces should be enabled
Expand Down Expand Up @@ -130,9 +141,8 @@ func (hook *SentryHook) Fire(entry *logrus.Entry) error {
if stConfig.Enable && entry.Level <= stConfig.Level {
if err, ok := getAndDelError(d, logrus.ErrorKey); ok {
var currentStacktrace *raven.Stacktrace
if stacktracer, ok := err.(Stacktracer); ok {
currentStacktrace = stacktracer.GetStacktrace()
} else {
currentStacktrace, err = hook.findStacktraceAndCause(err)
if currentStacktrace == nil {
currentStacktrace = raven.NewStacktrace(stConfig.Skip, stConfig.Context, stConfig.InAppPrefixes)
}
exc := raven.NewException(err, currentStacktrace)
Expand Down Expand Up @@ -167,6 +177,46 @@ func (hook *SentryHook) Fire(entry *logrus.Entry) error {
return nil
}

func (hook *SentryHook) findStacktraceAndCause(err error) (*raven.Stacktrace, error) {
errCause := errors.Cause(err)
var stacktrace *raven.Stacktrace
var stackErr errors.StackTrace
for err != nil {
// Find the earliest *raven.Stacktrace, or error.StackTrace
if tracer, ok := err.(Stacktracer); ok {
stacktrace = tracer.GetStacktrace()
stackErr = nil
} else if tracer, ok := err.(pkgErrorStackTracer); ok {
stacktrace = nil
stackErr = tracer.StackTrace()
}
if cause, ok := err.(causer); ok {
err = cause.Cause()
} else {
break
}
}
if stackErr != nil {
stacktrace = hook.convertStackTrace(stackErr)
}
return stacktrace, errCause
}

// convertStackTrace converts an errors.StackTrace into a natively consumable
// *raven.Stacktrace
func (hook *SentryHook) convertStackTrace(st errors.StackTrace) *raven.Stacktrace {
stConfig := &hook.StacktraceConfiguration
stFrames := []errors.Frame(st)
frames := make([]*raven.StacktraceFrame, 0, len(stFrames))
for i := range stFrames {
pc := uintptr(stFrames[i])
fn := runtime.FuncForPC(pc)
file, line := fn.FileLine(pc)
frames = append(frames, raven.NewStacktraceFrame(pc, file, line, stConfig.Context, stConfig.InAppPrefixes))
}
return &raven.Stacktrace{Frames: frames}
}

// Levels returns the available logging levels.
func (hook *SentryHook) Levels() []logrus.Level {
return hook.levels
Expand Down
40 changes: 36 additions & 4 deletions sentry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (

"github.com/Sirupsen/logrus"
"github.com/getsentry/raven-go"
pkgerrors "github.com/pkg/errors"
)

const (
Expand Down Expand Up @@ -196,7 +197,7 @@ func TestSentryStacktrace(t *testing.T) {
hook.StacktraceConfiguration.Enable = true

logger.Error(message) // this is the call that the last frame of stacktrace should capture
expectedLineno := 198 //this should be the line number of the previous line
expectedLineno := 199 //this should be the line number of the previous line

packet = <-pch
stacktraceSize = len(packet.Stacktrace.Frames)
Expand Down Expand Up @@ -240,9 +241,40 @@ func TestSentryStacktrace(t *testing.T) {
if packet.Exception.Stacktrace != nil {
frames = packet.Exception.Stacktrace.Frames
}
if len(frames) != 1 || frames[0].Filename != escpectedStackFrameFilename {
if len(frames) != 1 || frames[0].Filename != expectedStackFrameFilename {
t.Error("Stacktrace should be taken from err if it implements the Stacktracer interface")
}

logger.WithError(pkgerrors.Wrap(myStacktracerError{}, "wrapped")).Error(message) // use an error that wraps a Stacktracer
packet = <-pch
if packet.Exception.Stacktrace != nil {
frames = packet.Exception.Stacktrace.Frames
}
expectedCulprit := "myStacktracerError!"
if packet.Culprit != expectedCulprit {
t.Errorf("Expected culprit of '%s', got '%s'", expectedCulprit, packet.Culprit)
}
if len(frames) != 1 || frames[0].Filename != expectedStackFrameFilename {
t.Error("Stacktrace should be taken from err if it implements the Stacktracer interface")
}

logger.WithError(pkgerrors.New("errorX")).Error(message) // use an error that implements pkgErrorStackTracer
packet = <-pch
if packet.Exception.Stacktrace != nil {
frames = packet.Exception.Stacktrace.Frames
}
expectedPkgErrorsStackTraceFilename := "github.com/evalphobia/logrus_sentry/sentry_test.go"
expectedFrameCount := 5
expectedCulprit = "errorX"
if packet.Culprit != expectedCulprit {
t.Errorf("Expected culprit of '%s', got '%s'", expectedCulprit, packet.Culprit)
}
if len(frames) != expectedFrameCount {
t.Errorf("Expected %d frames, got %d", expectedFrameCount, len(frames))
}
if frames[0].Filename != expectedPkgErrorsStackTraceFilename {
t.Error("Stacktrace should be taken from err if it implements the pkgErrorStackTracer interface")
}
})
}

Expand Down Expand Up @@ -408,12 +440,12 @@ type myStacktracerError struct{}

func (myStacktracerError) Error() string { return "myStacktracerError!" }

const escpectedStackFrameFilename = "errorFile.go"
const expectedStackFrameFilename = "errorFile.go"

func (myStacktracerError) GetStacktrace() *raven.Stacktrace {
return &raven.Stacktrace{
Frames: []*raven.StacktraceFrame{
{Filename: escpectedStackFrameFilename},
{Filename: expectedStackFrameFilename},
},
}
}

0 comments on commit be14ffa

Please sign in to comment.