Overview
Add an OpenTelemetry auto-instrumentation layer to your Lambda function to send traces to SigNoz. The layer records each invocation and the outbound calls that it instruments. Your function code does not change.
Prerequisites
- AWS account with full access to AWS Lambda.
Send traces to SigNoz
Step 1: Create a Lambda function
Firstly, create a Lambda function in the language of your choice.
-
Navigate to AWS Lambda service on the AWS console, and click on the Create Function button present on the top right corner of the page.
-
In the Create function page, select Author from scratch.
-
In the Basic information section, provide an appropriate Function name, and Runtime based on the language in which you want to write your function.
-
Select the Architecture as per your requirement.
-
Click on Create function to create the function.
Next, we will create the Lambda function. The function will make a REST API call to the URL:
https://api.restful-api.dev/objects?id=3&id=5, print the response and return the same.
The URL will get the object details of the objects with ID 3 and 5.
On your machine, create a folder, say auto-instrument-tracing-lambda.
Add a file lambda_function.py in that folder with the lambda function code which will be as follows:
import json
import requests
def lambda_handler(event, context):
res = requests.get('https://api.restful-api.dev/objects?id=3&id=5')
print(res.json())
return {
'statusCode': 200,
'body': res.json()
}Inside the folder, create a new directory named package into which you will install your dependencies.
$ mkdir packageInstall the dependencies in the package directory using the command:
$ pip install --target ./package requestsCreate a .zip file with the installed libraries at the root.
$ cd package
$ zip -r ../auto-instrument-tracing-lambda.zip .This generates a auto-instrument-tracing-lambda.zip file in your project directory.
Add the lambda_function.py file to the root of the .zip file using the following command:
$ cd ..
$ zip auto-instrument-tracing-lambda.zip lambda_function.pyIn the Code tab on the Lambda function page, click on Upload from button and choose .zip from the dropdown.
Upload the auto-instrument-tracing-lambda.zip file. Now, you can test the lambda function.
On your machine, create a folder say auto-instrument-tracing-lambda. In the folder, create a file index.mjs and put the following code in it.
import https from 'node:https'
const url = 'https://api.restful-api.dev/objects?id=3&id=5'
// node:https, not fetch. The layer enables the `http` instrumentation by
// default, and `undici`, which powers global fetch, is off by default.
function get(target) {
return new Promise((resolve, reject) => {
https
.get(target, (res) => {
let data = ''
res.on('data', (chunk) => (data += chunk))
res.on('end', () => resolve(data))
})
.on('error', reject)
})
}
export const handler = async () => {
const data = await get(url)
return { statusCode: 200, body: data }
}The function uses only the Node.js standard library, so it needs no dependencies.
Zip the nodejs package using the command:
zip -r auto-instrument-tracing-lambda.zip .In the Code tab on the Lambda function page, click on Upload from button and choose .zip from the dropdown.
Upload the auto-instrument-tracing-lambda.zip file. Now, you can test the lambda function.
On your machine, create a Maven package in Java, say AutoInstrumentationLambda. The pom.xml file would look like as follows:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>AutoInstrumentationLambda</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-events</artifactId>
<version>3.11.1</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-log4j2</artifactId>
<version>1.5.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>Note that we have used Java 21 in this package.
Create the Main.java file in the location src/main/java/org/example/ inside the package. The Main.java will
have the handleRequest method that will run on invoking the AWS Lambda function. Here are the contents of the Main.java
file:
package org.example;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
public class Main implements RequestHandler<Object, String> {
@Override
public String handleRequest(Object event, Context context) {
try {
String urlString = "https://api.restful-api.dev/objects?id=3&id=5";
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
int responseCode = connection.getResponseCode();
System.out.println(responseCode);
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line+"\n");
}
br.close();
return sb.toString();
} catch(Exception e) {
System.out.println("Exception: " + e.getMessage());
}
return null;
}
}Now, run the following commands from the terminal from the base folder which contains the pom.xml file:
# This will build the maven package
$ mvn clean install
# This will generate the .jar file in the `target` folder
$ mvn packageUpload the .jar file in the AWS console on the corresponding function's page.
Also, edit the Runtime Settings to edit the Handler to be org.example.Main::handleRequest.
On your machine, create a folder for the project say, auto-instrumentation-tracing.
Create the lambda_function.rb file that will contain the code for the function. It uses net/http
and json from the Ruby standard library, so the project needs no gems and no Gemfile:
require 'net/http'
require 'uri'
require 'json'
API_URL = 'https://api.restful-api.dev/objects?id=3&id=5'.freeze
module LambdaFunction
class Handler
def self.process(event:, context:)
response = Net::HTTP.get_response(URI.parse(API_URL))
objects = JSON.parse(response.body)
{
statusCode: 200,
body: JSON.generate({ count: objects.length })
}
end
end
endSet the function handler to lambda_function.LambdaFunction::Handler.process.
Zip the contents of the folder with the following command:
$ zip auto-instrumentation-tracing.zip lambda_function.rbIn the Code tab on the Lambda function page, click on Upload from button and choose .zip from the dropdown.
Upload the auto-instrumentation-tracing.zip file. Now, you can test the lambda function.
Step 2: Add the auto-instrumentation layer
The language-specific auto-instrumentation lambda layers automatically instrument your Lambda function code with OpenTelemetry auto-instrumentation package for your specific language. Each language and region has its own layer ARN.
If your Lambda is already instrumented with an OpenTelemetry SDK, you can skip this step.
If your function is packaged as a container image, layers do not apply. Unpack the layer into the image instead, as shown in Instrument AWS Lambda Container Images.
In order to auto-instrument your Lambda function, follow the steps:
- Go to the Lambda function you want to auto-instrument.
- In the Layers section, click on Add a layer.
- In the Choose a layer section, select Specify an ARN option.
- Choose the correct ARN based on the language, ensure you replace the
<aws-region>with your AWS region (for exampleus-east-1):
arn:aws:lambda:<aws-region>:184161586896:layer:opentelemetry-python-0_21_0:1arn:aws:lambda:<aws-region>:184161586896:layer:opentelemetry-nodejs-0_23_0:1arn:aws:lambda:<aws-region>:184161586896:layer:opentelemetry-javaagent-0_21_0:1arn:aws:lambda:<aws-region>:184161586896:layer:opentelemetry-ruby-0_14_0:1The latest releases of the layers can be found in the OpenTelemetry Lambda Layers GitHub repository.
Step 3: Set the environment variables
These variables tell the runtime to load the wrapper, and tell the SDK where to send spans. The SDK exports to SigNoz directly, so no Collector is needed.
- Navigate to the Configuration tab within your function, and select Environment variables from the left navigation menu.
- Add the following environment variables.
OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.<region>.signoz.cloud:443
OTEL_EXPORTER_OTLP_HEADERS=signoz-ingestion-key=<your-ingestion-key>
AWS_LAMBDA_EXEC_WRAPPER=/opt/otel-instrument
OTEL_PROPAGATORS=tracecontext
OTEL_TRACES_SAMPLER=always_onOTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.<region>.signoz.cloud:443
OTEL_EXPORTER_OTLP_HEADERS=signoz-ingestion-key=<your-ingestion-key>
AWS_LAMBDA_EXEC_WRAPPER=/opt/otel-handler
OTEL_PROPAGATORS=tracecontext
OTEL_TRACES_SAMPLER=always_onOTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.<region>.signoz.cloud:443
OTEL_EXPORTER_OTLP_HEADERS=signoz-ingestion-key=<your-ingestion-key>
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
AWS_LAMBDA_EXEC_WRAPPER=/opt/otel-handler
OTEL_PROPAGATORS=tracecontext
OTEL_TRACES_SAMPLER=always_onOTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.<region>.signoz.cloud:443
OTEL_EXPORTER_OTLP_HEADERS=signoz-ingestion-key=<your-ingestion-key>
AWS_LAMBDA_EXEC_WRAPPER=/opt/otel-handler
OTEL_PROPAGATORS=tracecontext
OTEL_TRACES_SAMPLER=always_onVerify these values:
<region>: Your SigNoz Cloud region.<your-ingestion-key>: Your SigNoz ingestion key.
Validate
- Invoke the function once from the AWS console, or with
aws lambda invoke. - Open SigNoz and go to Traces in the left navigation menu.
- Filter on your
service.name, then click Refresh. Spans from your function appear within a few seconds.

- Open one trace. The invocation span sits at the root, with a client span under it for every outbound call that the layer instruments.

Troubleshooting
No traces reach SigNoz
- Likely cause: the wrapper never ran, so the SDK never loaded.
- Fix: Make sure that
AWS_LAMBDA_EXEC_WRAPPERmatches your language, and that the layer is attached to the function. - Verify: Set
OTEL_LOG_LEVEL=debugon the function, invoke it again, and read the export errors in CloudWatch.
SigNoz returns a 401 or a 403
- Likely cause: the ingestion key is wrong, or it belongs to a different region than the endpoint.
- Fix: Copy the key again from Settings → Ingestion Settings in SigNoz, and make sure that the region in
OTEL_EXPORTER_OTLP_ENDPOINTmatches the key. - Verify: Invoke the function again. The export errors stop.
The function fails to update with a layer error
- Likely cause: the layer ARN names a region that does not match the function.
- Fix: Edit the layer ARN so that its region matches the region of the function. A layer is regional.
- Verify: Save the configuration again. The update succeeds.
The invocation span appears without any child spans
- Likely cause: the library that you called is not on the layer's instrumentation list.
- Fix: Read the language README in the opentelemetry-lambda repository for the instrumentations that ship in your layer. Some are off by default.
- Verify: Invoke the function again. The client span appears under the invocation span.
Export through the OTel Collector layer (Optional)
The setup above exports from the function process straight to SigNoz. The Collector extension layer gives you a place to batch, filter, and enrich telemetry before it leaves the function.
The Collector does not always lower latency. It adds a local hop and its own startup time. When your AWS region is close to your SigNoz region, direct export is often faster. When the two are far apart, the Collector helps more. Measure your function with and without the layer before you keep it.
To install the OpenTelemetry Collector Lambda layer, follow these steps:
- Go to the Lambda function.
- In the Layers section, click on Add a layer.
- In the Choose a layer section, select Specify an ARN option.
- Choose the correct ARN based on your function architecture, ensure you replace the
<aws-region>with your AWS region (for exampleus-east-1):
arn:aws:lambda:<aws-region>:184161586896:layer:opentelemetry-collector-amd64-0_23_0:1arn:aws:lambda:<aws-region>:184161586896:layer:opentelemetry-collector-arm64-0_23_0:1- Add the following
collector.yamlfile to the root of your deployment archive. Lambda unpacks it to/var/task/collector.yaml:
receivers:
otlp:
protocols:
grpc:
endpoint: 'localhost:4317'
http:
endpoint: 'localhost:4318'
processors:
batch: {}
resource/env:
attributes:
- key: deployment.environment
value: prod # can be dev, prod, staging etc. based on your environment
action: upsert
exporters:
# Collector layer 0.23.0 bundles Collector v0.157.0.
# On Collector v0.143.0 and older, use "otlphttp" instead.
otlp_http:
endpoint: 'https://ingest.<region>.signoz.cloud:443'
headers:
# Read from the function environment, so the key stays out of the
# deployment archive.
signoz-ingestion-key: '${env:SIGNOZ_INGESTION_KEY}'
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch, resource/env]
exporters: [otlp_http]
# The layers export metrics to the same endpoint. Without this pipeline
# the function logs a 404 on every invocation.
metrics:
receivers: [otlp]
processors: [batch, resource/env]
exporters: [otlp_http]Verify these values:
<region>: Your SigNoz Cloud region.<your-ingestion-key>: Your SigNoz ingestion key.
-
Navigate to the Configuration tab within your function, and select Environment variables from the left navigation menu.
-
Add the following environment variable:
OPENTELEMETRY_COLLECTOR_CONFIG_URI=/var/task/collector.yamlThe function must now export to the Collector on localhost instead of to SigNoz. Replace the
endpoint from Step 3, and keep the ingestion key on the function, where collector.yaml reads it:
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
SIGNOZ_INGESTION_KEY=<your-ingestion-key>Remove OTEL_EXPORTER_OTLP_HEADERS. The Collector adds the key now, so the function no longer
needs it.
Troubleshooting the Collector
The Collector never starts. OPENTELEMETRY_COLLECTOR_CONFIG_URI points at a path that does not
exist. Keep collector.yaml at the root of the deployment archive, which Lambda unpacks to
/var/task/collector.yaml. On the next cold start the Collector logs
Using config URI from environment variable with your path.
The function logs Failed to export metrics batch code: 404. The layers export metrics as well
as traces, and collector.yaml has no metrics pipeline. Add the metrics pipeline shown above.
Next steps
- Instrument a container image function, which cannot use layers.
- Instrument a Go Lambda function, which has no auto-instrumentation layer.
- Collect Lambda logs with the same Collector extension layer.
- Collect Lambda metrics such as invocations, errors, and duration.
- Correlate traces with logs to move between signals during triage.
- Set up alerts on the latency and the error rate of the function.
Get Help
If you need help with the steps in this topic, please reach out to us on SigNoz Community Slack. If you are a SigNoz Cloud user, please use in product chat support located at the bottom right corner of your SigNoz instance or contact us at cloud-support@signoz.io.