From 19608eddd338f4d5a222153b1adea1646b1ad8f4 Mon Sep 17 00:00:00 2001 From: "will@newrelic.com" Date: Tue, 6 Nov 2018 11:02:10 -0800 Subject: [PATCH 1/3] Introduce HandlerTrace --- lambda/handler.go | 47 +++++++++++++++++++++++++++++++- lambda/handler_test.go | 61 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/lambda/handler.go b/lambda/handler.go index 0923a6e1..f780c4b7 100644 --- a/lambda/handler.go +++ b/lambda/handler.go @@ -70,6 +70,42 @@ func validateReturns(handler reflect.Type) error { return nil } +// HandlerTrace allows handlers which wrap the return value of NewHandler to +// access to the request and response events. +type HandlerTrace struct { + RequestEvent func(context.Context, interface{}) + ResponseEvent func(context.Context, interface{}) +} + +func callbackCompose(f1, f2 func(context.Context, interface{})) func(context.Context, interface{}) { + return func(ctx context.Context, event interface{}) { + if nil != f1 { + f1(ctx, event) + } + if nil != f2 { + f2(ctx, event) + } + } +} + +type handlerTraceKey struct{} + +// WithHandlerTrace adds callbacks to the provided context which allows handlers +// which wrap the return value of NewHandler to access to the request and +// response events. +func WithHandlerTrace(ctx context.Context, trace HandlerTrace) context.Context { + existing := contextHandlerTrace(ctx) + return context.WithValue(ctx, handlerTraceKey{}, HandlerTrace{ + RequestEvent: callbackCompose(existing.RequestEvent, trace.RequestEvent), + ResponseEvent: callbackCompose(existing.ResponseEvent, trace.ResponseEvent), + }) +} + +func contextHandlerTrace(ctx context.Context) HandlerTrace { + trace, _ := ctx.Value(handlerTraceKey{}).(HandlerTrace) + return trace +} + // NewHandler creates a base lambda handler from the given handler function. The // returned Handler performs JSON deserialization and deserialization, and // delegates to the input handler function. The handler function parameter must @@ -95,6 +131,9 @@ func NewHandler(handlerFunc interface{}) Handler { } return lambdaHandler(func(ctx context.Context, payload []byte) (interface{}, error) { + + trace := contextHandlerTrace(ctx) + // construct arguments var args []reflect.Value if takesContext { @@ -107,7 +146,9 @@ func NewHandler(handlerFunc interface{}) Handler { if err := json.Unmarshal(payload, event.Interface()); err != nil { return nil, err } - + if nil != trace.RequestEvent { + trace.RequestEvent(ctx, event.Elem().Interface()) + } args = append(args, event.Elem()) } @@ -123,6 +164,10 @@ func NewHandler(handlerFunc interface{}) Handler { var val interface{} if len(response) > 1 { val = response[0].Interface() + + if nil != trace.ResponseEvent { + trace.ResponseEvent(ctx, val) + } } return val, err diff --git a/lambda/handler_test.go b/lambda/handler_test.go index 15674dcf..d8c55fc8 100644 --- a/lambda/handler_test.go +++ b/lambda/handler_test.go @@ -206,3 +206,64 @@ func TestInvalidJsonInput(t *testing.T) { assert.Equal(t, "unexpected end of JSON input", err.Error()) } + +func TestHandlerTrace(t *testing.T) { + handler := NewHandler(func(ctx context.Context, x int) (int, error) { + if x != 123 { + t.Error(x) + } + return 456, nil + }) + requestHistory := "" + responseHistory := "" + checkInt := func(e interface{}, expected int) { + nt, ok := e.(int) + if !ok { + t.Error("not int as expected", e) + return + } + if nt != expected { + t.Error("unexpected value", nt, expected) + } + } + ctx := context.Background() + ctx = WithHandlerTrace(ctx, HandlerTrace{}) // empty HandlerTrace + ctx = WithHandlerTrace(ctx, HandlerTrace{ // with RequestEvent + RequestEvent: func(c context.Context, e interface{}) { + requestHistory += "A" + checkInt(e, 123) + }, + }) + ctx = WithHandlerTrace(ctx, HandlerTrace{ // with ResponseEvent + ResponseEvent: func(c context.Context, e interface{}) { + responseHistory += "X" + checkInt(e, 456) + }, + }) + ctx = WithHandlerTrace(ctx, HandlerTrace{ // with RequestEvent and ResponseEvent + RequestEvent: func(c context.Context, e interface{}) { + requestHistory += "B" + checkInt(e, 123) + }, + ResponseEvent: func(c context.Context, e interface{}) { + responseHistory += "Y" + checkInt(e, 456) + }, + }) + ctx = WithHandlerTrace(ctx, HandlerTrace{}) // empty HandlerTrace + + payload := []byte(`123`) + js, err := handler.Invoke(ctx, payload) + if err != nil { + t.Error("unexpected handler error", err) + } + if string(js) != "456" { + t.Error("unexpected handler output", string(js)) + } + if requestHistory != "AB" { + t.Error("request callbacks not called as expected", requestHistory) + } + if responseHistory != "XY" { + t.Error("response callbacks not called as expected", responseHistory) + } +} From 88abe6ac045d70e347b864388bcae36534f836ad Mon Sep 17 00:00:00 2001 From: "will@newrelic.com" Date: Mon, 28 Jan 2019 15:50:41 -0800 Subject: [PATCH 2/3] Move handler trace logic into handlertrace package --- lambda/handler.go | 40 +++----------------------------- lambda/handler_test.go | 11 +++++---- lambda/handlertrace/trace.go | 45 ++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 42 deletions(-) create mode 100644 lambda/handlertrace/trace.go diff --git a/lambda/handler.go b/lambda/handler.go index f780c4b7..b1d8f464 100644 --- a/lambda/handler.go +++ b/lambda/handler.go @@ -7,6 +7,8 @@ import ( "encoding/json" "fmt" "reflect" + + "github.com/aws/aws-lambda-go/lambda/handlertrace" ) type Handler interface { @@ -70,42 +72,6 @@ func validateReturns(handler reflect.Type) error { return nil } -// HandlerTrace allows handlers which wrap the return value of NewHandler to -// access to the request and response events. -type HandlerTrace struct { - RequestEvent func(context.Context, interface{}) - ResponseEvent func(context.Context, interface{}) -} - -func callbackCompose(f1, f2 func(context.Context, interface{})) func(context.Context, interface{}) { - return func(ctx context.Context, event interface{}) { - if nil != f1 { - f1(ctx, event) - } - if nil != f2 { - f2(ctx, event) - } - } -} - -type handlerTraceKey struct{} - -// WithHandlerTrace adds callbacks to the provided context which allows handlers -// which wrap the return value of NewHandler to access to the request and -// response events. -func WithHandlerTrace(ctx context.Context, trace HandlerTrace) context.Context { - existing := contextHandlerTrace(ctx) - return context.WithValue(ctx, handlerTraceKey{}, HandlerTrace{ - RequestEvent: callbackCompose(existing.RequestEvent, trace.RequestEvent), - ResponseEvent: callbackCompose(existing.ResponseEvent, trace.ResponseEvent), - }) -} - -func contextHandlerTrace(ctx context.Context) HandlerTrace { - trace, _ := ctx.Value(handlerTraceKey{}).(HandlerTrace) - return trace -} - // NewHandler creates a base lambda handler from the given handler function. The // returned Handler performs JSON deserialization and deserialization, and // delegates to the input handler function. The handler function parameter must @@ -132,7 +98,7 @@ func NewHandler(handlerFunc interface{}) Handler { return lambdaHandler(func(ctx context.Context, payload []byte) (interface{}, error) { - trace := contextHandlerTrace(ctx) + trace := handlertrace.ContextHandlerTrace(ctx) // construct arguments var args []reflect.Value diff --git a/lambda/handler_test.go b/lambda/handler_test.go index d8c55fc8..5d330299 100644 --- a/lambda/handler_test.go +++ b/lambda/handler_test.go @@ -8,6 +8,7 @@ import ( "fmt" "testing" + "github.com/aws/aws-lambda-go/lambda/handlertrace" "github.com/stretchr/testify/assert" ) @@ -227,20 +228,20 @@ func TestHandlerTrace(t *testing.T) { } } ctx := context.Background() - ctx = WithHandlerTrace(ctx, HandlerTrace{}) // empty HandlerTrace - ctx = WithHandlerTrace(ctx, HandlerTrace{ // with RequestEvent + ctx = handlertrace.WithHandlerTrace(ctx, handlertrace.HandlerTrace{}) // empty HandlerTrace + ctx = handlertrace.WithHandlerTrace(ctx, handlertrace.HandlerTrace{ // with RequestEvent RequestEvent: func(c context.Context, e interface{}) { requestHistory += "A" checkInt(e, 123) }, }) - ctx = WithHandlerTrace(ctx, HandlerTrace{ // with ResponseEvent + ctx = handlertrace.WithHandlerTrace(ctx, handlertrace.HandlerTrace{ // with ResponseEvent ResponseEvent: func(c context.Context, e interface{}) { responseHistory += "X" checkInt(e, 456) }, }) - ctx = WithHandlerTrace(ctx, HandlerTrace{ // with RequestEvent and ResponseEvent + ctx = handlertrace.WithHandlerTrace(ctx, handlertrace.HandlerTrace{ // with RequestEvent and ResponseEvent RequestEvent: func(c context.Context, e interface{}) { requestHistory += "B" checkInt(e, 123) @@ -250,7 +251,7 @@ func TestHandlerTrace(t *testing.T) { checkInt(e, 456) }, }) - ctx = WithHandlerTrace(ctx, HandlerTrace{}) // empty HandlerTrace + ctx = handlertrace.WithHandlerTrace(ctx, handlertrace.HandlerTrace{}) // empty HandlerTrace payload := []byte(`123`) js, err := handler.Invoke(ctx, payload) diff --git a/lambda/handlertrace/trace.go b/lambda/handlertrace/trace.go new file mode 100644 index 00000000..af03a34b --- /dev/null +++ b/lambda/handlertrace/trace.go @@ -0,0 +1,45 @@ +// Package handlertrace allows middleware authors using lambda.NewHandler to +// instrument request and response events. +package handlertrace + +import ( + "context" +) + +// HandlerTrace allows handlers which wrap the return value of lambda.NewHandler +// to access to the request and response events. +type HandlerTrace struct { + RequestEvent func(context.Context, interface{}) + ResponseEvent func(context.Context, interface{}) +} + +func callbackCompose(f1, f2 func(context.Context, interface{})) func(context.Context, interface{}) { + return func(ctx context.Context, event interface{}) { + if nil != f1 { + f1(ctx, event) + } + if nil != f2 { + f2(ctx, event) + } + } +} + +type handlerTraceKey struct{} + +// WithHandlerTrace adds callbacks to the provided context which allows handlers +// which wrap the return value of lambda.NewHandler to access to the request and +// response events. +func WithHandlerTrace(ctx context.Context, trace HandlerTrace) context.Context { + existing := ContextHandlerTrace(ctx) + return context.WithValue(ctx, handlerTraceKey{}, HandlerTrace{ + RequestEvent: callbackCompose(existing.RequestEvent, trace.RequestEvent), + ResponseEvent: callbackCompose(existing.ResponseEvent, trace.ResponseEvent), + }) +} + +// ContextHandlerTrace returns the HandlerTrace associated with the provided +// context. +func ContextHandlerTrace(ctx context.Context) HandlerTrace { + trace, _ := ctx.Value(handlerTraceKey{}).(HandlerTrace) + return trace +} From f830414ffeac3ca84e2328a62b95daec8aa329a8 Mon Sep 17 00:00:00 2001 From: "will@newrelic.com" Date: Tue, 29 Jan 2019 10:18:47 -0800 Subject: [PATCH 3/3] Address review feedback: Improve handlertrace names --- lambda/handler.go | 2 +- lambda/handler_test.go | 10 +++++----- lambda/handlertrace/trace.go | 13 ++++++------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/lambda/handler.go b/lambda/handler.go index b1d8f464..96eeac2e 100644 --- a/lambda/handler.go +++ b/lambda/handler.go @@ -98,7 +98,7 @@ func NewHandler(handlerFunc interface{}) Handler { return lambdaHandler(func(ctx context.Context, payload []byte) (interface{}, error) { - trace := handlertrace.ContextHandlerTrace(ctx) + trace := handlertrace.FromContext(ctx) // construct arguments var args []reflect.Value diff --git a/lambda/handler_test.go b/lambda/handler_test.go index 5d330299..962ac38c 100644 --- a/lambda/handler_test.go +++ b/lambda/handler_test.go @@ -228,20 +228,20 @@ func TestHandlerTrace(t *testing.T) { } } ctx := context.Background() - ctx = handlertrace.WithHandlerTrace(ctx, handlertrace.HandlerTrace{}) // empty HandlerTrace - ctx = handlertrace.WithHandlerTrace(ctx, handlertrace.HandlerTrace{ // with RequestEvent + ctx = handlertrace.NewContext(ctx, handlertrace.HandlerTrace{}) // empty HandlerTrace + ctx = handlertrace.NewContext(ctx, handlertrace.HandlerTrace{ // with RequestEvent RequestEvent: func(c context.Context, e interface{}) { requestHistory += "A" checkInt(e, 123) }, }) - ctx = handlertrace.WithHandlerTrace(ctx, handlertrace.HandlerTrace{ // with ResponseEvent + ctx = handlertrace.NewContext(ctx, handlertrace.HandlerTrace{ // with ResponseEvent ResponseEvent: func(c context.Context, e interface{}) { responseHistory += "X" checkInt(e, 456) }, }) - ctx = handlertrace.WithHandlerTrace(ctx, handlertrace.HandlerTrace{ // with RequestEvent and ResponseEvent + ctx = handlertrace.NewContext(ctx, handlertrace.HandlerTrace{ // with RequestEvent and ResponseEvent RequestEvent: func(c context.Context, e interface{}) { requestHistory += "B" checkInt(e, 123) @@ -251,7 +251,7 @@ func TestHandlerTrace(t *testing.T) { checkInt(e, 456) }, }) - ctx = handlertrace.WithHandlerTrace(ctx, handlertrace.HandlerTrace{}) // empty HandlerTrace + ctx = handlertrace.NewContext(ctx, handlertrace.HandlerTrace{}) // empty HandlerTrace payload := []byte(`123`) js, err := handler.Invoke(ctx, payload) diff --git a/lambda/handlertrace/trace.go b/lambda/handlertrace/trace.go index af03a34b..cdd452ff 100644 --- a/lambda/handlertrace/trace.go +++ b/lambda/handlertrace/trace.go @@ -26,20 +26,19 @@ func callbackCompose(f1, f2 func(context.Context, interface{})) func(context.Con type handlerTraceKey struct{} -// WithHandlerTrace adds callbacks to the provided context which allows handlers -// which wrap the return value of lambda.NewHandler to access to the request and +// NewContext adds callbacks to the provided context which allows handlers which +// wrap the return value of lambda.NewHandler to access to the request and // response events. -func WithHandlerTrace(ctx context.Context, trace HandlerTrace) context.Context { - existing := ContextHandlerTrace(ctx) +func NewContext(ctx context.Context, trace HandlerTrace) context.Context { + existing := FromContext(ctx) return context.WithValue(ctx, handlerTraceKey{}, HandlerTrace{ RequestEvent: callbackCompose(existing.RequestEvent, trace.RequestEvent), ResponseEvent: callbackCompose(existing.ResponseEvent, trace.ResponseEvent), }) } -// ContextHandlerTrace returns the HandlerTrace associated with the provided -// context. -func ContextHandlerTrace(ctx context.Context) HandlerTrace { +// FromContext returns the HandlerTrace associated with the provided context. +func FromContext(ctx context.Context) HandlerTrace { trace, _ := ctx.Value(handlerTraceKey{}).(HandlerTrace) return trace }