AWS Lambda

Middleware instruments AWS Lambda functions to collect application telemetry and help investigate function execution, errors, and requests across serverless services. Available signals and export configuration depend on the runtime and instrumentation method described below.

Middleware supports AWS Lambda instrumentation for Node.js, Python, Java, Go, and .NET.

How it works#

Middleware uses OpenTelemetry to collect telemetry from AWS Lambda functions.

Depending on the runtime, instrumentation works through:

  • OpenTelemetry Lambda layers: Automatically instrument Node.js, Python, and Java functions using runtime-specific layers and, where required, an OpenTelemetry Collector extension.
  • OpenTelemetry SDK: Instrument Go Lambda handlers and export traces directly to Middleware.
  • Middleware APM SDK: Configure .NET applications to send telemetry to Middleware.

Lambda execution environments can freeze after an invocation. Configure telemetry flushing to ensure spans are exported before the function stops processing.

AWS Lambda APM layer architecture and data flow

Supported telemetry#

TracesMetricsApp LogsCustom LogsProfiling
✅✅✅✅✖

Telemetry availability depends on the runtime and the instrumentation configuration.

Prerequisites#

Before instrumenting AWS Lambda, verify the following:

  • An AWS account with an existing Lambda function.
  • Permissions to update Lambda layers, environment variables, and function code.
  • A Middleware account with an API key and target URL.
  • Outbound HTTPS access from the Lambda function to your Middleware endpoint on port 443.
  • The appropriate OpenTelemetry Lambda layer for your runtime, AWS Region, and processor architecture, where applicable.

Optional: Enable AWS X-Ray Active Tracing if you need X-Ray integration and compatible trace context propagation.

Select your runtime#

RuntimeInstallation method
Node.jsOpenTelemetry Collector and auto-instrumentation layers
PythonOpenTelemetry Collector and auto-instrumentation layers
JavaOpenTelemetry Java instrumentation layer
GoOpenTelemetry Go SDK and Lambda handler wrapper
.NETMiddleware APM SDK

Note: OpenTelemetry Lambda layer ARNs are Region- and architecture-specific. Use the appropriate published version from the OpenTelemetry Lambda releases rather than relying on an older example ARN.

Installation#

Use OpenTelemetry Lambda layers to automatically instrument your Node.js function and send traces to Middleware.

1 Get the OpenTelemetry layer ARNs#

Open the OpenTelemetry Lambda repository.

Find the compatible versions of:

  • OpenTelemetry Collector Lambda layer
  • OpenTelemetry Node.js auto-instrumentation layer

Select the layer ARNs for your AWS Region, runtime, and architecture.

2 Add the layers to your Lambda function#

  1. Open AWS Console → Lambda → Functions.
  2. Select the function you want to instrument.
  3. Scroll to Layers.
  4. Click Add a layer → Specify an ARN.
  5. Add the Collector layer.
  6. Add the Node.js auto-instrumentation layer

Add the layers in the order specified above.

3 Configure environment variables#

Go to Configuration → Environment variables → Edit.

Add the following:

1AWS_LAMBDA_EXEC_WRAPPER=/opt/otel-handler
2NODE_OPTIONS=--require ./lambda-config.js
3OTEL_SERVICE_NAME=your-service-name
4OTEL_EXPORTER_OTLP_ENDPOINT=https://<MW_UID>.middleware.io:443
5OTEL_RESOURCE_ATTRIBUTES=mw.account_key=<MW_API_KEY>
6OTEL_LAMBDA_DISABLE_AWS_CONTEXT_PROPAGATION=true
7OTEL_PROPAGATORS=tracecontext

Replace the placeholders with your Middleware account details and service name.

Note: For ESM-based Node.js applications, use the appropriate --import configuration instead of --require.

4 Configure trace exporting#

Create a file named lambda-config.js in your Lambda function's root directory.

1const { SimpleSpanProcessor } = require("@opentelemetry/sdk-trace-base");
2const {
3  OTLPTraceExporter,
4} = require("@opentelemetry/exporter-trace-otlp-proto");
5
6global.configureTracerProvider = (tracerProvider) => {
7  const spanProcessor = new SimpleSpanProcessor(new OTLPTraceExporter());
8  tracerProvider.addSpanProcessor(spanProcessor);
9};

This configures the trace provider to export spans as they finish, reducing the risk of losing spans when Lambda freezes the execution environment.

Ensure the required OpenTelemetry dependencies are included in your application package.

5 Deploy and verify#

Deploy the updated Lambda function and invoke it.

Go to APM → Services in Middleware and verify that your configured service appears.

Add custom spans (optional)#

You can use the OpenTelemetry API to capture custom operations in your Lambda function.

1const { trace } = require("@opentelemetry/api");
2
3const tracer = trace.getTracer("lambda-handler", "1.0.0");
4
5exports.handler = async (event) => {
6  const span = tracer.startSpan("process-request");
7
8  try {
9    span.setAttribute("function.operation", "process-request");
10
11    // Your application logic here
12
13    return {
14      statusCode: 200,
15      body: JSON.stringify({ message: "Success" }),
16    };
17  } catch (error) {
18    span.recordException(error);
19    throw error;
20  } finally {
21    span.end();
22  }
23};

Use custom spans to capture application-specific operations that automatic instrumentation does not cover.

Use OpenTelemetry Lambda layers to collect traces and logs from Python functions.

1 Get the OpenTelemetry layer ARNs#

From the OpenTelemetry Lambda repository find the compatible:

  • OpenTelemetry Collector Lambda layer
  • OpenTelemetry Python auto-instrumentation layer

Select the correct ARNs for your AWS Region and architecture.

2 Create the Collector configuration#

Create a file named collector.yaml in your Lambda deployment package.

1receivers:
2  telemetryapi: {}
3  otlp:
4    protocols:
5      grpc:
6        endpoint: "localhost:9319"
7      http:
8        endpoint: "localhost:9320"
9
10exporters:
11  debug:
12    verbosity: detailed
13  otlp:
14    endpoint: "https://<MW_UID>.middleware.io:443"
15    headers:
16      authorization: <MW_API_KEY>
17
18processors:
19  decouple: {}
20  resource:
21    attributes:
22      - action: upsert
23        key: service.name
24        value: my-service-name
25
26service:
27  pipelines:
28    traces:
29      receivers:
30        - telemetryapi
31        - otlp
32      processors:
33        - resource
34        - decouple
35      exporters:
36        - otlp
37
38    logs:
39      receivers:
40        - telemetryapi
41        - otlp
42      processors:
43        - resource
44        - decouple
45      exporters:
46        - otlp

Replace the Middleware credentials and service name.

The Collector receives telemetry locally and exports it to Middleware.

Note: This configuration requires a Collector distribution that supports the telemetryapi receiver and decouple processor. Verify compatibility with the selected Collector Lambda layer.

3 Add the Lambda layers#

  1. Open AWS Console → Lambda → Functions.
  2. Select your Python Lambda function.
  3. Go to Layers → Add a layer.
  4. Add the Collector layer.
  5. Add the Python auto-instrumentation layer.

Use the appropriate ARNs for your deployment.

4 Configure environment variables#

Under Configuration → Environment variables, add:

1AWS_LAMBDA_EXEC_WRAPPER=/opt/otel-instrument
2OPENTELEMETRY_COLLECTOR_CONFIG_URI=/var/task/collector.yaml
3OPENTELEMETRY_EXTENSION_LOG_LEVEL=info
4OTEL_BSP_SCHEDULE_DELAY=500
5OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:9320
6OTEL_LAMBDA_DISABLE_AWS_CONTEXT_PROPAGATION=true
7OTEL_PROPAGATORS=tracecontext

The application sends telemetry to the local Collector, which forwards it to Middleware.

5 Enable JSON logging#

To collect structured application logs:

  1. Open your Lambda function.
  2. Go to Configuration → Monitoring and operations tools.
  3. Click Edit.
  4. Set the log format to JSON.
  5. Save the configuration.

6 Configure application logging (optional)#

Use Python's logging module to emit application logs.

1import json
2import logging
3
4def lambda_handler(event, context):
5    logger1 = logging.getLogger("logger1")
6    logger1.setLevel(logging.DEBUG)
7
8    logger1.debug("This is a debug message from aws lambda")
9    logger1.info("This is an info message from aws lambda")
10
11    logger1.propagate = False
12
13    logging.shutdown()
14
15    return {
16        "statusCode": 200,
17        "body": json.dumps("Hello from Lambda!")
18    }

Set logger1.propagate = False in this configuration to avoid duplicate log records.

Deploy the function and invoke it to generate telemetry.

Use the OpenTelemetry Java Lambda instrumentation layer to collect application traces.

1 Add the Java instrumentation layer#

  1. Open AWS Console → Lambda → Functions.
  2. Select your Java Lambda function.
  3. Go to Layers → Add a layer.
  4. Add the compatible OpenTelemetry Java instrumentation layer ARN for your AWS Region and runtime.

Find the current layer information in the OpenTelemetry Lambda repository.

2 Configure environment variables#

Add the following variables to your Lambda function:

1AWS_LAMBDA_EXEC_WRAPPER=/opt/otel-handler
2OTEL_EXPORTER_OTLP_ENDPOINT=https://<MW_UID>.middleware.io:443
3OTEL_LAMBDA_DISABLE_AWS_CONTEXT_PROPAGATION=true
4OTEL_PROPAGATORS=tracecontext
5OTEL_RESOURCE_ATTRIBUTES=mw.account_key=<YOUR_MW_ACCOUNT_KEY>
6OTEL_SERVICE_NAME=<JAVA_LAMBDA_SERVICE>

Replace the placeholders with your Middleware credentials and service name.

3 Add custom instrumentation (optional)#

Automatic instrumentation captures supported Lambda operations. To create custom spans, add the OpenTelemetry API dependency.

Gradle

1dependencies {
2    implementation 'io.opentelemetry:opentelemetry-api:1.28.0'
3    implementation 'com.amazonaws:aws-lambda-java-core:1.2.1'
4}

Maven

1<dependencies>
2    <dependency>
3        <groupId>io.opentelemetry</groupId>
4        <artifactId>opentelemetry-api</artifactId>
5        <version>1.28.0</version>
6    </dependency>
7    <dependency>
8        <groupId>com.amazonaws</groupId>
9        <artifactId>aws-lambda-java-core</artifactId>
10        <version>1.2.1</version>
11    </dependency>
12</dependencies>

Use dependency versions compatible with your Java runtime and OpenTelemetry instrumentation layer.

4 Deploy and verify#

Deploy the updated function and invoke it.

Go to APM → Services in Middleware to verify trace collection.

Instrument Go Lambda functions using the OpenTelemetry Go SDK and the official Lambda handler wrapper.

Unlike the layer-based methods, this setup exports traces directly to Middleware using OTLP/HTTP.

1 Install the required packages#

Add the OpenTelemetry and AWS Lambda packages to your Go project.

1go get github.com/aws/aws-lambda-go/lambda
2go get go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda
3go get go.opentelemetry.io/otel
4go get go.opentelemetry.io/otel/sdk
5go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp

2 Configure the trace exporter#

Create a tracer provider that exports spans to Middleware.

1func initTracerProvider(ctx context.Context) (*trace.TracerProvider, error) {
2    exp, err := otlptracehttp.New(ctx,
3        otlptracehttp.WithEndpointURL(
4            "https://<MW_UID>.middleware.io:443/v1/traces",
5        ),
6    )
7    if err != nil {
8        return nil, err
9    }
10
11    res := resource.NewSchemaless(
12        attribute.String("service.name", "go-lambda-service"),
13        attribute.String("mw.account_key", "<YOUR_MW_ACCOUNT_KEY>"),
14        attribute.String("library.language", "go"),
15    )
16
17    tp := trace.NewTracerProvider(
18        trace.WithBatcher(exp),
19        trace.WithResource(res),
20    )
21
22    otel.SetTracerProvider(tp)
23
24    return tp, nil
25}

The WithEndpointURL option requires the complete OTLP/HTTP traces URL, including /v1/traces.

3 Instrument the Lambda handler#

Add a custom span to your Lambda handler.

1type MyEvent struct {
2    Name string `json:"name"`
3    Age  int    `json:"age"`
4}
5
6type MyResponse struct {
7    Message string `json:"message"`
8}
9
10func HandleLambdaEvent(ctx context.Context, event *MyEvent) (*MyResponse, error) {
11    tracer := otel.Tracer("lambda")
12    _, span := tracer.Start(ctx, "HandleLambdaEvent")
13    defer span.End()
14
15    span.SetAttributes(
16        attribute.String("user.name", event.Name),
17        attribute.Int("user.age", event.Age),
18    )
19
20    return &MyResponse{Message: "ok"}, nil
21}

4 Configure trace flushing#

Wrap the handler using otellambda.InstrumentHandler.

1func main() {
2    ctx := context.Background()
3
4    tp, err := initTracerProvider(ctx)
5    if err != nil {
6        log.Fatalf("failed to init tracer provider: %v", err)
7    }
8
9    defer tp.Shutdown(ctx)
10
11    lambda.Start(otellambda.InstrumentHandler(
12        HandleLambdaEvent,
13        otellambda.WithFlusher(tp),
14    ))
15}

WithFlusher helps export completed spans before the Lambda execution environment freezes.

Note: Include the required imports from the existing Go example and verify that the selected exporter supports your Middleware authentication configuration.

5 Deploy and verify#

Build and deploy your Go Lambda function.

Invoke the function and verify traces under APM → Services in Middleware.

Use the Middleware APM package to instrument supported .NET applications.

1 Verify the .NET version#

Ensure .NET 6 or later is installed in your development environment.

1dotnet --version

2 Install the Middleware APM package#

1dotnet add package MW.APM

3 Configure instrumentation#

Add the Middleware instrumentation configuration to your application startup.

For an ASP.NET Core application using the minimal hosting model:

1var builder = WebApplication.CreateBuilder(args);
2
3var configuration = new ConfigurationBuilder()
4    .SetBasePath(Directory.GetCurrentDirectory())
5    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
6    .AddEnvironmentVariables()
7    .Build();
8
9builder.Services.ConfigureMWInstrumentation(configuration);
10
11builder.Logging.AddConfiguration(configuration.GetSection("Logging"));
12builder.Logging.AddConsole();
13builder.Logging.SetMinimumLevel(LogLevel.Debug);
14
15var app = builder.Build();
16
17Logger.Init(app.Services.GetRequiredService<ILoggerFactory>());
18
19app.Run();

4 Configure Middleware credentials#

Add the following configuration to appsettings.json:

1{
2  "MW": {
3    "ApiKey": "<YOUR_MW_ACCOUNT_KEY>",
4    "TargetURL": "<MW_UID>.middleware.io:443",
5    "ServiceName": "<SERVICE_NAME>"
6  },
7  "Logging": {
8    "LogLevel": {
9      "Default": "Information",
10      "Microsoft.AspNetCore": "Warning"
11    }
12  }
13}

Replace the placeholders with your Middleware account details.

Note: The startup example above is for ASP.NET Core. A Lambda project using a different hosting model may require a different initialization method.

5 Deploy and verify#

Deploy the updated application to AWS Lambda.

Invoke the function and check APM → Services in Middleware for the configured service.

Verify Installation#

After instrumenting your Lambda function, invoke it to generate telemetry.

1 Invoke the function#

Use the AWS Lambda console to create and run a test event.

Alternatively, invoke the function using the AWS CLI:

1aws lambda invoke \
2  --function-name <FUNCTION_NAME> \
3  --payload '{}' \
4  --cli-binary-format raw-in-base64-out \
5  response.json

2 Verify traces#

  1. Log in to Middleware.
  2. Go to APM → Services.
  3. Find the service name configured using OTEL_SERVICE_NAME or the Middleware SDK configuration.
  4. Open the service to view its traces, errors, and latency.

3 Verify logs#

Go to Log Monitoring

If logs do not appear, verify that logging is enabled and that the configured Collector or SDK exports logs to Middleware.

AWS X-Ray tracing (optional)#

AWS X-Ray provides additional tracing context for Lambda functions and their AWS service dependencies.

To enable Active Tracing:

  1. Open AWS Console → Lambda → Functions.
  2. Select your Lambda function.
  3. Go to Configuration → Monitoring and operations tools.
  4. Under AWS X-Ray, enable Active tracing.
  5. Save the changes.

For W3C trace context propagation, use:

1OTEL_PROPAGATORS=tracecontext

If you need AWS X-Ray interoperability, configure compatible propagators rather than disabling AWS context propagation.

Environment variables reference#

VariableDescription
AWS_LAMBDA_EXEC_WRAPPERStarts runtime auto-instrumentation before the Lambda handler
OTEL_SERVICE_NAMEService name displayed in Middleware
OTEL_RESOURCE_ATTRIBUTESResource metadata, including Middleware account identification
OTEL_PROPAGATORSTrace context propagation format
OTEL_LAMBDA_DISABLE_AWS_CONTEXT_PROPAGATIONControls AWS-specific context propagation
OTEL_EXPORTER_OTLP_ENDPOINTBase endpoint for OTLP export
OTEL_EXPORTER_OTLP_TRACES_ENDPOINTSignal-specific endpoint for traces
OTEL_BSP_SCHEDULE_DELAYSpan batch processor delay in milliseconds
NODE_OPTIONSLoads the Node.js instrumentation configuration
OPENTELEMETRY_COLLECTOR_CONFIG_URIPath to the Collector configuration file
OPENTELEMETRY_EXTENSION_LOG_LEVELLogging level for the Collector extension
OTEL_LOGS_EXPORTERConfigures the OpenTelemetry logs exporter
OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLEDEnables Python logging instrumentation

Troubleshooting#

No traces in Middleware#

If traces do not appear after invoking your Lambda function:

  • Verify that the correct instrumentation layer is attached for your runtime, Region, and architecture.
  • Confirm that the required environment variables are configured.
  • Check that your Middleware target URL and account key are correct.
  • Verify that the Lambda function can reach Middleware over HTTPS.
  • Review the function's CloudWatch Logs for instrumentation or exporter errors.

Traces are missing or delayed#

Lambda functions can freeze before buffered spans are exported.

  • Reduce the batch span processor delay, for example OTEL_BSP_SCHEDULE_DELAY=500.
  • For Go, use otellambda.WithFlusher(tp) to flush spans after invocations.
  • Verify that the function timeout allows sufficient time for telemetry export.

Collector layer is not exporting data#

If you use the Collector Lambda layer:

  • Verify that collector.yaml is included in the deployment package.
  • Confirm that OPENTELEMETRY_COLLECTOR_CONFIG_URI points to the correct file.
  • Check the Collector exporter configuration and Middleware credentials.
  • Review CloudWatch Logs for Collector startup or export errors.

Node.js instrumentation is not loading#

If the Node.js wrapper starts but the bootstrap file does not load:

  • Verify that lambda-config.js is included in the deployment package.
  • Check the NODE_OPTIONS path.
  • For ESM applications, use a compatible --import configuration rather than CommonJS --require.

Python logs are missing#

If traces appear but Python application logs are missing:

  • Enable OpenTelemetry Python logging instrumentation.
  • Set OTEL_LOGS_EXPORTER=otlp when required.
  • Verify that the Collector has a logs pipeline.
  • Check for duplicate log propagation if logs appear more than once.

Go traces are not exported#

If Go instrumentation is enabled but traces do not appear:

  • Verify that the OTLP/HTTP traces endpoint includes /v1/traces.
  • Check that the exporter sends the required Middleware account information.
  • Confirm that the Lambda handler uses otellambda.InstrumentHandler.
  • Verify that spans are flushed before the invocation ends.

Lambda function cannot connect to Middleware#

If the function runs inside a VPC:

  • Verify that its subnet routing provides outbound access.
  • Configure a NAT Gateway or another appropriate egress path if required.
  • Confirm that security groups and network ACLs allow outbound HTTPS traffic.

Traces are not connected across services#

If traces appear separately instead of forming a distributed trace:

  • Use a consistent propagation format across services.
  • Verify that upstream services forward the required tracing headers.
  • Check AWS X-Ray and W3C propagation settings for compatibility.

If the issue persists, contact Middleware Support at [email protected].

Need assistance or want to learn more about Middleware? Get in touch with us via our Contact Us or join our Slack channel.

What did you think of this content?