@@ -63,6 +63,7 @@ def custom_event_context_extractor(lambda_event):
6363import logging
6464import os
6565import time
66+ from enum import Enum
6667from importlib import import_module
6768from typing import TYPE_CHECKING , Any , Callable , Collection
6869from urllib .parse import urlencode
@@ -71,6 +72,10 @@ def custom_event_context_extractor(lambda_event):
7172
7273from opentelemetry import context as context_api
7374from opentelemetry .context .context import Context
75+ from opentelemetry .instrumentation .aws_lambda ._sqs import (
76+ _is_sqs_event ,
77+ _run_sqs_handler ,
78+ )
7479from opentelemetry .instrumentation .aws_lambda .package import _instruments
7580from opentelemetry .instrumentation .aws_lambda .version import __version__
7681from opentelemetry .instrumentation .cidict import CIDict
@@ -85,6 +90,7 @@ def custom_event_context_extractor(lambda_event):
8590from opentelemetry .semconv ._incubating .attributes .faas_attributes import (
8691 FAAS_INVOCATION_ID ,
8792 FAAS_TRIGGER ,
93+ FaasTriggerValues ,
8894)
8995from 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)
100106from opentelemetry .trace import (
101- Span ,
102107 SpanKind ,
103108 TracerProvider ,
104109 get_tracer ,
@@ -109,6 +114,7 @@ def custom_event_context_extractor(lambda_event):
109114logger = logging .getLogger (__name__ )
110115
111116_HANDLER = "_HANDLER"
117+
112118_X_AMZN_TRACE_ID = "_X_AMZN_TRACE_ID"
113119ORIG_HANDLER = "ORIG_HANDLER"
114120OTEL_INSTRUMENTATION_AWS_LAMBDA_FLUSH_TIMEOUT = (
@@ -117,6 +123,7 @@ def custom_event_context_extractor(lambda_event):
117123
118124if 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+
142163def _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
285284def _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