Trace AWS Lambda .NET Core functions with OpenTelemetry .NET
Preview
In Feb 2021, AWS announced Support for AWS Distro for OpenTelemetry .NET Tracing. For general information about AWS Distro for OpenTelemetry, see the AWS Distro for OpenTelemetry page.
For tracing AWS Lambda for other languages such as Java, Node.JS, and Python using the Dynatrace AWS Lambda Layer, see Deploy OneAgent as Lambda extension.
Prerequisites
The following prerequisites and limitations apply:
- Dynatrace version 1.222+
- W3C Trace Context is enabled
- From the Dynatrace menu, go to Settings > Preferences > OneAgent features.
- Turn on Send W3C Trace Context HTTP headers.
The OpenTelemetry Protocol (OTLP) exporters for .NET currently support gRPC and HTTP 1.1 with binary Protocol Buffers (Protobuf) payload transports. Supported corresponding protocol values are grpc
and http/protobuf
. Configuration options can be set either via environment variables or explicitly in code.
Instrument AWS Lambda .NET Core functions
Dynatrace uses OpenTelemetry trace ingest to provide end-to-end visibility to your AWS Lambda .NET Core functions.
To instrument your AWS Lambda .NET Core functions
Set up export
Add dependencies
Add OpenTelemetry Tracer
Set up export
To ingest gRPC via the Dynatrace Trace API, you need to use an OpenTelemetry collector between Dynatrace and the exporter. You can choose to either self-host a collector or use the AWS Distro for OpenTelemetry Collector (ADOT Collector).
If you use environment variables for setup, you need to set the following value:
- For
OTEL_EXPORTER_OTLP_PROTOCOL
:grpc
Add the ARN of the Lambda Layer ADOT Collector
Lambda layers are a rationalized resource, meaning that they can only be used in the AWS region in which they are published. Make sure to use the layer in the same region as your Lambda functions.
Collector layer: aws-otel-collector-ver-0-27-0
.
For a complete list of the AWS-managed OpenTelemetry Lambda layers, see AWS Distro for OpenTelemetry - AWS Lambda respository
Lambda layer ARN format is:
arn:aws:lambda:\<region>:901920570463:layer:<layer>:1
Configure ADOT Collector
The configuration of the ADOT Collector follows the OpenTelemetry standard.
By default, the ADOT Lambda layer uses the config.yaml
file, which exports OpenTelemetry data to AWS X-Ray. To export the data to Dynatrace, you need to customize the configuration using the OpenTelemetry OTLP exporter.
To customize the collector configuration, add a configuration YAML file to your function code. After the file has been deployed with a Lambda function, create environment variable OPENTELEMETRY_COLLECTOR_CONFIG_FILE
on your Lambda function and set it to /var/task/<path>/<to>/<filename>
. This tells the extension where to find the collector configuration.
Here is a sample configuration file, collector.yaml
, in the root directory:
- Copy
collector.yaml
in the root directory - Set an environment variable
OPENTELEMETRY_COLLECTOR_CONFIG_FILE
to/var/task/<path>/<to>/<file>
receivers:
otlp:
protocols:
grpc:
exporters:
otlphttp:
endpoint: "https://<YOUR-TENANT-ID>.live.dynatrace.com/api/v2/otlp"
headers: {"Authorization": "Api-Token <YOUR-DYNATRACE-API-TOKEN>"}
service:
pipelines:
traces:
receivers: [otlp]
exporters: [otlphttp]
For further details on configuration, see Send OpenTelemetry trace data to Dynatrace.
You can set this via the Lambda console or the AWS CLI. With the CLI, use the following command:
aws lambda update-function-configuration --function-name Function --environment Variables={OPENTELEMETRY_COLLECTOR_CONFIG_FILE=/var/task/collector.yaml}
You can also configure environment variables via CloudFormation template:
Function:
Type: AWS::Serverless::Function
Properties:
...
Environment:
Variables:
OPENTELEMETRY_COLLECTOR_CONFIG_FILE: /var/task/collector.yaml
To ingest HTTP via the Dynatrace Trace API, you need to configure the exporter. The exporter will then directly send traces to the configured endpoint.
If you use environment variables for setup, you need to set the following values:
-
For
OTEL_EXPORTER_OTLP_PROTOCOL
:http/protobuf
-
For
OTEL_EXPORTER_OTLP_ENDPOINT
: the URL for export endpoint- If you set the endpoint URL via environment variables, the export endpoints for traces and metrics are automatically appended by
v1/traces
for traces andv1/metrics
for metrics. For example, if the endpoint is set tohttps://<YOUR-TENANT-ID>.live.dynatrace.com/api/v2/otlp
, traces will be exported tohttps://<YOUR-TENANT-ID>.live.dynatrace.com/api/v2/otlp/v1/traces
. - If you set the endpoint explicitly in code, it will be used as is.
For details, see Endpoint URLs for OTLP/HTTP.
- If you set the endpoint URL via environment variables, the export endpoints for traces and metrics are automatically appended by
-
For
OTEL_EXPORTER_OTLP_HEADERS
: the authorization API token value in the following format:Authorization=Api-Token <TOKEN>
.
Add dependencies
Add the following dependencies via NuGet to your project:
OpenTelemetry.Exporter.OpenTelemetryProtocol
If you are using the AWS SDK to interact with other AWS services, you can add auto-instrumentation using the ADOT SDK for .NET
OpenTelemetry.Contrib.Instrumentation.AWS
OpenTelemetry also provides other auto-instrumentation libraries available as NuGet packages
Add OpenTelemetry Tracer
The AWS Distro for OpenTelemetry doesn't provide a wrapper layer for .NET as it does for other languages. You need to add a tracer to your code and create a "root span."
The following sample uses an AWS Lambda Proxy Integration and gRPC transport.
If you don't set the Protocol
property of the OtlpExporterOptions
class via environment variables or in code, it will be initialized as OtlpExportProtocol.Grpc
by default.
public class Functions
{
public Functions() {}
//Defines the OpenTelemetry resource attribute "service.name" which is mandatory
private const string servicename = "AWS Lambda";
//Defines the OpenTelemetry Instrumentation Scope.
private const string activitySource = "MyCompany.MyProduct.MyLibrary";
//Provides the API for starting/stopping activities.
private static readonly ActivitySource MyActivitySource = new ActivitySource(activitySource);
public async Task<APIGatewayProxyResponse> Get(APIGatewayProxyRequest request, ILambdaContext context)
{
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport",true);
//Initialize OpenTelemetry Tracer
using (Sdk.CreateTracerProviderBuilder()
.SetSampler(new AlwaysOnSampler())
.AddSource(activitySource)
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(servicename))
.AddAWSInstrumentation() //Add auto-instrumentation for AWS SDK
.AddHttpClientInstrumentation() //Add auto-instrumentation for AWS SDK
.AddOtlpExporter(otlpOptions =>
{
//Use a local endpoint for AWS Lambda ADOT Collector Layer
//or an endpoint configured via environment variable.
var collectorUrl = Environment.GetEnvironmentVariable("COLLECTOR_URL") ?? "http://localhost:55680";
otlpOptions.Endpoint = new Uri(collectorUrl);
})
.Build())
{
//create root-span, connecting with trace-parent read from the http-header
using (var activity = MyActivitySource.StartActivity("Invoke", ActivityKind.Server, request.Headers["traceparent"]))
{
//.....
//... YOUR CODE GOES HERE
//....
}
}
}
}
The code sample using HTTP exporter is similar to the gRPC exporter sample; the only difference is in the configuration of OtlpExporterOptions
:
//Initialize OpenTelemetry Tracer
using (Sdk.CreateTracerProviderBuilder()
// ... other initialization code (see code snippet for the gRPC case)
.AddOtlpExporter(otlpOptions =>
{
otlpOptions.Protocol = OtlpExportProtocol.HttpProtobuf;
otlpOptions.Headers = "Authorization=Api-Token <TOKEN>";
//Use an explicitly set endpoint for export
//or an endpoint configured via environment variable.
otlpOptions.Endpoint = new Uri("https://<YOUR-TENANT-ID>.live.dynatrace.com/api/v2/otlp");
})
.Build())
{
// ... span creation code and your code goes here (see code snippet for the gRPC case)
}
If configuration is done via environment variables, the code for adding an OTLP/HTTP exporter looks even simpler:
//Initialize OpenTelemetry Tracer
using (Sdk.CreateTracerProviderBuilder()
// ... other initialization code (see code snippet for the gRPC case)
.AddOtlpExporter()
.Build())
{
// ... span creation code and your code goes here (see code snippet for the gRPC case)
}