Docs » Instrument applications for Splunk APM » Export spans from an AWS Lambda function

Export spans from an AWS Lambda function 🔗

Important

The original µAPM product, released in 2019, is now called µAPM Previous Generation (µAPM PG). Wherever you see the reference, µAPM now refers to the product released on March 31, 2020.

If you’re using µAPM Previous Generation (µAPM PG), see Overview of SignalFx Microservices APM Previous Generation (µAPM PG).

Instrument Java, Node.js, Python, Go, and Ruby AWS Lambda functions to send traces to Splunk APM. This enables you to inspect the performance and functionality of your serverless applications.

If your application architecture includes multiple Lambda functions, you can visualize each one as an independent service in APM. To do so, specify a service name when instrumenting a Lambda function. How you do this depends on the language your Lambda function uses. If you want to represent multiple Lambda functions as a single service, assign each function the same service name.

You can instrument these languages with SignalFx Lambda wrappers or instrumentation libraries:

Export traces to an ingest endpoint or OpenTelemetry Collector. How you do this depends on the language the Lambda function uses and how you implement instrumentation.

If you export traces from a Lambda function to an OpenTelemetry Collector that processes and routes traces to an ingest endpoint, you may experience lower latency compared to when you export traces directly to an ingest endpoint. If you deploy an OpenTelemetry Collector, you have to deploy it in a VPC that Lambda functions have access to. For information about deploying an OpenTelemetry Collector, see Deploy an OpenTelemetry Collector for Splunk APM.

Instrument Lambda functions 🔗

The SignalFx Lambda Wrapper for Node.js and Python automatically instruments supported libraries. The SignalFx Lambda Wrapper for Ruby only instruments the Lambda function, and doesn’t instrument any supported libraries. The SignalFx Tracing Library for Go helps you configure custom instrumentation with the OpenTracing API. If you want to instrument anything other than just the Lambda function for Python, Go, or Ruby, you can configure instrumentation manually.

To collect traces from a Lambda function, follow these steps:

  1. Install and configure a SignalFx Lambda wrapper or instrumentation library according to the Lambda function’s language:
  1. Depending on the language, you may have to modify your application code to capture and export traces from your Lambda function. These code additions enable the instrumentation library to work with Lambda functions, or in the case of Go to configure instrumentation manually.

    Here are code snippets for each supported language you have to manually add code for. Some snippets have instructions in comments.

    Node.js asynchronous:

    const sfx = require("signalfx-lambda");
    const axios = require("axios");
    
    exports.lambdaHandler = sfx.asyncWrapperTracing(async (event, context) => {
      const response = await axios("http://localhost:55679/");
      return {
        statusCode: 200,
        headers: { "content-type": "text/html" },
        body: response.data,
      };
    });
    

    Node.js callback:

    const sfx = require("signalfx-lambda");
    const axios = require("axios");
    
    exports.lambdaHandler = sfx.wrapperTracing(event, context, callback) => {
      axios("http://example:55679/debug/rpcz")
        .then((ret) => {
            callback(null, {
                statusCode: 200,
                headers: { "content-type": "text/html" },
                body: ret.data,
            });
        })
        .catch((err) => {
          callback(err);
        });
    };
    

    Python:

    import json
    import signalfx_lambda
    
    @signalfx_lambda.is_traced()
    def lambda_handler(event, context):
        return {
            "statusCode": 200,
            "body": json.dumps({
                "message": "hello world",
            }),
        }
    

    Go:

    package main
    
    import (
       "fmt"
    
       "github.com/aws/aws-lambda-go/events"
       "github.com/aws/aws-lambda-go/lambda"
       sfxtracing "github.com/signalfx/signalfx-go-tracing/tracing"
    )
    
    // >>>> instrumentation code starts
    
    // Step 1:
    // env var SIGNALFX_ENDPOINT_URL should specify
    // https://ingest.REALM.signalfx/com/v2/trace for an ingest endpoint or
    // <collector_url>:9411/api/v2/spans for an OpenTelemetry Collector
    
    // Step 2:
    // Run `go mod tidy` on the project to add dependencies to go.mod
    // after making these change, add the handler code.
    
    var tracerInitialized bool
    
    type lambdaHandler func(events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error)
    
    func initTracer() {
       if tracerInitialized {
          return
       }
       sfxtracing.Start()
       tracerInitialized = true
    }
    
    func traceWrapper(handler lambdaHandler) lambdaHandler {
       return func(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
          initTracer()
          span := tracer.StartSpan("lambda-span-name")
          span.SetTag("http.path", request.Path)
          span.SetTag("http.method", request.HTTPMethod)
    
          response, err := handler(request)
          if err != nil {
             span.SetTag("error", err)
             span.SetTag("http.status_code", "500")
          } else {
             span.SetTag("http.status_code", response.StatusCode)
          }
    
          span.Finish()
          tracer.ForceFlush()
          return response, err
       }
    }
    // <<<< instrumentation code ends
    
    // our original handler remains unchanged.
    func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
       return events.APIGatewayProxyResponse{
          Body:       fmt.Sprintf(`{"message": "Hello World"}`),
          StatusCode: 200,
       }, nil
    }
    
    func main() {
       // now we wrap handler in traceWrapper
       lambda.Start(traceWrapper(handler))
    }
    

    Ruby

    require 'json'
    require 'signalfx/lambda'
    
    # Change the value of the Handler or EntryPoint
    # to <source>.SignalFx::Lambda.wrapped_handler
    # where source is the source file of your Lambda function.
    # For example, if it was "app.lambda_handler", change it
    # to "app.SignalFx::Lambda.wrapped_handler"
    
    # Rename the handler:
    def handler(event:, context:)
    {
       statusCode: 200,
       body: {
          message: "Hello World!",
       }.to_json
    }
    end
    
    # Add this call:
    SignalFx::Lambda.register_handler(metrics: true, tracing: true, &method(:handler))