Skip to content

Commit

Permalink
Merge pull request #64 from wheniwork/master
Browse files Browse the repository at this point in the history
Allow full customization of error unwrapping and stack tracing (and support Unwrap automatically)
  • Loading branch information
waltjones authored Sep 24, 2019
2 parents 7f01d22 + 6f4ecdf commit b2d6a90
Show file tree
Hide file tree
Showing 9 changed files with 365 additions and 102 deletions.
32 changes: 25 additions & 7 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (
"io"
"net/http"
"os"
"reflect"
"regexp"
"runtime"
"reflect"
)

// A Client can be used to interact with Rollbar via the configured Transport.
Expand Down Expand Up @@ -163,11 +163,26 @@ func (c *Client) SetTransform(transform func(map[string]interface{})) {
c.configuration.transform = transform
}

// SetStackTracer sets the stackTracer function which is called to extract the stack
// trace from enhanced error types. Return nil if no trace information is available.
// Return true if the error type can be handled and false otherwise.
// This feature can be used to add support for custom error type stack trace extraction.
func (c *Client) SetStackTracer(stackTracer func(err error) ([]runtime.Frame, bool)) {
// SetUnwrapper sets the UnwrapperFunc used by the client. The unwrapper function
// is used to extract wrapped errors from enhanced error types. This feature can be used to add
// support for custom error types that do not yet implement the Unwrap method specified in Go 1.13.
// See the documentation of UnwrapperFunc for more details.
//
// In order to preserve the default unwrapping behavior, callers of SetUnwrapper may wish to include
// a call to DefaultUnwrapper in their custom unwrapper function. See the example on the SetUnwrapper function.
func (c *Client) SetUnwrapper(unwrapper UnwrapperFunc) {
c.configuration.unwrapper = unwrapper
}

// SetStackTracer sets the StackTracerFunc used by the client. The stack tracer
// function is used to extract the stack trace from enhanced error types. This feature can be used
// to add support for custom error types that do not implement the Stacker interface.
// See the documentation of StackTracerFunc for more details.
//
// In order to preserve the default stack tracing behavior, callers of SetStackTracer may wish
// to include a call to DefaultStackTracer in their custom tracing function. See the example
// on the SetStackTracer function.
func (c *Client) SetStackTracer(stackTracer StackTracerFunc) {
c.configuration.stackTracer = stackTracer
}

Expand Down Expand Up @@ -605,7 +620,8 @@ type configuration struct {
scrubFields *regexp.Regexp
checkIgnore func(string) bool
transform func(map[string]interface{})
stackTracer func(error) ([]runtime.Frame, bool)
unwrapper UnwrapperFunc
stackTracer StackTracerFunc
person Person
captureIp captureIp
}
Expand All @@ -629,6 +645,8 @@ func createConfiguration(token, environment, codeVersion, serverHost, serverRoot
fingerprint: false,
checkIgnore: func(_s string) bool { return false },
transform: func(_d map[string]interface{}) {},
unwrapper: DefaultUnwrapper,
stackTracer: DefaultStackTracer,
person: Person{},
captureIp: CaptureIpFull,
}
Expand Down
32 changes: 20 additions & 12 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ Package rollbar is a Golang Rollbar client that makes it easy to report errors t
Basic Usage
This package is designed to be used via the functions exposed at the root of the `rollbar` package. These work by managing a single instance of the `Client` type that is configurable via the setter functions at the root of the package.
package main
import (
Expand All @@ -12,10 +14,10 @@ Basic Usage
func main() {
rollbar.SetToken("MY_TOKEN")
rollbar.SetEnvironment("production") // defaults to "development"
rollbar.SetCodeVersion("v2") // optional Git hash/branch/tag (required for GitHub integration)
rollbar.SetServerHost("web.1") // optional override; defaults to hostname
rollbar.SetServerRoot("/") // local path of project (required for GitHub integration and non-project stacktrace collapsing)
rollbar.SetEnvironment("production") // defaults to "development"
rollbar.SetCodeVersion("v2") // optional Git hash/branch/tag (required for GitHub integration)
rollbar.SetServerHost("web.1") // optional override; defaults to hostname
rollbar.SetServerRoot("/") // local path of project (required for GitHub integration and non-project stacktrace collapsing)
rollbar.Info("Message body goes here")
rollbar.WrapAndWait(doSomething)
Expand All @@ -26,13 +28,12 @@ Basic Usage
timer.Reset(10) // this will panic
}
This package is designed to be used via the functions exposed at the root of the `rollbar` package. These work by managing a single instance of the `Client` type that is configurable via the setter functions at the root of the package.
If you wish for more fine grained control over the client or you wish to have multiple independent clients then you can create and manage your own instances of the `Client` type.
We provide two implementations of the `Transport` interface, `AsyncTransport` and `SyncTransport`. These manage the communication with the network layer. The Async version uses a buffered channel to communicate with the Rollbar API in a separate go routine. The Sync version is fully synchronous. It is possible to create your own `Transport` and configure a Client to use your preferred implementation.
Handling Panics
Go does not provide a mechanism for handling all panics automatically, therefore we provide two functions `Wrap` and `WrapAndWait` to make working with panics easier. They both take a function and then report to Rollbar if that function panics. They use the recover mechanism to capture the panic, and therefore if you wish your process to have the normal behaviour on panic (i.e. to crash), you will need to re-panic the result of calling `Wrap`. For example,
package main
Expand Down Expand Up @@ -60,15 +61,22 @@ Go does not provide a mechanism for handling all panics automatically, therefore
The above pattern of calling `Wrap(...)` and then `Wait(...)` can be combined via `WrapAndWait(...)`. When `WrapAndWait(...)` returns if there was a panic it has already been sent to the Rollbar API. The error is still returned by this function if there is one.
Tracing Errors
Due to the nature of the `error` type in Go, it can be difficult to attribute errors to their original origin without doing some extra work. To account for this, we provide multiple ways of configuring the client to unwrap errors and extract stack traces.
The client will automatically unwrap any error type which implements the `Unwrap() error` method specified in Go 1.13. (See https://golang.org/pkg/errors/ for details.) This behavior can be extended for other types of errors by calling `SetUnwrapper`.
Due to the nature of the `error` type in Go, it can be difficult to attribute errors to their original origin without doing some extra work. To account for this, we define the interface `CauseStacker`:
For stack traces, we provide the `Stacker` interface, which can be implemented on custom error types:
type CauseStacker interface {
error
Cause() error
type Stacker interface {
Stack() []runtime.Frame
}
One can implement this interface for custom Error types to be able to build up a chain of stack traces. In order to get the correct stack, callers are required to call runtime.Callers and build the runtime.Frame slice on their own at the time the cause is wrapped. This is the least intrusive mechanism for gathering this information due to the decisions made by the Go runtime to not track this information.
If you cannot implement the `Stacker` interface on your error type (which is common for third-party error libraries), you can provide a custom tracing function by calling `SetStackTracer`.
See the documentation of `SetUnwrapper` and `SetStackTracer` for more information and examples.
Finally, users of github.com/pkg/errors can use the utilities provided in the `errors` sub-package.
*/
package rollbar
2 changes: 2 additions & 0 deletions errors/go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module github.com/rollbar/rollbar-go/errors

go 1.13

require github.com/pkg/errors v0.8.1
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
module github.com/rollbar/rollbar-go

go 1.13
93 changes: 84 additions & 9 deletions rollbar.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,60 @@ var (
nilErrTitle = "<nil>"
)

// An UnwrapperFunc is used to extract wrapped errors when building an error chain. It should return
// the wrapped error if available, or nil otherwise.
//
// The client will use DefaultUnwrapper by default, and a user can override the default behavior
// by calling SetUnwrapper. See SetUnwrapper for more details.
type UnwrapperFunc func(error) error

// A StackTracerFunc is used to extract stack traces when building an error chain. The first return
// value should be the extracted stack trace, if available. The second return value should be
// whether the function was able to extract a stack trace (even if the extracted stack trace was
// empty or nil).
//
// The client will use DefaultStackTracer by default, and a user can override the default
// behavior by calling SetStackTracer. See SetStackTracer for more details.
type StackTracerFunc func(error) ([]runtime.Frame, bool)

// DefaultUnwrapper is the default UnwrapperFunc used by rollbar-go clients. It can unwrap any
// error types with the Unwrap method specified in Go 1.13, or any error type implementing the
// legacy CauseStacker interface.
//
// It also implicitly supports errors from github.com/pkg/errors. However, users of pkg/errors may
// wish to also use the stack trace extraction features provided in the
// github.com/rollbar/rollbar-go/errors package.
var DefaultUnwrapper UnwrapperFunc = func(err error) error {
type causer interface {
Cause() error
}
type wrapper interface { // matches the new Go 1.13 Unwrap() method, copied from xerrors
Unwrap() error
}

if e, ok := err.(causer); ok {
return e.Cause()
}
if e, ok := err.(wrapper); ok {
return e.Unwrap()
}

return nil
}

// DefaultStackTracer is the default StackTracerFunc used by rollbar-go clients. It can extract
// stack traces from error types implementing the Stacker interface (and by extension, the legacy
// CauseStacker interface).
//
// To support stack trace extraction for other types of errors, see SetStackTracer.
var DefaultStackTracer StackTracerFunc = func(err error) ([]runtime.Frame, bool) {
if s, ok := err.(Stacker); ok {
return s.Stack(), true
}

return nil, false
}

// SetEnabled sets whether or not the managed Client instance is enabled.
// If this is true then this library works as normal.
// If this is false then no calls will be made to the network.
Expand Down Expand Up @@ -126,12 +180,25 @@ func SetTransform(transform func(map[string]interface{})) {
std.SetTransform(transform)
}

// SetStackTracer sets the stackTracer function on the managed Client instance.
// StackTracer is called to extract the stack trace from enhanced error types.
// Return nil if no trace information is available. Return true if the error type
// can be handled and false otherwise.
// This feature can be used to add support for custom error type stack trace extraction.
func SetStackTracer(stackTracer func(err error) ([]runtime.Frame, bool)) {
// SetUnwrapper sets the UnwrapperFunc used by the managed Client instance. The unwrapper function
// is used to extract wrapped errors from enhanced error types. This feature can be used to add
// support for custom error types that do not yet implement the Unwrap method specified in Go 1.13.
// See the documentation of UnwrapperFunc for more details.
//
// In order to preserve the default unwrapping behavior, callers of SetUnwrapper may wish to include
// a call to DefaultUnwrapper in their custom unwrapper function. See the provided example.
func SetUnwrapper(unwrapper UnwrapperFunc) {
std.SetUnwrapper(unwrapper)
}

// SetStackTracer sets the StackTracerFunc used by the managed Client instance. The stack tracer
// function is used to extract the stack trace from enhanced error types. This feature can be used
// to add support for custom error types that do not implement the Stacker interface.
// See the documentation of StackTracerFunc for more details.
//
// In order to preserve the default stack tracing behavior, callers of SetStackTracer may wish
// to include a call to DefaultStackTracer in their custom tracing function. See the provided example.
func SetStackTracer(stackTracer StackTracerFunc) {
std.SetStackTracer(stackTracer)
}

Expand Down Expand Up @@ -541,11 +608,19 @@ func LambdaWrapper(handlerFunc interface{}) interface{} {
return std.LambdaWrapper(handlerFunc)
}

// Stacker is an interface that errors can implement to allow the extraction of stack traces.
// To generate a stack trace, users are required to call runtime.Callers and build the runtime.Frame slice
// at the time the error is created.
type Stacker interface {
Stack() []runtime.Frame
}

// CauseStacker is an interface that errors can implement to create a trace_chain.
// Callers are required to call runtime.Callers and build the runtime.Frame slice
// on their own at the time the cause is wrapped.
//
// Deprecated: For unwrapping, use the `Unwrap() error` method specified in Go 1.13. (See https://golang.org/pkg/errors/ for more information).
// For stack traces, use the `Stacker` interface directly.
type CauseStacker interface {
error
Cause() error
Stack() []runtime.Frame
Stacker
}
31 changes: 31 additions & 0 deletions rollbar_example_set_stack_tracer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package rollbar_test

import (
"runtime"

"github.com/rollbar/rollbar-go"
)

type CustomTraceError struct {
error
trace []runtime.Frame
}

func (e CustomTraceError) GetTrace() []runtime.Frame {
return e.trace
}

func ExampleSetStackTracer() {
rollbar.SetStackTracer(func(err error) ([]runtime.Frame, bool) {
// preserve the default behavior for other types of errors
if trace, ok := rollbar.DefaultStackTracer(err); ok {
return trace, ok
}

if cerr, ok := err.(CustomTraceError); ok {
return cerr.GetTrace(), true
}

return nil, false
})
}
27 changes: 27 additions & 0 deletions rollbar_example_set_unwrapper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package rollbar_test

import "github.com/rollbar/rollbar-go"

type CustomWrappingError struct {
error
wrapped error
}

func (e CustomWrappingError) GetWrappedError() error {
return e.wrapped
}

func ExampleSetUnwrapper() {
rollbar.SetUnwrapper(func(err error) error {
// preserve the default behavior for other types of errors
if unwrapped := rollbar.DefaultUnwrapper(err); unwrapped != nil {
return unwrapped
}

if ex, ok := err.(CustomWrappingError); ok {
return ex.GetWrappedError()
}

return nil
})
}
Loading

0 comments on commit b2d6a90

Please sign in to comment.