Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add logic to instrument AWS Lambda functions using Application Signals #136

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -467,4 +467,9 @@ lychee/out.md
*/dist

# Python cache
*.pytest_cache
*.pytest_cache

# Terraform files
.terraform*
terraform.tfstate
terraform.tfstate.backup
7 changes: 7 additions & 0 deletions AWS.Distro.OpenTelemetry.AutoInstrumentation.sln
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MockCollector", "test\image
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTelemetry.Instrumentation.AWS", "src\OpenTelemetry.Instrumentation.AWS\OpenTelemetry.Instrumentation.AWS.csproj", "{30B39779-F7AA-4AAB-B3AB-A0EFEEC35CED}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleLambdaFunction", "sample-applications\lambda-test-apps\SimpleLambdaFunction\src\SimpleLambdaFunction\SimpleLambdaFunction.csproj", "{3E5D9A9C-5280-4061-9B6A-5E2FDAF6FE49}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -69,6 +71,10 @@ Global
{30B39779-F7AA-4AAB-B3AB-A0EFEEC35CED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{30B39779-F7AA-4AAB-B3AB-A0EFEEC35CED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30B39779-F7AA-4AAB-B3AB-A0EFEEC35CED}.Release|Any CPU.Build.0 = Release|Any CPU
{3E5D9A9C-5280-4061-9B6A-5E2FDAF6FE49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3E5D9A9C-5280-4061-9B6A-5E2FDAF6FE49}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E5D9A9C-5280-4061-9B6A-5E2FDAF6FE49}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E5D9A9C-5280-4061-9B6A-5E2FDAF6FE49}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -81,5 +87,6 @@ Global
{E3401B0D-DCE1-45C2-8178-2D21FA52F659} = {DD015346-5FF7-4298-AD31-326B18040908}
{ECD2F534-A9FB-48B4-8C06-6DD2F8B07C41} = {E3401B0D-DCE1-45C2-8178-2D21FA52F659}
{30B39779-F7AA-4AAB-B3AB-A0EFEEC35CED} = {BDFD4285-9DB0-4F47-8EDC-7F32A781DCD4}
{3E5D9A9C-5280-4061-9B6A-5E2FDAF6FE49} = {7E420F1D-B6D6-47AF-A430-5368FE14D2AA}
EndGlobalSection
EndGlobal
68 changes: 60 additions & 8 deletions instrument.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ case "$ENABLE_PROFILING" in
;;
esac

if [ ! -z "${AWS_LAMBDA_FUNCTION_NAME}" ]; then
OTEL_DOTNET_AUTO_HOME="/opt"
fi

# set defaults
test -z "$OTEL_DOTNET_AUTO_HOME" && OTEL_DOTNET_AUTO_HOME="$HOME/.otel-dotnet-auto"

Expand Down Expand Up @@ -173,13 +177,61 @@ if [ "$ENABLE_PROFILING" = "true" ]; then

# AWS OTEL DOTNET
export OTEL_DOTNET_AUTO_PLUGINS="AWS.Distro.OpenTelemetry.AutoInstrumentation.Plugin, AWS.Distro.OpenTelemetry.AutoInstrumentation"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://127.0.0.1:4316"
export OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT="http://127.0.0.1:4316/v1/metrics"
export OTEL_METRICS_EXPORTER="none"
export OTEL_AWS_APPLICATION_SIGNALS_ENABLED="true"
export OTEL_TRACES_SAMPLER="xray"
export OTEL_TRACES_SAMPLER_ARG="endpoint=http://127.0.0.1:2000"

# If this is true, that means we are in a lambda env. In this case, setup the environment for lambda.
if [ ! -z "${AWS_LAMBDA_FUNCTION_NAME}" ]; then

export OTEL_INSTRUMENTATION_AWS_LAMBDA_HANDLER="$_HANDLER"
export _HANDLER="AWS.Distro.OpenTelemetry.AutoInstrumentation::AWS.Distro.OpenTelemetry.AutoInstrumentation.LambdaWrapper::TracingFunctionHandler"

echo "$DOTNET_SHARED_STORE"

if [ -z "${OTEL_EXPORTER_OTLP_PROTOCOL}" ]; then
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
fi

if [ -z "${OTEL_SERVICE_NAME}" ]; then
export OTEL_SERVICE_NAME=$AWS_LAMBDA_FUNCTION_NAME;
fi

export LAMBDA_RESOURCE_ATTRIBUTES="cloud.region=$AWS_REGION,cloud.provider=aws,faas.name=$AWS_LAMBDA_FUNCTION_NAME,faas.version=$AWS_LAMBDA_FUNCTION_VERSION,faas.instance=$AWS_LAMBDA_LOG_STREAM_NAME,aws.log.group.names=$AWS_LAMBDA_LOG_GROUP_NAME";

if [ -z "${OTEL_AWS_APPLICATION_SIGNALS_ENABLED}" ]; then
export OTEL_AWS_APPLICATION_SIGNALS_ENABLED="true";
fi

if [ -z "${OTEL_METRICS_EXPORTER}" ]; then
vastin marked this conversation as resolved.
Show resolved Hide resolved
export OTEL_METRICS_EXPORTER="none";
fi

if [ -z "${OTEL_RESOURCE_ATTRIBUTES}" ]; then
export OTEL_RESOURCE_ATTRIBUTES=$LAMBDA_RESOURCE_ATTRIBUTES;
else
export OTEL_RESOURCE_ATTRIBUTES="$LAMBDA_RESOURCE_ATTRIBUTES,$OTEL_RESOURCE_ATTRIBUTES";
fi

if [ -z "${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT}" ] && [ -z "${OTEL_EXPORTER_OTLP_ENDPOINT}" ]; then
export OTEL_TRACES_EXPORTER="none";
fi

# TODO: need to disable all instrumentations except aws sdk and lambda.

else
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://127.0.0.1:4316"
export OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT="http://127.0.0.1:4316/v1/metrics"
export OTEL_METRICS_EXPORTER="none"
export OTEL_AWS_APPLICATION_SIGNALS_ENABLED="true"
export OTEL_TRACES_SAMPLER="xray"
export OTEL_TRACES_SAMPLER_ARG="endpoint=http://127.0.0.1:2000"
fi

fi

exec "$@"
# in a lambda env, we need to change the handler in the exec command to the lambda wrapper
# else, we pass the execution back to the main caller unchanged.
if [ ! -z "${AWS_LAMBDA_FUNCTION_NAME}" ]; then
exec "${@//$OTEL_INSTRUMENTATION_AWS_LAMBDA_HANDLER/$_HANDLER}"
else
exec "$@"
fi
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# AWS Lambda Application Signals Support

This package provides support for **Application Signals** in AWS Lambda environment.

## Features

- Supports Application Signals, including traces and metrics, for AWS Lambda .NET Runtimes.
- Automates the deployment process, including the creation of the Application .NET Lambda Layer and a sample Lambda function.

## Prerequisites

Before you begin, make sure you have installed and configured the following tools:

- [Docker](https://www.docker.com/get-started)
- [Terraform](https://www.terraform.io/downloads)
- [AWS CLI](https://aws.amazon.com/cli/) (to configure AWS credentials)
- [.NET Lambda Tools](https://docs.aws.amazon.com/lambda/latest/dg/csharp-package-cli.html) (to build and publish lambda function)

### Configure AWS Credentials

Ensure that your AWS credentials are properly configured in your local environment. You can use the following command with the AWS CLI:

```bash
aws configure
```
This will prompt you to enter your `AWS Access Key ID`, `AWS Secret Access Key`, `Default region name`, and `Default output format`.

## Installation and Deployment

### 1. Clone the Repository

First, clone this repository to your local machine:

```bash
git clone https://github.com/aws-observability/aws-otel-dotnet-instrumentation.git
```

### 2. Run the Build Script

Navigate to the `lambda-test-apps` folder and run the `build-and-deploy.sh` script. This will create the Application .NET Lambda Layer and a Lambda sample app in your AWS account:

```bash
cd sample-applications/lambda-test-apps/SimpleLambdaFunction/
./build-and-deploy.sh
```

## Lambda Sample App

Once the script has successfully run, you will see the deployed Lambda sample app in your AWS account. You can trigger the
Lambda function and view the traces and metrics through the AWS CloudWatch Console.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

check_if_step_failed_and_exit() {
if [ $? -ne 0 ]; then
echo $1
exit 1
fi
}

# Build the distro
cd ../../../
bash ./build.sh
check_if_step_failed_and_exit "There was an error building AWS Otel DotNet, exiting"

cd ./sample-applications/lambda-test-apps/SimpleLambdaFunction
dotnet lambda package -pl ./src/SimpleLambdaFunction
check_if_step_failed_and_exit "There was an error building the SimpleLambdaFunction, exiting"

cd ./terraform/lambda
terraform init
terraform apply -auto-approve
check_if_step_failed_and_exit "There was an error deploying the SimpleLambdaFunction, exiting"
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Amazon.Lambda.Core;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.S3;

// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace SimpleLambdaFunction;

public class Function
{
private static readonly HttpClient httpClient = new HttpClient();
private static readonly AmazonS3Client s3Client = new AmazonS3Client();

/// <summary>
/// This function handles API Gateway requests and returns results from an HTTP request and S3 call.
/// </summary>
/// <param name="apigProxyEvent"></param>
/// <param name="context"></param>
/// <returns></returns>
public async Task<APIGatewayProxyResponse> FunctionHandler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)
{
context.Logger.LogLine("Making HTTP call to https://aws.amazon.com/");
await httpClient.GetAsync("https://aws.amazon.com/");

context.Logger.LogLine("Making AWS S3 ListBuckets call");
int bucketCount = await ListS3Buckets().ConfigureAwait(false);

var traceId = Environment.GetEnvironmentVariable("_X_AMZN_TRACE_ID");

return new APIGatewayProxyResponse
{
StatusCode = 200,
Body = $"Hello lambda - found {bucketCount} buckets. X-Ray Trace ID: {traceId}",
Headers = new Dictionary<string, string> { { "Content-Type", "text/plain" } }
};
}

/// <summary>
/// List all S3 buckets using AWS SDK for .NET
/// </summary>
/// <returns>Number of buckets available</returns>
private async Task<int> ListS3Buckets()
{
var response = await s3Client.ListBucketsAsync();
return response.Buckets.Count;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# AWS Lambda Empty Function Project

This starter project consists of:
* Function.cs - class file containing a class with a single function handler method
* aws-lambda-tools-defaults.json - default argument settings for use with Visual Studio and command line deployment tools for AWS

You may also have a test project depending on the options selected.

The generated function handler is a simple method accepting a string argument that returns the uppercase equivalent of the input string. Replace the body of this method, and parameters, to suit your needs.

## Here are some steps to follow from Visual Studio:

To deploy your function to AWS Lambda, right click the project in Solution Explorer and select *Publish to AWS Lambda*.

To view your deployed function open its Function View window by double-clicking the function name shown beneath the AWS Lambda node in the AWS Explorer tree.

To perform testing against your deployed function use the Test Invoke tab in the opened Function View window.

To configure event sources for your deployed function, for example to have your function invoked when an object is created in an Amazon S3 bucket, use the Event Sources tab in the opened Function View window.

To update the runtime configuration of your deployed function use the Configuration tab in the opened Function View window.

To view execution logs of invocations of your function use the Logs tab in the opened Function View window.

## Here are some steps to follow to get started from the command line:

Once you have edited your template and code you can deploy your application using the [Amazon.Lambda.Tools Global Tool](https://github.com/aws/aws-extensions-for-dotnet-cli#aws-lambda-amazonlambdatools) from the command line.

Install Amazon.Lambda.Tools Global Tools if not already installed.
```
dotnet tool install -g Amazon.Lambda.Tools
```

If already installed check if new version is available.
```
dotnet tool update -g Amazon.Lambda.Tools
```

Execute unit tests
```
cd "SimpleLambdaFunction/test/SimpleLambdaFunction.Tests"
dotnet test
```

Deploy function to AWS Lambda
```
cd "SimpleLambdaFunction/src/SimpleLambdaFunction"
dotnet lambda deploy-function
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<AWSProjectType>Lambda</AWSProjectType>
<!-- This property makes the build directory similar to a publish directory and helps the AWS .NET Lambda Mock Test Tool find project dependencies. -->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<!-- Generate ready to run images during publishing to improve cold start time. -->
<PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Amazon.Lambda.APIGatewayEvents" Version="2.7.1" />
<PackageReference Include="Amazon.Lambda.Core" Version="2.2.0" />
<PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.4.1" />
<PackageReference Include="AWSSDK.S3" Version="3.7.405.2" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"Information": [
"This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.",
"To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.",
"dotnet lambda help",
"All the command line options for the Lambda command can be specified in this file."
],
"profile": "",
"region": "",
"configuration": "Release",
"function-architecture": "x86_64",
"function-runtime": "dotnet8",
"function-memory-size": 512,
"function-timeout": 30,
"function-handler": "SimpleLambdaFunction::SimpleLambdaFunction.Function::FunctionHandler"
}
Loading
Loading