Setting up native tracing for Go-based AWS Lambda functions

You can set up native tracing for AWS Lambda functions written in Go by installing and configuring Instana Go Tracer.

Supported runtimes

Instana supports native tracing for go1.x runtime that uses Go 1.8 or later.

Installing Go Tracer

You need to install Instana Go Tracer to collect tracing data from the AWS Lambda functions.

Before you install the Go Tracer, ensure that the Instana AWS sensor for Lambda monitoring is configured to collect versions and runtime metrics from AWS Lambda. For more information, see AWS sensor for Lambda monitoring.

To install the Instana Go Tracer, complete the following steps:

  1. Download the Go Tracer module:

    go get github.com/instana/go-sensor
    
  2. Add the Go Tracer module to your go.mod file.

Updating Go Tracer

Ensure that you use the latest version of the tracer.

To make sure that you are using the latest version of the Go Tracer, check the go.mod file in your project or run the following command:

go get github.com/instana/go-sensor@latest

If you do not have the latest Go Tracer, download the latest version and update the version in the go.mod file.

Configuring the AWS Lambda function

After you install the Instana Go Tracer, it automatically detects that a service is running on AWS Lambda and switches to serverless mode. Instead of sending collected traces to the host agent, the tracer submits them directly to the Instana serverless acceptor endpoint in the Instana backend.

To send collected traces to the Instana backend, set the following environment variables to your AWS Lambda function:

  • **INSTANA_ENDPOINT_URL**
  • **INSTANA_AGENT_KEY**

To provide these variables to the AWS handler in the AWS Console UI, complete the following steps:

  1. On the "AWS Lambda configuration" page, click your Lambda function.

  2. In the Environment variables section, click Edit and add the following variables:

    configuring environment variables

You can use optional environment variables to change the default settings in the tracer, such as the list of HTTP headers to collect or a custom service name to use.

Enabling tracing

With the aws-lambda-go package, you can run the Go code on AWS Lambda.

To trace AWS Lambda, trigger events, and monitor internal and external calls that are made within the AWS handler function by using Instana, you must instrument your handler code first.

You can instrument your handler code by using the middleware wrappers that are provided by the Instana instrumentation module instalambda.

Adding the Instana instrumentation module

To add the instalambda module to your Go Lambda project, run the following command from the folder that contains the go.mod file:

go get github.com/instana/go-sensor/instrumentation/instalambda

This command adds the instrumentation module to your project dependencies list and to the main Go Tracer.

Instrumenting a handler function

The following code snippet shows a typical AWS Lambda function that is written in Go:

package main

import (
    "github.com/aws/aws-lambda-go/lambda"
)

func main() {
    lambda.Start(Handle)
}

func Handle() (string, error) {
    // handler code
}

A handler function can take and return up to two arguments, with a limitation that if the handler function takes two arguments, the first argument must implement context.Context.

To instrument a handler function, complete the following steps:

  1. Create an instrumented handler from the original handler function by using instalambda.NewHandler().
  2. Pass the instrumented handler to lambda.StartHandler(). The following snippet shows the code changes:
package main

import (
    "github.com/aws/aws-lambda-go/lambda"

    // Import the in-process sensor and instrumentation packages
    instana "github.com/instana/go-sensor"
    "github.com/instana/go-sensor/instrumentation/instalambda"
)

func main() {
    // Initialize the instana.Sensor instance
    sensor := instana.NewSensor("my-lambda-handler")

    // Create an instrumented handler from your handler function
    h := instalambda.NewHandler(Handle, sensor)

    // Pass the handler to the lambda.StartHandler() invoke loop
    lambda.StartHandler(h)
}

func Handle() (string, error) {
    // handler code
}

Instrumenting a lambda.Handler

The following code snippet shows a typical AWS Lambda handler that is implemented as lambda.Handler:

package main

import (
    "github.com/aws/aws-lambda-go/lambda"
)

func main() {
    h := &Handler{
        // handler configuration
    }

    lambda.StartHandler(h, sensor)
}

type Handler struct {
    // ...
}

func (h *Handler) Invoke(ctx context.Context, payload []byte) ([]byte, error) {
    // handler code
}

To instrument such a handler, wrap it with instalambda.WrapHandler() and pass to labmda.StartHandler():

package main

import (
    "github.com/aws/aws-lambda-go/lambda"

    // Import the in-process sensor and instrumentation packages
    instana "github.com/instana/go-sensor"
    "github.com/instana/go-sensor/instrumentation/instalambda"
)

func main() {
    // Initialize the instana.Sensor instance
    sensor := instana.NewSensor("my-lambda-handler")

    h := &Handler{
        // handler configuration
    }

    // Wrap and pass the handler to the lambda.StartHandler() invoke loop
    lambda.StartHandler(instalambda.WrapHandler(h, sensor))
}

type Handler struct {
    // ...
}

func (h *Handler) Invoke(ctx context.Context, payload []byte) ([]byte, error) {
    // handler code
}

Enabling trace context propagation

To enable trace context propagation, you need minimal instrumentation that involves making a few changes in your main() function without updating your handler code. However, you can add context.Context to the list of arguments of your handler function. When you add context.Context as an argument, instalambda injects the spans of the Lambda trigger event into the context. You can retrieve these spans with instana.SpanFromContext() and use them as a parent to trace internal and external calls that are made within the handler:

func MyHandler(ctx context.Context) error {
    // Pass the handler context to a subcall to trace its execution
    subCall(ctx)

    // ...

    // Propagate the trace context within an HTTP request to another service monitored with Instana
    // using an instrumented http.Client
    req, err := http.NewRequest("GET", url, nil)
    client := &http.Client{
        Transport: instana.RoundTripper(sensor, nil),
    }

    client.Do(req.WithContext(ctx))

    // ...
}

func subCall(ctx context.Context) {
    if parent, ok := instana.SpanFromContext(ctx); ok {
        // start a new span, using the Lambda entry span as a parent
        sp = parent.Tracer().StartSpan(/* ... */)
        defer sp.Finish()
    }

    // ...
}