Skip to content

Commit aa3fdc6

Browse files
herin049xrmx
andauthored
opentelemetry-instrumentation-aws-lambda: add SQS context propagation support (#4668)
* opentelemetry-instrumentation-aws-lambda: add SQS context propagation support * add changelog fragment * small update to comments * Cleanup methods to not modify in place the arguments --------- Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
1 parent 6a42e58 commit aa3fdc6

5 files changed

Lines changed: 857 additions & 88 deletions

File tree

.changelog/4668.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
`opentelemetry-instrumentation-aws-lambda`: add SQS context propagation support

instrumentation/opentelemetry-instrumentation-aws-lambda/src/opentelemetry/instrumentation/aws_lambda/__init__.py

Lines changed: 92 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def custom_event_context_extractor(lambda_event):
6363
import logging
6464
import os
6565
import time
66+
from enum import Enum
6667
from importlib import import_module
6768
from typing import TYPE_CHECKING, Any, Callable, Collection
6869
from urllib.parse import urlencode
@@ -71,6 +72,10 @@ def custom_event_context_extractor(lambda_event):
7172

7273
from opentelemetry import context as context_api
7374
from opentelemetry.context.context import Context
75+
from opentelemetry.instrumentation.aws_lambda._sqs import (
76+
_is_sqs_event,
77+
_run_sqs_handler,
78+
)
7479
from opentelemetry.instrumentation.aws_lambda.package import _instruments
7580
from opentelemetry.instrumentation.aws_lambda.version import __version__
7681
from opentelemetry.instrumentation.cidict import CIDict
@@ -85,6 +90,7 @@ def custom_event_context_extractor(lambda_event):
8590
from opentelemetry.semconv._incubating.attributes.faas_attributes import (
8691
FAAS_INVOCATION_ID,
8792
FAAS_TRIGGER,
93+
FaasTriggerValues,
8894
)
8995
from opentelemetry.semconv._incubating.attributes.http_attributes import (
9096
HTTP_METHOD,
@@ -98,7 +104,6 @@ def custom_event_context_extractor(lambda_event):
98104
NET_HOST_NAME,
99105
)
100106
from opentelemetry.trace import (
101-
Span,
102107
SpanKind,
103108
TracerProvider,
104109
get_tracer,
@@ -109,6 +114,7 @@ def custom_event_context_extractor(lambda_event):
109114
logger = logging.getLogger(__name__)
110115

111116
_HANDLER = "_HANDLER"
117+
112118
_X_AMZN_TRACE_ID = "_X_AMZN_TRACE_ID"
113119
ORIG_HANDLER = "ORIG_HANDLER"
114120
OTEL_INSTRUMENTATION_AWS_LAMBDA_FLUSH_TIMEOUT = (
@@ -117,6 +123,7 @@ def custom_event_context_extractor(lambda_event):
117123

118124
if TYPE_CHECKING:
119125
import typing
126+
from collections.abc import MutableMapping
120127

121128
class LambdaContext(typing.Protocol):
122129
"""Type definition for AWS Lambda context object.
@@ -139,6 +146,20 @@ class LambdaContext(typing.Protocol):
139146
log_stream_name: str
140147

141148

149+
class _LambdaEventType(Enum):
150+
SQS = "sqs"
151+
API_GATEWAY = "api_gateway"
152+
UNKNOWN = "unknown"
153+
154+
155+
def _get_lambda_event_type(lambda_event: Any) -> _LambdaEventType:
156+
if _is_sqs_event(lambda_event):
157+
return _LambdaEventType.SQS
158+
if isinstance(lambda_event, dict) and lambda_event.get("requestContext"):
159+
return _LambdaEventType.API_GATEWAY
160+
return _LambdaEventType.UNKNOWN
161+
162+
142163
def _default_event_context_extractor(lambda_event: Any) -> Context:
143164
"""Default way of extracting the context from the Lambda Event.
144165
@@ -197,89 +218,67 @@ def _determine_parent_context(
197218
return event_context_extractor(lambda_event)
198219

199220

200-
def _set_api_gateway_v1_proxy_attributes(
201-
lambda_event: Any, span: Span
202-
) -> Span:
221+
def _get_api_gateway_v1_proxy_attributes(
222+
lambda_event: Any,
223+
) -> MutableMapping[str, Any]:
203224
"""Sets HTTP attributes for REST APIs and v1 HTTP APIs
204225
205226
More info:
206227
https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
207228
"""
208-
span.set_attribute(HTTP_METHOD, lambda_event.get("httpMethod"))
229+
attributes = {}
230+
attributes[HTTP_METHOD] = lambda_event.get("httpMethod")
209231

210232
if lambda_event.get("headers"):
211233
headers = CIDict(lambda_event["headers"])
212234
if "User-Agent" in headers:
213-
span.set_attribute(
214-
HTTP_USER_AGENT,
215-
headers["User-Agent"],
216-
)
235+
attributes[HTTP_USER_AGENT] = headers["User-Agent"]
217236
if "X-Forwarded-Proto" in headers:
218-
span.set_attribute(
219-
HTTP_SCHEME,
220-
headers["X-Forwarded-Proto"],
221-
)
237+
attributes[HTTP_SCHEME] = headers["X-Forwarded-Proto"]
222238
if "Host" in headers:
223-
span.set_attribute(
224-
NET_HOST_NAME,
225-
headers["Host"],
226-
)
239+
attributes[NET_HOST_NAME] = headers["Host"]
240+
227241
if "resource" in lambda_event:
228-
span.set_attribute(HTTP_ROUTE, lambda_event["resource"])
242+
attributes[HTTP_ROUTE] = lambda_event["resource"]
229243

230244
if lambda_event.get("queryStringParameters"):
231-
span.set_attribute(
232-
HTTP_TARGET,
233-
f"{lambda_event['resource']}?{urlencode(lambda_event['queryStringParameters'])}",
245+
attributes[HTTP_TARGET] = (
246+
f"{lambda_event['resource']}?{urlencode(lambda_event['queryStringParameters'])}"
234247
)
235248
else:
236-
span.set_attribute(HTTP_TARGET, lambda_event["resource"])
249+
attributes[HTTP_TARGET] = lambda_event["resource"]
250+
return attributes
237251

238-
return span
239252

240-
241-
def _set_api_gateway_v2_proxy_attributes(
242-
lambda_event: Any, span: Span
243-
) -> Span:
253+
def _get_api_gateway_v2_proxy_attributes(
254+
lambda_event: Any,
255+
) -> MutableMapping[str, Any]:
244256
"""Sets HTTP attributes for v2 HTTP APIs
245257
246258
More info:
247259
https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
248260
"""
261+
attributes = {}
249262
if "domainName" in lambda_event["requestContext"]:
250-
span.set_attribute(
251-
NET_HOST_NAME,
252-
lambda_event["requestContext"]["domainName"],
253-
)
263+
attributes[NET_HOST_NAME] = lambda_event["requestContext"][
264+
"domainName"
265+
]
254266

255267
if lambda_event["requestContext"].get("http"):
256-
if "method" in lambda_event["requestContext"]["http"]:
257-
span.set_attribute(
258-
HTTP_METHOD,
259-
lambda_event["requestContext"]["http"]["method"],
260-
)
261-
if "userAgent" in lambda_event["requestContext"]["http"]:
262-
span.set_attribute(
263-
HTTP_USER_AGENT,
264-
lambda_event["requestContext"]["http"]["userAgent"],
265-
)
266-
if "path" in lambda_event["requestContext"]["http"]:
267-
span.set_attribute(
268-
HTTP_ROUTE,
269-
lambda_event["requestContext"]["http"]["path"],
270-
)
268+
http = lambda_event["requestContext"]["http"]
269+
if "method" in http:
270+
attributes[HTTP_METHOD] = http["method"]
271+
if "userAgent" in http:
272+
attributes[HTTP_USER_AGENT] = http["userAgent"]
273+
if "path" in http:
274+
attributes[HTTP_ROUTE] = http["path"]
271275
if lambda_event.get("rawQueryString"):
272-
span.set_attribute(
273-
HTTP_TARGET,
274-
f"{lambda_event['requestContext']['http']['path']}?{lambda_event['rawQueryString']}",
276+
attributes[HTTP_TARGET] = (
277+
f"{http['path']}?{lambda_event['rawQueryString']}"
275278
)
276279
else:
277-
span.set_attribute(
278-
HTTP_TARGET,
279-
lambda_event["requestContext"]["http"]["path"],
280-
)
281-
282-
return span
280+
attributes[HTTP_TARGET] = http["path"]
281+
return attributes
283282

284283

285284
def _get_lambda_context_attributes(
@@ -356,43 +355,57 @@ def _instrumented_lambda_handler_call( # noqa pylint: disable=too-many-branches
356355
)
357356

358357
token = context_api.attach(parent_context)
358+
event_type = _get_lambda_event_type(lambda_event)
359+
span_attributes: MutableMapping[str, Any] = (
360+
_get_lambda_context_attributes(lambda_context)
361+
)
362+
if event_type is _LambdaEventType.SQS:
363+
span_attributes[FAAS_TRIGGER] = FaasTriggerValues.PUBSUB.value
364+
elif event_type is _LambdaEventType.API_GATEWAY:
365+
# If the request came from an API Gateway, extract http attributes from the event
366+
# https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/instrumentation/aws-lambda.md#api-gateway
367+
# https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-server-semantic-conventions
368+
span_attributes[FAAS_TRIGGER] = "http"
369+
if lambda_event.get("version") == "2.0":
370+
span_attributes |= _get_api_gateway_v2_proxy_attributes(
371+
lambda_event
372+
)
373+
else:
374+
span_attributes |= _get_api_gateway_v1_proxy_attributes(
375+
lambda_event
376+
)
377+
359378
try:
360379
with tracer.start_as_current_span(
361380
name=lambda_context.function_name,
362381
kind=SpanKind.SERVER,
363-
attributes=_get_lambda_context_attributes(lambda_context),
382+
attributes=span_attributes,
364383
) as span:
365384
exception = None
366385
result = None
386+
367387
try:
368-
result = call_wrapped(*args, **kwargs)
369-
except Exception as exc: # pylint: disable=W0703
388+
if event_type is _LambdaEventType.SQS:
389+
result = _run_sqs_handler(
390+
tracer, lambda_event, call_wrapped, args, kwargs
391+
)
392+
else:
393+
result = call_wrapped(*args, **kwargs)
394+
# pylint: disable-next=broad-exception-caught
395+
except Exception as exc:
370396
exception = exc
371397
span.set_status(Status(StatusCode.ERROR))
372398
span.record_exception(exception)
373399

374-
# If the request came from an API Gateway, extract http attributes from the event
375-
# https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/instrumentation/aws-lambda.md#api-gateway
376-
# https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-server-semantic-conventions
377-
if isinstance(lambda_event, dict) and lambda_event.get(
378-
"requestContext"
400+
if (
401+
event_type is _LambdaEventType.API_GATEWAY
402+
and isinstance(result, dict)
403+
and result.get("statusCode")
379404
):
380-
span.set_attribute(FAAS_TRIGGER, "http")
381-
382-
if lambda_event.get("version") == "2.0":
383-
_set_api_gateway_v2_proxy_attributes(
384-
lambda_event, span
385-
)
386-
else:
387-
_set_api_gateway_v1_proxy_attributes(
388-
lambda_event, span
389-
)
390-
391-
if isinstance(result, dict) and result.get("statusCode"):
392-
span.set_attribute(
393-
HTTP_STATUS_CODE,
394-
result.get("statusCode"),
395-
)
405+
span.set_attribute(
406+
HTTP_STATUS_CODE,
407+
result.get("statusCode"),
408+
)
396409
finally:
397410
if token:
398411
context_api.detach(token)

0 commit comments

Comments
 (0)