AWS
autotel-aws provides ergonomic, vendor-agnostic OpenTelemetry instrumentation for AWS services.
Installation
Section titled “Installation”npm install autotel-aws autotelLambda Handler
Section titled “Lambda Handler”import { init } from 'autotel';import { wrapHandler } from 'autotel-aws/lambda';
init({ service: 'my-lambda', endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,});
export const handler = wrapHandler(async (event, context) => { return { statusCode: 200 };});Zero-Config
Section titled “Zero-Config”import 'autotel-aws/lambda/auto'; // Must be FIRST import
export const handler = async (event, context) => { return { statusCode: 200 };};AWS SDK Auto-Instrumentation
Section titled “AWS SDK Auto-Instrumentation”import { autoInstrumentAWS } from 'autotel-aws/sdk';
autoInstrumentAWS(); // All SDK clients are automatically traced
const s3 = new S3Client({ region: 'us-east-1' });const dynamodb = new DynamoDBClient({ region: 'us-east-1' });Per-Client
Section titled “Per-Client”import { instrumentSDK, createTracedClient } from 'autotel-aws/sdk';import { S3Client } from '@aws-sdk/client-s3';
// Wrap existing clientconst s3 = instrumentSDK(new S3Client({ region: 'us-east-1' }));
// Or create pre-instrumentedconst s3 = createTracedClient(S3Client, { region: 'us-east-1' });SQS Producer/Consumer
Section titled “SQS Producer/Consumer”import { SQSProducer, SQSConsumer } from 'autotel-aws/sqs';
const producer = new SQSProducer(sqs, { queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456/events',});
await producer.send({ body: JSON.stringify({ type: 'order.created', orderId: '123' }),});
const consumer = new SQSConsumer(sqs, { queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456/events', handleMessage: async (message) => { // Trace context automatically extracted from message console.log('Received:', message.body); },});Durable Functions
Section titled “Durable Functions”AWS’s OpenTelemetry plugin, @aws/durable-execution-sdk-js-otel, traces durable executions: a workflow span, an invocation span per Lambda invocation, and operation and attempt spans in one trace. autotel-aws/durable adds what the plugin needs when autotel owns the tracer provider:
import { init } from 'autotel';import { withDurableExecution } from '@aws/durable-execution-sdk-js';import { ExecutionOtelPlugin } from '@aws/durable-execution-sdk-js-otel';import { autotelContextExtractor, durableFlush } from 'autotel-aws/durable';
init({ service: 'orders' });
export const handler = withDurableExecution( async (event, ctx) => { const reserved = await ctx.step('reserve-inventory', () => reserve(event)); await ctx.wait({ seconds: 300 }); return ctx.step('charge', () => charge(reserved)); }, { plugins: [ new ExecutionOtelPlugin({ contextExtractor: autotelContextExtractor }), durableFlush(), ], },);durableFlush()flushes autotel at the end of each invocation, before Lambda freezes the environment.autotelContextExtractorkeeps the trace and parent from Lambda’s X-Ray header and lets autotel’s sampler decide. Lambda sendsSampled=0unless X-Ray active tracing is on.
Spans you create inside a step nest under its attempt span. InvocationOtelPlugin works the same way.
Chained invokes
Section titled “Chained invokes”Wrap the ctx.invoke() payload, and a durable target using autotelContextExtractor joins the caller’s trace:
import { withInvokeTraceContext } from 'autotel-aws/durable';
await ctx.invoke( 'validate-customer', 'arn:aws:lambda:eu-north-1:123456789012:function:customer-service:1', withInvokeTraceContext({ customerId }),);A plain Lambda target joins with wrapHandler(fn, { extractTraceContext: (e) => e._traceContext }).
Status-change consumers
Section titled “Status-change consumers”durableExecutionContext(event) gives a consumer of Durable Execution Status Change events the execution’s trace. Give the consumer’s role lambda:GetDurableExecution.
import { context } from '@opentelemetry/api';import { wrapHandler } from 'autotel-aws/lambda';import { durableExecutionContext, isDurableExecutionStatusChange,} from 'autotel-aws/durable';
const traced = wrapHandler(async (event) => notify(event.detail));
export const handler = async (event, lambdaContext) => isDurableExecutionStatusChange(event) ? context.with(await durableExecutionContext(event), () => traced(event, lambdaContext), ) : traced(event, lambdaContext);CloudWatch native OTLP
Section titled “CloudWatch native OTLP”As of late 2025 / early 2026, CloudWatch accepts OTLP/HTTP directly. No collector required for low-volume workloads.
| Signal | Endpoint | Lands in |
|---|---|---|
| Traces | https://xray.<region>.amazonaws.com/v1/traces |
X-Ray + Application Signals + Transaction Search |
| Logs | https://logs.<region>.amazonaws.com/v1/logs |
CloudWatch Logs (Logs Insights / LiveTail) |
| Metrics | https://monitoring.<region>.amazonaws.com/v1/metrics |
CloudWatch Metrics (PromQL-queryable) |
autotel-aws/cloudwatch ships SigV4-signing OTLP/JSON exporters that hit these endpoints directly. No sidecar, no extra layer.
Install peer dependencies
Section titled “Install peer dependencies”pnpm add autotel-aws autotel \ @smithy/signature-v4 @aws-crypto/sha256-js \ @aws-sdk/credential-providers \ @opentelemetry/sdk-trace-base \ @opentelemetry/sdk-logs \ @opentelemetry/sdk-metrics \ @opentelemetry/otlp-transformerDrop the signal-specific peers you don’t use (e.g. omit sdk-logs if you only export traces).
Traces
Section titled “Traces”import { init } from 'autotel';import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';import { CloudWatchTraceExporter } from 'autotel-aws/cloudwatch';
init({ service: 'my-service', spanProcessors: [ new BatchSpanProcessor( new CloudWatchTraceExporter({ region: 'eu-west-1' }), ), ],});In Lambda, force-flush before the handler returns (or use SimpleSpanProcessor). The runtime freezes the process before the batch ships.
The exporter reads AWS_LAMBDA_LOG_GROUP_NAME / AWS_LAMBDA_LOG_STREAM_NAME by default (set automatically by the Lambda runtime).
import { BatchLogRecordProcessor, LoggerProvider,} from '@opentelemetry/sdk-logs';import { CloudWatchLogExporter } from 'autotel-aws/cloudwatch';
const provider = new LoggerProvider({ processors: [new BatchLogRecordProcessor(new CloudWatchLogExporter())],});Metrics
Section titled “Metrics”import { MeterProvider, PeriodicExportingMetricReader,} from '@opentelemetry/sdk-metrics';import { CloudWatchMetricExporter } from 'autotel-aws/cloudwatch';
const provider = new MeterProvider({ readers: [ new PeriodicExportingMetricReader({ exporter: new CloudWatchMetricExporter({ region: 'eu-west-1' }), exportIntervalMillis: 60_000, }), ],});Direct vs ADOT collector layer
Section titled “Direct vs ADOT collector layer”In-process (autotel-aws/cloudwatch) |
ADOT Lambda extension layer | |
|---|---|---|
| Sidecar | None | Yes (extension layer) |
| Export latency | In the billed handler time | Outside billed time |
| Cold start cost | Bundle size + SigV4 init | Layer init |
| Best for | Low/medium volume Lambdas | Higher-throughput functions |
Pick one. Running both ships duplicate spans.
ADOT collector config (SigV4)
Section titled “ADOT collector config (SigV4)”When using OTEL_MODE=cloudwatch-adot, configure the ADOT collector with sigv4auth so OTLP is signed before forwarding:
extensions: sigv4auth: region: us-west-2 service: xray
receivers: otlp: protocols: http:
exporters: otlphttp/traces: endpoint: https://xray.us-west-2.amazonaws.com/v1/traces auth: authenticator: sigv4auth
service: extensions: [sigv4auth] pipelines: traces: receivers: [otlp] exporters: [otlphttp/traces]For logs/metrics, use the same pattern with endpoint + service pairs:
- logs:
https://logs.<region>.amazonaws.com/v1/logs, servicelogs - metrics:
https://monitoring.<region>.amazonaws.com/v1/metrics, servicemonitoring
Production checklist
Section titled “Production checklist”- Set region explicitly (
AWS_REGION) and match it to the endpoint region. - Ensure IAM policy allows writes to X-Ray, CloudWatch Logs, and CloudWatch Metrics as needed.
- For logs endpoint, make sure target log group + stream exist, or use Lambda-provided env defaults.
- In Lambda, flush spans/logs/metrics before return (or use ADOT mode) to avoid frozen-runtime drops.
- Monitor 4xx/429 responses and tune export interval/batch size for quota limits.
- Prefer one export path per signal (direct or ADOT), not both, to avoid duplicates.
- Keep payload size under endpoint limits and use gzip where appropriate.
- Validate clocks (NTP) to avoid timestamp-window rejections.
Payload format note
Section titled “Payload format note”autotel-aws/cloudwatch currently sends OTLP over HTTP using JSON payloads. CloudWatch endpoints also accept protobuf payloads, but protobuf transport is not currently wired in these exporters.
Endpoint limits
Section titled “Endpoint limits”- Traces: 5 MB / request, 10 000 spans / batch, 200 KB / span, timestamps must be within
[-14d, +2h]. - Logs: 1 MB / request (20 MB in some regions for Large Log Objects). 1 MB / log event before truncation.
- Metrics: 500 TPS / account, 150 labels / datapoint, 1 MB / request, 1 000 datapoints / request.
Full details: CloudWatch OTLP endpoint docs.
Semantic Helpers
Section titled “Semantic Helpers”import { traceS3, traceDynamoDB } from 'autotel-aws';
// S3 with automatic semantic attributesexport const uploadFile = traceS3({ bucket: 'my-bucket', operation: 'PutObject',})((ctx) => async (key: string, body: Buffer) => { return s3.putObject({ Bucket: 'my-bucket', Key: key, Body: body });});Attribute builders
Section titled “Attribute builders”When you instrument an AWS call by hand, autotel-aws/attributes builds the
canonical span attributes for you. AWS_ATTRS holds the semantic-convention
keys; the build* helpers turn a service operation into the attribute object.
import { buildLambdaAttributes, buildDynamoDBAttributes,} from 'autotel-aws/attributes';
span.setAttributes( buildLambdaAttributes({ functionName: 'my-handler', functionVersion: '$LATEST', awsRequestId: context.awsRequestId, coldStart: true, }),);Builders exist for Lambda, the SDK, DynamoDB, S3, SQS, SNS, Kinesis, Step Functions, and EventBridge.
Testing
Section titled “Testing”autotel-aws/testing runs Lambda handlers against a mock context and helps you
assert on the spans they produce, with optional LocalStack helpers.
import { createLambdaTestHarness } from 'autotel-aws/testing';
const harness = createLambdaTestHarness();const result = await harness.invoke(handler, mockEvent, mockContext);expect(result.statusCode).toBe(200);Examples
Section titled “Examples”example-aws-lambda: Lambda withwrapHandler(), AWS SDK v3 auto-instrumentation, S3/DynamoDB/SQS helpers, and LocalStack support.
Entry Points
Section titled “Entry Points”autotel-aws/lambda: Lambda handler wrappingautotel-aws/lambda/auto: Zero-config Lambdaautotel-aws/sdk: AWS SDK instrumentationautotel-aws/sqs: SQS producer/consumerautotel-aws/sns: SNS publisherautotel-aws/kinesis: Kinesis producer/consumerautotel-aws/step-functions: Step Functionsautotel-aws/eventbridge: EventBridge publisherautotel-aws/durable: Durable functions additions to AWS’s OpenTelemetry pluginautotel-aws/xray: X-Ray compatibilityautotel-aws/cloudwatch: SigV4-signed OTLP exporters for CloudWatch’s native endpointsautotel-aws/attributes: Attribute builders for hand-rolled instrumentationautotel-aws/testing: Test utilities