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  }