Skip to content

Commit e47aff2

Browse files
committed
Add documentation.
1 parent 74d00ff commit e47aff2

File tree

12 files changed

+312
-6
lines changed

12 files changed

+312
-6
lines changed

aws_lambda_powertools/tracing/otel/tracer.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class TracerOpenTelemetry:
4646
----------
4747
mode : Literal["auto", "manual"]
4848
Instrumentation mode. "auto" uses global TracerProvider from OTel SDK (e.g., ADOT).
49-
"manual" uses provided TracerProvider or creates vanilla one.
49+
"manual" uses provided TracerProvider or creates default one.
5050
service : str, optional
5151
Service name for spans. Falls back to POWERTOOLS_SERVICE_NAME or Lambda function name.
5252
tracer_provider : TracerProvider, optional
@@ -117,11 +117,11 @@ def _resolve_tracer_provider(self, tracer_provider: SDKTracerProvider | None) ->
117117
raise ValueError("tracer_provider cannot be provided in auto mode")
118118
return None # Will use global provider
119119

120-
# Manual mode: use provided or create vanilla
120+
# Manual mode: use provided or create default
121121
if tracer_provider is not None:
122122
return tracer_provider
123123

124-
# Create vanilla TracerProvider
124+
# Create default TracerProvider
125125
from opentelemetry.sdk.trace import TracerProvider as SDKTracerProvider
126126

127127
return SDKTracerProvider()

docs/core/tracer-otel.md

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
---
2+
title: Tracer (OpenTelemetry)
3+
description: Core utility
4+
---
5+
6+
TracerOpenTelemetry is an OpenTelemetry-native tracer for AWS Lambda, providing distributed tracing with the [OpenTelemetry SDK](https://opentelemetry.io/docs/languages/python/){target="_blank" rel="nofollow"}.
7+
8+
## Key features
9+
10+
* Two modes: **auto** (ADOT Layer) and **manual** (custom TracerProvider)
11+
* Auto capture cold start as span attribute
12+
* Auto-disable when `POWERTOOLS_TRACE_DISABLED` is set
13+
* Support tracing async methods, generators, and context managers
14+
* SQS context propagation helpers
15+
16+
## Getting started
17+
18+
???+ tip
19+
All examples shared in this documentation are available within the [project repository](https://github.com/aws-powertools/powertools-lambda-python/tree/develop/examples){target="_blank"}.
20+
21+
### Install
22+
23+
!!! info "This is not necessary if you're installing Powertools for AWS Lambda (Python) via [Lambda Layer/SAR](../index.md#lambda-layer){target="_blank"}"
24+
25+
Add `aws-lambda-powertools[otel]` as a dependency in your preferred tool: _e.g._, _requirements.txt_, _pyproject.toml_. This will ensure you have the required dependencies before using TracerOpenTelemetry.
26+
27+
### Auto mode (ADOT Layer)
28+
29+
Use **auto mode** when deploying with the [AWS Distro for OpenTelemetry (ADOT) Lambda Layer](https://aws-otel.github.io/docs/getting-started/lambda){target="_blank" rel="nofollow"}. The ADOT Layer configures a global TracerProvider that exports spans automatically.
30+
31+
```python hl_lines="1 3 7" title="Auto mode with ADOT Layer"
32+
--8<-- "examples/tracer_otel/src/getting_started_auto_mode.py"
33+
```
34+
35+
`capture_lambda_handler` performs these additional tasks to ease operations:
36+
37+
* Adds a `faas.coldstart` attribute to easily filter traces that have had an initialization overhead
38+
* Adds a `service.name` attribute if `service` parameter or `POWERTOOLS_SERVICE_NAME` is set
39+
* Captures any response, or full exceptions generated by the handler, and includes as span attributes
40+
41+
### Manual mode
42+
43+
Use **manual mode** when you want full control over the TracerProvider configuration, or when not using the ADOT Layer.
44+
45+
```python hl_lines="1-3 8-9 11" title="Manual mode with custom TracerProvider"
46+
--8<-- "examples/tracer_otel/src/getting_started_manual_mode.py"
47+
```
48+
49+
If you don't provide a `tracer_provider`, a default TracerProvider is created that respects OpenTelemetry SDK environment variables.
50+
51+
### Synchronous functions
52+
53+
You can trace synchronous functions using the `capture_method` decorator.
54+
55+
```python hl_lines="7-8 12" title="Tracing an arbitrary function with capture_method"
56+
--8<-- "examples/tracer_otel/src/capture_method.py"
57+
```
58+
59+
### Asynchronous functions
60+
61+
You can trace asynchronous functions using `capture_method`.
62+
63+
```python hl_lines="8-10" title="Tracing async functions"
64+
--8<-- "examples/tracer_otel/src/capture_method_async.py"
65+
```
66+
67+
### Creating custom spans
68+
69+
Use `add_span` context manager to create child spans with custom attributes:
70+
71+
```python hl_lines="8-11" title="Creating custom spans with add_span"
72+
--8<-- "examples/tracer_otel/src/add_span.py"
73+
```
74+
75+
### Environment variables
76+
77+
The following environment variables are available to configure TracerOpenTelemetry at a global scope:
78+
79+
| Setting | Description | Environment variable | Default |
80+
|-----------------------|--------------------------------------------------|--------------------------------------|---------------|
81+
| **Disable Tracing** | Explicitly disables all tracing. | `POWERTOOLS_TRACE_DISABLED` | `false` |
82+
| **Service Name** | Sets the service name for spans. | `POWERTOOLS_SERVICE_NAME` | Function name |
83+
| **Response Capture** | Captures Lambda or method return as attribute. | `POWERTOOLS_TRACER_CAPTURE_RESPONSE` | `true` |
84+
| **Exception Capture** | Records exceptions in spans. | `POWERTOOLS_TRACER_CAPTURE_ERROR` | `true` |
85+
86+
## Advanced
87+
88+
### Disabling response auto-capture
89+
90+
Use **`capture_response=False`** parameter in both `capture_lambda_handler` and `capture_method` decorators to instruct Tracer **not** to serialize function responses as span attributes.
91+
92+
???+ info "Info: This is useful in two common scenarios"
93+
1. You might **return sensitive** information you don't want it to be added to your traces
94+
2. You might return **more than 1KB** of data which exceeds span attribute limits
95+
96+
```python hl_lines="6" title="Disabling response capture"
97+
--8<-- "examples/tracer_otel/src/disable_capture_response.py"
98+
```
99+
100+
### SQS context propagation
101+
102+
Propagate trace context through SQS messages to maintain distributed traces across services. This requires two separate Lambda functions: a **producer** that sends messages and a **consumer** that processes them.
103+
104+
The producer injects trace context into the message payload before sending to SQS. The consumer extracts this context to continue the same trace, linking both functions in your distributed trace.
105+
106+
=== "Producer Handler"
107+
108+
```python hl_lines="4 15-16" title="Inject trace context before sending to SQS"
109+
--8<-- "examples/tracer_otel/src/sqs_propagation_producer.py"
110+
```
111+
112+
=== "Consumer Handler"
113+
114+
```python hl_lines="4 16" title="Extract trace context from SQS message"
115+
--8<-- "examples/tracer_otel/src/sqs_propagation_consumer.py"
116+
```
117+
118+
### Cold start detection
119+
120+
TracerOpenTelemetry automatically adds a `faas.coldstart` attribute to the handler span:
121+
122+
* `true` on the first invocation after a cold start
123+
* `false` on subsequent warm invocations
124+
125+
### Accessing the current span
126+
127+
You can use `get_current_span` method to access the current active span and add custom attributes.
128+
129+
```python
130+
span = tracer.get_current_span()
131+
if span:
132+
span.set_attribute("custom_key", "custom_value")
133+
```
134+
135+
## Testing your code
136+
137+
TracerOpenTelemetry is disabled by default when `POWERTOOLS_TRACE_DISABLED` environment variable is set to `true`. This means you can disable tracing during tests without code changes.
138+
139+
## Comparison with X-Ray Tracer
140+
141+
| Feature | Tracer (X-Ray) | TracerOpenTelemetry |
142+
|---------|---------------|---------------------|
143+
| Backend | AWS X-Ray | Any OTel-compatible backend |
144+
| SDK | AWS X-Ray SDK | OpenTelemetry SDK |
145+
| Auto-patching | Yes (boto3, requests, etc.) | Via OTel instrumentation packages |
146+
| Annotations | `put_annotation()` | `span.set_attribute()` |
147+
| Metadata | `put_metadata()` | `span.set_attribute()` |
148+
| Cold start | Annotation | Span attribute |
149+
| Context propagation | X-Ray header | W3C Trace Context |
150+
151+
## API Reference
152+
153+
### TracerOpenTelemetry
154+
155+
```python
156+
TracerOpenTelemetry(
157+
mode: Literal["auto", "manual"] = "manual",
158+
service: str | None = None,
159+
tracer_provider: TracerProvider | None = None,
160+
disabled: bool | None = None,
161+
)
162+
```
163+
164+
**Parameters:**
165+
166+
| Parameter | Type | Description |
167+
|-----------|------|-------------|
168+
| `mode` | `"auto"` or `"manual"` | Instrumentation mode |
169+
| `service` | `str` | Service name for spans |
170+
| `tracer_provider` | `TracerProvider` | Custom provider (manual mode only) |
171+
| `disabled` | `bool` | Disable tracing |
172+
173+
**Methods:**
174+
175+
| Method | Description |
176+
|--------|-------------|
177+
| `capture_lambda_handler` | Decorator for Lambda handlers |
178+
| `capture_method` | Decorator for methods |
179+
| `add_span(name)` | Context manager for custom spans |
180+
| `get_current_span()` | Get the current active span |
181+
182+
### Context propagation
183+
184+
| Function | Description |
185+
|----------|-------------|
186+
| `inject_trace_context(carrier)` | Inject trace context into a dict |
187+
| `create_span_from_context(name, carrier)` | Create span from extracted context |
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from aws_lambda_powertools.tracing.otel import TracerOpenTelemetry
2+
3+
tracer = TracerOpenTelemetry(mode="auto")
4+
5+
6+
@tracer.capture_lambda_handler
7+
def lambda_handler(event, context):
8+
with tracer.add_span("process_order") as span:
9+
span.set_attribute("order_id", event.get("order_id", "unknown"))
10+
span.set_attribute("customer_tier", "premium")
11+
# Process order logic here
12+
result = {"order_id": event.get("order_id"), "status": "completed"}
13+
14+
return {"statusCode": 200, "body": result}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from aws_lambda_powertools.tracing.otel import TracerOpenTelemetry
2+
3+
tracer = TracerOpenTelemetry(mode="auto")
4+
5+
6+
@tracer.capture_method
7+
def process_payment(payment_id: str) -> dict:
8+
return {"payment_id": payment_id, "status": "processed"}
9+
10+
11+
@tracer.capture_lambda_handler
12+
def lambda_handler(event, context):
13+
result = process_payment(event.get("payment_id", "123"))
14+
return {"statusCode": 200, "body": result}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import asyncio
2+
3+
from aws_lambda_powertools.tracing.otel import TracerOpenTelemetry
4+
5+
tracer = TracerOpenTelemetry(mode="auto")
6+
7+
8+
@tracer.capture_method
9+
async def async_get_user(user_id: str) -> dict:
10+
await asyncio.sleep(0.1) # Simulate async I/O
11+
return {"user_id": user_id, "name": "John Doe"}
12+
13+
14+
@tracer.capture_lambda_handler
15+
def lambda_handler(event, context):
16+
user = asyncio.run(async_get_user(event.get("user_id", "123")))
17+
return {"statusCode": 200, "body": user}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from aws_lambda_powertools.tracing.otel import TracerOpenTelemetry
2+
3+
tracer = TracerOpenTelemetry(mode="auto")
4+
5+
6+
@tracer.capture_lambda_handler(capture_response=False)
7+
def lambda_handler(event, context):
8+
# Response won't be captured in span attributes
9+
return {"statusCode": 200, "body": "sensitive data"}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from aws_lambda_powertools.tracing.otel import TracerOpenTelemetry
2+
3+
tracer = TracerOpenTelemetry(mode="auto")
4+
5+
6+
@tracer.capture_lambda_handler
7+
def lambda_handler(event, context):
8+
return {"statusCode": 200, "body": "Hello, World!"}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
2+
from opentelemetry.sdk.trace import TracerProvider
3+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
4+
5+
from aws_lambda_powertools.tracing.otel import TracerOpenTelemetry
6+
7+
# Configure custom TracerProvider with OTLP exporter
8+
provider = TracerProvider()
9+
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
10+
11+
tracer = TracerOpenTelemetry(mode="manual", tracer_provider=provider, service="my-service")
12+
13+
14+
@tracer.capture_lambda_handler
15+
def lambda_handler(event, context):
16+
return {"statusCode": 200, "body": "Hello, World!"}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import json
2+
3+
from aws_lambda_powertools.tracing.otel import TracerOpenTelemetry
4+
from aws_lambda_powertools.tracing.otel.propagation import create_span_from_context
5+
6+
tracer = TracerOpenTelemetry(mode="auto")
7+
8+
9+
@tracer.capture_lambda_handler
10+
def lambda_handler(event, context):
11+
"""Consumer: Extract trace context from SQS message."""
12+
for record in event.get("Records", []):
13+
message = json.loads(record["body"])
14+
15+
# Continue the trace from the producer
16+
with create_span_from_context("process_order", message) as span:
17+
span.set_attribute("order_id", message.get("order_id"))
18+
# Process the order
19+
20+
return {"statusCode": 200}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import json
2+
3+
from aws_lambda_powertools.tracing.otel import TracerOpenTelemetry
4+
from aws_lambda_powertools.tracing.otel.propagation import inject_trace_context
5+
6+
tracer = TracerOpenTelemetry(mode="auto")
7+
8+
9+
@tracer.capture_lambda_handler
10+
def lambda_handler(event, context):
11+
"""Producer: Inject trace context into SQS message."""
12+
message = {"order_id": "12345", "amount": 99.99}
13+
14+
# Inject trace context for downstream consumers
15+
message_with_context = inject_trace_context(message)
16+
17+
# Send to SQS with: sqs.send_message(QueueUrl=queue_url, MessageBody=json.dumps(message_with_context))
18+
19+
return {"statusCode": 200, "body": json.dumps(message_with_context)}

0 commit comments

Comments
 (0)