• Log in

Trace your .NET Lambda functions with New Relic and OpenTelemetry

PREVIEW

AWS Lambda with OpenTelemetry for .NET is still in development.

This guide will cover how you can use OpenTelemetry Lambda to trace your .NET Lambda functions using AWS's managed OpenTelemetry Lambda Layers. OpenTelemetry makes it easy to instrument your functions, including automatic instrumentation for many popular libraries.

Prerequisites

This guide assumes you have the following:

Step 1: Enable X-Ray

Even though we're not using AWS X-Ray in this setup, we still need to enable X-Ray in the Lambda function to enable the tracing features built into AWS OpenDistro.

To enable X-Ray:

  1. Open your function in the Lambda Console.
  2. Choose Configuration, then Monitoring Tools.
  3. Choose Edit.
  4. Under X-Ray, enable Active tracing.

Alternatively, if you're using SAM (Serverless Application Model) or CloudFormation templates, you can enable X-Ray by adding the following to your Lambda function's properties:

yourFunctionHere:
Type: AWS::Serverless::Function
Properties:
...
Policies:
- AWSLambdaBasicExecutionRole
...
- AWSXrayWriteOnlyAccess
Tracing: Active

Important

This option requires that you have AWSLambdaBasicExecutionRole or an equivalent policy attached to your function.

Step 2: Install the layer

AWS publishes a managed layer that includes the OpenTelemetry Lambda Collector.

To install it:

  1. Open your function in the Lambda Console.
  2. Under Layers in the Designer section, choose Add a layer.
  3. Under Specify an ARN, paste one of the layer ARNs for your function's architecture from the list below. Replace {region}with your AWS region, such as us-east-1.
  4. Choose Add.
  • AMD64 / X86_64: arn:aws:lambda:{region}:901920570463:layer:aws-otel-collector-amd64-ver-0-45-0:2
  • ARM64: arn:aws:lambda:\<region>:901920570463:layer:aws-otel-collector-arm64-ver-0-45-0:2

For SAM and CloudFormation templates, add the following to your function's properties. Be sure to replace your-license-key-here with your New Relic license key and otlp.nr-data.net:4317 with the New Relic OpenTelemetry Endpoint for your region.

yourFunctionHere:
Type: AWS::Serverless::Function
Properties:
...
Layers:
# Use this if using x86_64 architecture
- !Sub arn:${AWS::Partition}:lambda:${AWS::Region}:901920570463:layer:aws-otel-collector-amd64-ver-0-45-0:2
# Use this if using arm64 architecture
- !Sub arn:${AWS::Partition}:lambda:${AWS::Region}:901920570463:layer:aws-otel-collector-arm64-ver-0-45-0:2

Important

Refer to the latest ARNs published by AWS to verify the layer ARNs above are up to date.

Step 3: Add New Relic environment variables

To send the OpenTelemetry data that this layer collects to New Relic, we need to configure the OpenTelemetry Lambda Collector that is packaged with the layer to export the telemetry it receives to the New Relic OpenTelemetry Endpoint. Before we do that, we first need to set some environment variables that it will depend upon.

  1. Generate and copy a New Relic license key from your New Relic account.
  2. Open up your function in the Lambda Console.
  3. Choose Configuration and then Environment variables.
  4. Under Environment variables, choose Edit.
  5. Choose Add environment variable.
  6. For the Key set it to NEW_RELIC_LICENSE_KEY and set the Value to the value of the license key you generated in step 1. Then choose Save.
  7. Choose Add environment variable again.
  8. For the Key set it to NEW_RELIC_OPENTELEMETRY_ENDPOINT and set the Value to one of the options below (depends on your New Relic region). Then choose Save.
  9. Choose Add environment variable again.
  10. For the Key set it to OTEL_SERVICE_NAME and set the Value to your function's name. Then choose Save.
  • otlp.nr-data.net:4317: If your New Relic account is in the US region
  • otlp.eu01.nr-data.net:4317: If your New Relic account is in the EU region

For SAM and CloudFormation templates, add the following to your function's properties. Be sure to replace your-license-key-here with your New Relic license key and otlp.nr-data.net:4317 with the New Relic OpenTelemetry Endpoint for your region.

yourFunctionHere:
Type: AWS::Serverless::Function
Properties:
...
Environment:
Variables:
...
NEW_RELIC_LICENSE_KEY: your-license-key-here
NEW_RELIC_OPENTELEMETRY_ENDPOINT: otlp.nr-data.net:4317
OTEL_SERVICE_NAME: your-function-name-here

Important

Replace your-license-key-here with your New Relic License Key and otlp.nr-data.net:4317 with the endpoint appropriate for your New Relic region (see list above).

Step 4: Configure the collector

Now we will override the OpenTelemetry Lambda Collector's default configuration with one that exports telemetry to the New Relic OpenTelemetry Endpoint. To do this we must include a collector.yaml config file with our function.

Create a collector.yaml file in your function's root directory with the following contents:

receivers:
otlp:
protocols:
grpc:
http:
exporters:
otlp:
endpoint: ${NEW_RELIC_OPENTELEMETRY_ENDPOINT}
headers:
api-key: ${NEW_RELIC_LICENSE_KEY}
service:
pipelines:
traces:
receivers: [otlp]
exporters: [otlp]
metrics:
receivers: [otlp]
exporters: [otlp]
logs:
receivers: [otlp]
exporters: [otlp]

Bundle this collector.yaml file in the root directory of your function's zip package.

An example *.csproj config might look like this:

<ItemGroup>
<Content Include="collector.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>

Then re-deploy your function.

  1. Open your function in the Lambda Console.
  2. Choose Configuration and then Environment variables.
  3. Under Environment variables, choose Edit.
  4. Choose Add environment variable.
  5. For the Key set OPENTELEMETRY_COLLECTOR_CONFIG_FILE and set the Value to /var/task/collector.yaml.
  6. Then choose Save.

For SAM and CloudFormation templates, add this to your function's properties:

yourFunctionHere:
Type: AWS::Serverless::Function
Properties:
...
Environment:
Variables:
...
OPENTELEMETRY_COLLECTOR_CONFIG_FILE: /var/task/collector.yaml

Important

This assumes you bundled your collector.yaml in your function's root directory. If you bundled it somewhere else, replace /var/task/collector.yaml with the path to your collector.yaml.

Step 5: Instrument Your Function

First add the OpenTelemetry SDK for AWS Lambda.

dotnet add package OpenTelemetry.Contrib.Instrumentation.AWSLambda

Add a call to AddAWSLambdaConfigurations() from TracerProvider.

TracerProvider tracerProvider = Sdk.CreateTracerProviderBuilder()
// add other instrumentations here
.AddAWSLambdaConfigurations()
.Build();

Create a wrapper function with the same signature as the original Lambda handler function. Call AWSLambdaWrapper.Trace() API and pass TracerProvider, the original Lambda function, and its inputs as parameters.

// new Lambda function handler passed in
public string TracingFunctionHandler(JObject input, ILambdaContext context)
=> AWSLambdaWrapper.Trace(tracerProvider, OriginalFunctionHandler, input, context);
public string OriginalFunctionHandler(JObject input, ILambdaContext context)
{
return input?.ToString();
}

For example, a basic API Gateway Lambda function would look like this:

namespace Example
{
public class Function
{
public static TracerProvider tracerProvider;
static Function()
{
tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddAWSInstrumentation()
.AddOtlpExporter()
.AddAWSLambdaConfigurations()
.Build();
}
// use AwsSdkSample::AwsSdkSample.Function::TracingFunctionHandler as input Lambda handler instead
public APIGatewayProxyResponse TracingFunctionHandler(APIGatewayProxyRequest request, ILambdaContext context)
{
return AWSLambdaWrapper.Trace(tracerProvider, FunctionHandler, request, context);
}
/// <summary>
/// A simple function that takes a APIGatewayProxyRequest and returns a APIGatewayProxyResponse
/// </summary>
/// <param name="input"></param>
/// <param name="context"></param>
/// <returns></returns>
public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest request, ILambdaContext context)
{
return new APIGatewayProxyResponse() { StatusCode = 200, Body = Environment.GetEnvironmentVariable("_X_AMZN_TRACE_ID") };
}
}
}

Then set the wrapper function as the Lambda function's handler. For the class above, the handler would be function::Example.Function::TracingFunctionHandler.

For a working example, including tracing the AWS SDK, see this sample app.

The above is just a basic example, for more advanced instrumentation refer to the OpenTelemetry .NET SDK documentation.

Step 6: View your data in the New Relic UI

First you will want to invoke your Lambda function a few times to start generating telemetry. From there, head over to New Relic to find your traces, metrics, and logs.

Your telemetry will not appear under New Relic Serverless. Instead, you will find your telemetry data under the New Relic OpenTelemetry Nerdlets.

Distributed Tracing

In some cases you may see fragmented distributed traces within New Relic with this configuration. This occurs when a trace starts or involves a service that is outside the ADOT context (for example, a managed AWS service). Spans from that service are created by X-Ray, not OpenTelemetry, and ADOT does not forward them to New Relic. Although the traces appear fragmented, they still provide complete insight into performance within the Lambda function as well as other services whose spans were forwarded to New Relic.

More information

For more information, check out the New Relic OpenTelemetry quick start.

Copyright © 2022 New Relic Inc.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.