Skip to content

Commit dcf76fe

Browse files
willnewrelicbmoffatt
willnewrelic
authored andcommitted
Introduce HandlerTrace callbacks (#141)
* Introduce HandlerTrace * Move handler trace logic into handlertrace package * Address review feedback: Improve handlertrace names
1 parent 527f5d3 commit dcf76fe

File tree

3 files changed

+118
-1
lines changed

3 files changed

+118
-1
lines changed

lambda/handler.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"encoding/json"
88
"fmt"
99
"reflect"
10+
11+
"github.com/aws/aws-lambda-go/lambda/handlertrace"
1012
)
1113

1214
type Handler interface {
@@ -95,6 +97,9 @@ func NewHandler(handlerFunc interface{}) Handler {
9597
}
9698

9799
return lambdaHandler(func(ctx context.Context, payload []byte) (interface{}, error) {
100+
101+
trace := handlertrace.FromContext(ctx)
102+
98103
// construct arguments
99104
var args []reflect.Value
100105
if takesContext {
@@ -107,7 +112,9 @@ func NewHandler(handlerFunc interface{}) Handler {
107112
if err := json.Unmarshal(payload, event.Interface()); err != nil {
108113
return nil, err
109114
}
110-
115+
if nil != trace.RequestEvent {
116+
trace.RequestEvent(ctx, event.Elem().Interface())
117+
}
111118
args = append(args, event.Elem())
112119
}
113120

@@ -123,6 +130,10 @@ func NewHandler(handlerFunc interface{}) Handler {
123130
var val interface{}
124131
if len(response) > 1 {
125132
val = response[0].Interface()
133+
134+
if nil != trace.ResponseEvent {
135+
trace.ResponseEvent(ctx, val)
136+
}
126137
}
127138

128139
return val, err

lambda/handler_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"testing"
1010

11+
"github.com/aws/aws-lambda-go/lambda/handlertrace"
1112
"github.com/stretchr/testify/assert"
1213
)
1314

@@ -206,3 +207,64 @@ func TestInvalidJsonInput(t *testing.T) {
206207
assert.Equal(t, "unexpected end of JSON input", err.Error())
207208

208209
}
210+
211+
func TestHandlerTrace(t *testing.T) {
212+
handler := NewHandler(func(ctx context.Context, x int) (int, error) {
213+
if x != 123 {
214+
t.Error(x)
215+
}
216+
return 456, nil
217+
})
218+
requestHistory := ""
219+
responseHistory := ""
220+
checkInt := func(e interface{}, expected int) {
221+
nt, ok := e.(int)
222+
if !ok {
223+
t.Error("not int as expected", e)
224+
return
225+
}
226+
if nt != expected {
227+
t.Error("unexpected value", nt, expected)
228+
}
229+
}
230+
ctx := context.Background()
231+
ctx = handlertrace.NewContext(ctx, handlertrace.HandlerTrace{}) // empty HandlerTrace
232+
ctx = handlertrace.NewContext(ctx, handlertrace.HandlerTrace{ // with RequestEvent
233+
RequestEvent: func(c context.Context, e interface{}) {
234+
requestHistory += "A"
235+
checkInt(e, 123)
236+
},
237+
})
238+
ctx = handlertrace.NewContext(ctx, handlertrace.HandlerTrace{ // with ResponseEvent
239+
ResponseEvent: func(c context.Context, e interface{}) {
240+
responseHistory += "X"
241+
checkInt(e, 456)
242+
},
243+
})
244+
ctx = handlertrace.NewContext(ctx, handlertrace.HandlerTrace{ // with RequestEvent and ResponseEvent
245+
RequestEvent: func(c context.Context, e interface{}) {
246+
requestHistory += "B"
247+
checkInt(e, 123)
248+
},
249+
ResponseEvent: func(c context.Context, e interface{}) {
250+
responseHistory += "Y"
251+
checkInt(e, 456)
252+
},
253+
})
254+
ctx = handlertrace.NewContext(ctx, handlertrace.HandlerTrace{}) // empty HandlerTrace
255+
256+
payload := []byte(`123`)
257+
js, err := handler.Invoke(ctx, payload)
258+
if err != nil {
259+
t.Error("unexpected handler error", err)
260+
}
261+
if string(js) != "456" {
262+
t.Error("unexpected handler output", string(js))
263+
}
264+
if requestHistory != "AB" {
265+
t.Error("request callbacks not called as expected", requestHistory)
266+
}
267+
if responseHistory != "XY" {
268+
t.Error("response callbacks not called as expected", responseHistory)
269+
}
270+
}

lambda/handlertrace/trace.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Package handlertrace allows middleware authors using lambda.NewHandler to
2+
// instrument request and response events.
3+
package handlertrace
4+
5+
import (
6+
"context"
7+
)
8+
9+
// HandlerTrace allows handlers which wrap the return value of lambda.NewHandler
10+
// to access to the request and response events.
11+
type HandlerTrace struct {
12+
RequestEvent func(context.Context, interface{})
13+
ResponseEvent func(context.Context, interface{})
14+
}
15+
16+
func callbackCompose(f1, f2 func(context.Context, interface{})) func(context.Context, interface{}) {
17+
return func(ctx context.Context, event interface{}) {
18+
if nil != f1 {
19+
f1(ctx, event)
20+
}
21+
if nil != f2 {
22+
f2(ctx, event)
23+
}
24+
}
25+
}
26+
27+
type handlerTraceKey struct{}
28+
29+
// NewContext adds callbacks to the provided context which allows handlers which
30+
// wrap the return value of lambda.NewHandler to access to the request and
31+
// response events.
32+
func NewContext(ctx context.Context, trace HandlerTrace) context.Context {
33+
existing := FromContext(ctx)
34+
return context.WithValue(ctx, handlerTraceKey{}, HandlerTrace{
35+
RequestEvent: callbackCompose(existing.RequestEvent, trace.RequestEvent),
36+
ResponseEvent: callbackCompose(existing.ResponseEvent, trace.ResponseEvent),
37+
})
38+
}
39+
40+
// FromContext returns the HandlerTrace associated with the provided context.
41+
func FromContext(ctx context.Context) HandlerTrace {
42+
trace, _ := ctx.Value(handlerTraceKey{}).(HandlerTrace)
43+
return trace
44+
}

0 commit comments

Comments
 (0)