diff --git a/dynamodb_handler.go b/dynamodb_handler.go new file mode 100644 index 0000000..ce34b29 --- /dev/null +++ b/dynamodb_handler.go @@ -0,0 +1,67 @@ +package g8 + +import ( + "context" + + "github.com/aws/aws-lambda-go/events" + "github.com/aws/aws-lambda-go/lambda" + "github.com/google/uuid" + newrelic "github.com/newrelic/go-agent" + "github.com/newrelic/go-agent/_integrations/nrlambda" + "github.com/rs/zerolog" +) + +type DynamoDbContext struct { + Context context.Context + EventRecord events.DynamoDBEventRecord + Logger zerolog.Logger + NewRelicTx newrelic.Transaction + CorrelationID string +} + +type DynamoHandlerFunc func(c *DynamoDbContext) error + +func DynamoDbHandler(h DynamoHandlerFunc, conf HandlerConfig) func(context.Context, events.DynamoDBEvent) error { + return func(ctx context.Context, e events.DynamoDBEvent) error { + for _, record := range e.Records { + correlationID := uuid.New().String() + + logger := configureLogger(conf). + Str("dynamodb_event_source", record.EventSource). + Str("correlation_id", correlationID). + Logger() + + c := &DynamoDbContext{ + Context: ctx, + EventRecord: record, + Logger: logger, + NewRelicTx: newrelic.FromContext(ctx), + CorrelationID: correlationID, + } + + c.AddNewRelicAttribute("functionName", conf.FunctionName) + c.AddNewRelicAttribute("buildVersion", conf.BuildVersion) + c.AddNewRelicAttribute("correlationID", correlationID) + c.AddNewRelicAttribute("dynamoDBEventSource", record.EventSource) + + if err := h(c); err != nil { + logUnhandledError(c.Logger, err) + return err + } + } + return nil + } +} + +func DynamoDbHandlerWithNewRelic(h DynamoHandlerFunc, conf HandlerConfig) lambda.Handler { + return nrlambda.Wrap(DynamoDbHandler(h, conf), conf.NewRelicApp) +} + +func (c *DynamoDbContext) AddNewRelicAttribute(key string, val interface{}) { + if c.NewRelicTx == nil { + return + } + if err := c.NewRelicTx.AddAttribute(key, val); err != nil { + c.Logger.Error().Msgf("failed to add attr '%s' to new relic tx: %+v", key, err) + } +} diff --git a/dynamodb_handler_test.go b/dynamodb_handler_test.go new file mode 100644 index 0000000..930b8f4 --- /dev/null +++ b/dynamodb_handler_test.go @@ -0,0 +1,91 @@ +package g8_test + +import ( + "context" + "fmt" + "io/ioutil" + "testing" + + "github.com/aws/aws-lambda-go/events" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + + "github.com/JSainsburyPLC/g8" +) + +func TestDynamoDbHandler_SingleMessage(t *testing.T) { + timesCalled := 0 + h := g8.DynamoDbHandler(func(c *g8.DynamoDbContext) error { + timesCalled++ + + assert.Equal(t, "event1", c.EventRecord.EventName) + assert.IsType(t, events.DynamoDBStreamRecord{}, c.EventRecord.Change) + assert.NotEmpty(t, c.CorrelationID) + + return nil + }, g8.HandlerConfig{Logger: zerolog.New(ioutil.Discard)}) + + err := h(context.Background(), events.DynamoDBEvent{ + Records: []events.DynamoDBEventRecord{ + { + EventName: "event1", + Change: events.DynamoDBStreamRecord{}, + }, + }, + }) + + assert.Nil(t, err) + assert.Equal(t, 1, timesCalled) +} + +func TestDynamoDbHandler_MultipleMessages(t *testing.T) { + timesCalled := 0 + h := g8.DynamoDbHandler(func(c *g8.DynamoDbContext) error { + timesCalled++ + + assert.Equal(t, fmt.Sprintf("event-%d", timesCalled), c.EventRecord.EventName) + assert.NotEmpty(t, c.CorrelationID) + assert.IsType(t, events.DynamoDBStreamRecord{}, c.EventRecord.Change) + + return nil + }, g8.HandlerConfig{Logger: zerolog.New(ioutil.Discard)}) + + err := h(context.Background(), events.DynamoDBEvent{ + Records: []events.DynamoDBEventRecord{ + { + EventName: "event-1", + Change: events.DynamoDBStreamRecord{}, + }, + { + EventName: "event-2", + Change: events.DynamoDBStreamRecord{}, + }, + }, + }) + + assert.Nil(t, err) + assert.Equal(t, 2, timesCalled) +} + +func TestDynamoDbHandler_HandlerError(t *testing.T) { + timesCalled := 0 + handlerFunc := func(c *g8.DynamoDbContext) error { + timesCalled++ + return assert.AnError + } + + h := g8.DynamoDbHandler(handlerFunc, g8.HandlerConfig{ + Logger: zerolog.New(ioutil.Discard), + }) + err := h(context.Background(), events.DynamoDBEvent{ + Records: []events.DynamoDBEventRecord{ + { + EventName: "event-1", + Change: events.DynamoDBStreamRecord{}, + }, + }, + }) + + assert.Equal(t, assert.AnError, err) + assert.Equal(t, 1, timesCalled) +}