git.colasdn.top/newrelic/go-agent@v3.26.0+incompatible/_integrations/nrlambda/handler.go (about) 1 // Copyright 2020 New Relic Corporation. All rights reserved. 2 // SPDX-License-Identifier: Apache-2.0 3 4 // Package nrlambda adds support for AWS Lambda. 5 // 6 // Use this package to instrument your AWS Lambda handler function. Data is 7 // sent to CloudWatch when the Lambda is invoked. CloudWatch collects Lambda 8 // log data and sends it to a New Relic log-ingestion Lambda. The log-ingestion 9 // Lambda sends that data to us. 10 // 11 // Monitoring AWS Lambda requires several steps shown here: 12 // https://docs.newrelic.com/docs/serverless-function-monitoring/aws-lambda-monitoring/get-started/enable-new-relic-monitoring-aws-lambda 13 // 14 // Example: https://github.com/newrelic/go-agent/tree/master/_integrations/nrlambda/example/main.go 15 package nrlambda 16 17 import ( 18 "context" 19 "io" 20 "net/http" 21 "os" 22 "sync" 23 24 "github.com/aws/aws-lambda-go/lambda" 25 "github.com/aws/aws-lambda-go/lambda/handlertrace" 26 "github.com/aws/aws-lambda-go/lambdacontext" 27 newrelic "github.com/newrelic/go-agent" 28 "github.com/newrelic/go-agent/internal" 29 "github.com/newrelic/go-agent/internal/integrationsupport" 30 ) 31 32 type response struct { 33 header http.Header 34 code int 35 } 36 37 var _ http.ResponseWriter = &response{} 38 39 func (r *response) Header() http.Header { return r.header } 40 func (r *response) Write([]byte) (int, error) { return 0, nil } 41 func (r *response) WriteHeader(int) {} 42 43 func requestEvent(ctx context.Context, event interface{}) { 44 txn := newrelic.FromContext(ctx) 45 46 if nil == txn { 47 return 48 } 49 50 if sourceARN := getEventSourceARN(event); "" != sourceARN { 51 integrationsupport.AddAgentAttribute(txn, internal.AttributeAWSLambdaEventSourceARN, sourceARN, nil) 52 } 53 54 if request := eventWebRequest(event); nil != request { 55 txn.SetWebRequest(request) 56 } 57 } 58 59 func responseEvent(ctx context.Context, event interface{}) { 60 txn := newrelic.FromContext(ctx) 61 if nil == txn { 62 return 63 } 64 if rw := eventResponse(event); nil != rw && 0 != rw.code { 65 txn.SetWebResponse(rw) 66 txn.WriteHeader(rw.code) 67 } 68 } 69 70 func (h *wrappedHandler) Invoke(ctx context.Context, payload []byte) ([]byte, error) { 71 var arn, requestID string 72 if lctx, ok := lambdacontext.FromContext(ctx); ok { 73 arn = lctx.InvokedFunctionArn 74 requestID = lctx.AwsRequestID 75 } 76 77 defer internal.ServerlessWrite(h.app, arn, h.writer) 78 79 txn := h.app.StartTransaction(h.functionName, nil, nil) 80 defer txn.End() 81 82 integrationsupport.AddAgentAttribute(txn, internal.AttributeAWSRequestID, requestID, nil) 83 integrationsupport.AddAgentAttribute(txn, internal.AttributeAWSLambdaARN, arn, nil) 84 h.firstTransaction.Do(func() { 85 integrationsupport.AddAgentAttribute(txn, internal.AttributeAWSLambdaColdStart, "", true) 86 }) 87 88 ctx = newrelic.NewContext(ctx, txn) 89 ctx = handlertrace.NewContext(ctx, handlertrace.HandlerTrace{ 90 RequestEvent: requestEvent, 91 ResponseEvent: responseEvent, 92 }) 93 94 response, err := h.original.Invoke(ctx, payload) 95 96 if nil != err { 97 txn.NoticeError(err) 98 } 99 100 return response, err 101 } 102 103 type wrappedHandler struct { 104 original lambda.Handler 105 app newrelic.Application 106 // functionName is copied from lambdacontext.FunctionName for 107 // deterministic tests that don't depend on environment variables. 108 functionName string 109 // Although we are told that each Lambda will only handle one request at 110 // a time, we use a synchronization primitive to determine if this is 111 // the first transaction for defensiveness in case of future changes. 112 firstTransaction sync.Once 113 // writer is used to log the data JSON at the end of each transaction. 114 // This field exists (rather than hardcoded os.Stdout) for testing. 115 writer io.Writer 116 } 117 118 // WrapHandler wraps the provided handler and returns a new handler with 119 // instrumentation. StartHandler should generally be used in place of 120 // WrapHandler: this function is exposed for consumers who are chaining 121 // middlewares. 122 func WrapHandler(handler lambda.Handler, app newrelic.Application) lambda.Handler { 123 if nil == app { 124 return handler 125 } 126 return &wrappedHandler{ 127 original: handler, 128 app: app, 129 functionName: lambdacontext.FunctionName, 130 writer: os.Stdout, 131 } 132 } 133 134 // Wrap wraps the provided handler and returns a new handler with 135 // instrumentation. Start should generally be used in place of Wrap. 136 func Wrap(handler interface{}, app newrelic.Application) lambda.Handler { 137 return WrapHandler(lambda.NewHandler(handler), app) 138 } 139 140 // Start should be used in place of lambda.Start. Replace: 141 // 142 // lambda.Start(myhandler) 143 // 144 // With: 145 // 146 // nrlambda.Start(myhandler, app) 147 // 148 func Start(handler interface{}, app newrelic.Application) { 149 lambda.StartHandler(Wrap(handler, app)) 150 } 151 152 // StartHandler should be used in place of lambda.StartHandler. Replace: 153 // 154 // lambda.StartHandler(myhandler) 155 // 156 // With: 157 // 158 // nrlambda.StartHandler(myhandler, app) 159 // 160 func StartHandler(handler lambda.Handler, app newrelic.Application) { 161 lambda.StartHandler(WrapHandler(handler, app)) 162 }