Thundra

Thundra: Serverless Observability for AWS Lambda

The black box nature of AWS Lambda and other serverless environments means that identifying and fixing performance issues is difficult and time-consuming. Built for straightforward debugging, monitoring, and observability, Thundra provides deep insight into your entire serverless environment. Thundra collects and correlates all your metrics, logs, and traces, allowing you to quickly identify problematic invocations and also analyzes external services associated with that function. With Thundra’s zero overhead and automated instrumentation capabilities, your developers are free to write code without worrying about bulking up their Lambdas or wasting time on chasing black box problems.

Get Started    Discussions

Trace Support

By instrumenting your functions, you shall enable detailed trace capabilities using Thundra. This will provide great insights into the behavior of your Lambda functions on each invocation made. Those capabilities can be enabled through instrumenting your function using the OpenTracing API .

Manual Instrumentation with Open Tracing

Thundra uses the OpenTracing API to implement instrumentation. Thus, you can manually instrument your code by following OpenTracing API instructions as Thundra’s agents are compliant with the OpenTracing API.

To manually instrument your Go functions in order to see detailed spans, you will have to create span instances using the OpenTracinig's StartSpan methods. OpenTracing API provides two different methods: opentracing.StartSpan and opentracing.StartSpanFromContext that you can use to start new spans in your function. We suggest using opentracing.StartSpanFromContext method for creating new spans by passing it a context, an operation name, and optional options parameters. Refer the section about using contexts to learn about more.

You don't need to create a span for main handler function

Thundra automatically creates a root span representing the main Lambda handler function and finishes this root span when your Lambda function is done. Thus, you don't need to create a root span representing your handler function. Any other span you will create in your main handler or in other functions will be the child of this root span.

The following code snippet shows the example usage of OpenTracing API to create new spans.

// Pass a context parameter to the function
func sayHello(ctx context.Context, name string) {
	// Create a new span to represent operations made in this function
	span, _ := opentracing.StartSpanFromContext(ctx, "sayHello")
	// Finish the span when function is done
	defer span.Finish()
	// Perform actual function logic
	fmt.Printf("Hello, %s!\n", name)
}

Using contexts while creating new spans

Currently, Golang does not provide a thread local storage or a similar construct. For this reason, OpenTracing API handles parent-child relations between the spans using Golang's context objects.

Since Thundra creates a root span representing your main handler function, we strongly suggest you create your main handler function such that it accepts a context object as its first argument. This way you will be able to access the root span that Thundra have created to represent your main handler function and use the passed context variable to create child spans of this root span.

The following code snippet shows how context objects can be used to create child spans.

package main

import (
	"context"
	"time"

	"github.com/aws/aws-lambda-go/lambda"
	opentracing "github.com/opentracing/opentracing-go"
	"github.com/thundra-io/thundra-lambda-agent-go/thundra"
)

// Your main lambda handler
func handler(ctx context.Context) (string, error) {
	// Currently ctx object contains the root span that
	// Thundra have created for you

	// Sleep some amount, representing the operations
	// that you make in your handler function before
	// creating a new child span
	time.Sleep(time.Millisecond * 100)

	// Say you continue to make some operations in your
	// main handler but this time create a childSpan
	// to represent these operations.
	aSpan, _ := opentracing.StartSpanFromContext(ctx, "childSpan-1")
	time.Sleep(time.Millisecond * 100)
	aSpan.Finish()

	// Make a call to another function. Note that this function
	// also accepts a context object. We are using this context
	// object to carry the parent span information.
	aFunction(ctx)

	// Creating another span representing the another operations
	// that we are doing inside the root span.
	anotherSpan, _ := opentracing.StartSpanFromContext(ctx, "childSpan-3")
	time.Sleep(time.Millisecond * 100)
	anotherSpan.Finish()

	// Say we have also some operations before our handler finishes
	time.Sleep(time.Millisecond * 50)

	return "Hello ƛ!", nil
}

func aFunction(ctx context.Context) {
	span, _ := opentracing.StartSpanFromContext(ctx, "childSpan-2")
	defer span.Finish()

	time.Sleep(time.Millisecond * 100)
}

func main() {
	// Wrap your lambda handler with Thundra
	lambda.Start(thundra.Wrap(handler))
}

The resulting trace chart that you will see in Thundra web console for this function, will be like the following image.

As you can see, there is a root span that Thundra automatically created, and other spans are the child of the root span, as they should be.

Configuration

You can configure the trace plugin using the following environment variables.

thundra_agent_lambda_trace_disable

Set this true if you want to disable the trace plugin. By default, trace plugin is enabled.

thundra_agent_lambda_trace_request_skip

Set this true if you want to disable monitoring the Lambda request.

thundra_agent_lambda_trace_response_skip

Set this true if you want to disable monitoring the Lambda response.

See the configuration variables section, for all the configuration variables.