Repository: rollbar/rollbar-go
Pull request #59 (2019-06-07 01:41:41)

Wrapper for AWS Lambda handlers
Use:
```
lambda.Start(rollbar.LambdaWrapper(MyHandler))
```
* Uncaught panics will be reported to Rollbar.
* All Rollbar events will be allowed to complete before the function exits.
  

 



Patch:
diff --git a/client.go b/client.go
index 4bd3359..3da1c25 100644
--- a/client.go
+++ b/client.go
@@ -11,6 +11,7 @@ import (
 	"os"
 	"regexp"
 	"runtime"
+	"reflect"
 )
 
 // A Client can be used to interact with Rollbar via the configured Transport.
@@ -467,6 +468,59 @@ func (c *Client) WrapAndWait(f func()) (err interface{}) {
 	return
 }
 
+// LambdaWrapper calls handlerFunc with arguments, and recovers and reports a
+// panic to Rollbar if it occurs. This functions as a passthrough wrapper for
+// lambda.Start(). This also waits before returning to ensure all messages completed.
+func (c *Client) LambdaWrapper(handlerFunc interface{}) interface{} {
+	if handlerFunc == nil {
+		return lambdaErrorHandler(fmt.Errorf("handler is nil"))
+	}
+	handlerType := reflect.TypeOf(handlerFunc)
+	handlerValue := reflect.ValueOf(handlerFunc)
+
+	if handlerType.Kind() != reflect.Func {
+		return lambdaErrorHandler(fmt.Errorf("handler kind %s is not %s", handlerType.Kind(), reflect.Func))
+	}
+
+	handler := func(args []reflect.Value) []reflect.Value {
+		defer func() {
+			err := recover()
+			switch val := err.(type) {
+			case nil:
+				return
+			case error:
+				if c.configuration.checkIgnore(val.Error()) {
+					return
+				}
+				c.ErrorWithStackSkip(CRIT, val, 2)
+			default:
+				str := fmt.Sprint(val)
+				if c.configuration.checkIgnore(str) {
+					return
+				}
+				errValue := errors.New(str)
+				c.ErrorWithStackSkip(CRIT, errValue, 2)
+			}
+			c.Wait()
+		}()
+
+		ret := handlerValue.Call(args)
+		c.Wait()
+		return ret
+	}
+
+	fn := reflect.MakeFunc(handlerValue.Type(), handler).Interface()
+	return fn
+}
+
+type lambdaHandler func(context.Context, []byte) (interface{}, error)
+
+func lambdaErrorHandler(e error) lambdaHandler {
+	return func(ctx context.Context, payload []byte) (interface{}, error) {
+		return nil, e
+	}
+}
+
 // Wait will call the Wait method of the Transport. If using an asynchronous
 // transport then this will block until the queue of
 // errors / messages is empty. If using a synchronous transport then there
diff --git a/rollbar.go b/rollbar.go
index 11970de..2790fb3 100644
--- a/rollbar.go
+++ b/rollbar.go
@@ -525,6 +525,13 @@ func WrapAndWait(f func()) interface{} {
 	return std.WrapAndWait(f)
 }
 
+// LambdaWrapper calls handlerFunc with arguments, and recovers and reports a
+// panic to Rollbar if it occurs. This functions as a passthrough wrapper for
+// lambda.Start(). This also waits before returning to ensure all messages completed.
+func LambdaWrapper(handlerFunc interface{}) interface{} {
+	return std.LambdaWrapper(handlerFunc)
+}
+
 // 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.


Test patch:
diff --git a/client_test.go b/client_test.go
index 7799fe7..638f4c0 100644
--- a/client_test.go
+++ b/client_test.go
@@ -4,6 +4,8 @@ import (
 	"errors"
 	"github.com/rollbar/rollbar-go"
 	"regexp"
+	"context"
+	"reflect"
 	"testing"
 )
 
@@ -209,6 +211,88 @@ func TestWrapAndWaitNonErrorIgnore(t *testing.T) {
 	}
 }
 
+func testCallLambdaHandler(handler interface{}) interface{} {
+	fn := reflect.ValueOf(handler)
+	var args []reflect.Value
+	return fn.Call(args)
+}
+
+func testLambdaHandlerWithContext(ctx context.Context) (context.Context, error) {
+	return ctx, errors.New("test")
+}
+
+func testLambdaHandlerWithMessage(message TestMessage) (TestMessage, error) {
+	return message, errors.New("test")
+}
+
+type TestMessage struct {
+	Name string
+}
+
+func TestLambdaWrapperWithError(t *testing.T) {
+	client := testClient()
+	err := errors.New("bork")
+	//ctx := context.TODO()
+	handler := client.LambdaWrapper(func() {
+		panic(err)
+	})
+	fn := reflect.ValueOf(handler)
+	var args []reflect.Value
+	fn.Call(args)
+	//testCallLambdaHandler(handler)
+
+	if transport, ok := client.Transport.(*TestTransport); ok {
+		if transport.Body == nil {
+			t.Error("Expected Body to be present")
+		}
+		if !transport.WaitCalled {
+			t.Error("Expected wait to be called")
+		}
+	} else {
+		t.Fail()
+	}
+}
+
+func TestLambdaWrapperWithContext(t *testing.T) {
+	client := testClient()
+	ctx := context.TODO()
+	handler := client.LambdaWrapper(testLambdaHandlerWithContext)
+	var args []reflect.Value
+	args = append(args, reflect.ValueOf(ctx))
+	resp := reflect.ValueOf(handler).Call(args)
+	var outCtx context.Context
+	outCtx = resp[0].Interface().(context.Context)
+	var err error
+	err = resp[1].Interface().(error)
+
+	if outCtx != ctx {
+		t.Error("Expected ctx to be present")
+	}
+	if err.Error() != "test" {
+		t.Error("Expected error to be present")
+	}
+}
+
+func TestLambdaWrapperWithMessage(t *testing.T) {
+	client := testClient()
+	message := TestMessage{Name: "foo"}
+	handler := client.LambdaWrapper(testLambdaHandlerWithMessage)
+	var args []reflect.Value
+	args = append(args, reflect.ValueOf(message))
+	resp := reflect.ValueOf(handler).Call(args)
+	var outMessage TestMessage
+	outMessage = resp[0].Interface().(TestMessage)
+	var err error
+	err = resp[1].Interface().(error)
+
+	if outMessage != message {
+		t.Error("Expected message to be present")
+	}
+	if err.Error() != "test" {
+		t.Error("Expected error to be present")
+	}
+}
+
 func TestGettersAndSetters_Default(t *testing.T) {
 	c := testClient()
 	testGettersAndSetters(c, t)