@@ -69,11 +69,13 @@ def custom_event_context_extractor(lambda_event):
6969---
7070"""
7171
72+ from __future__ import annotations
73+
7274import logging
7375import os
7476import time
7577from importlib import import_module
76- from typing import Any , Callable , Collection
78+ from typing import TYPE_CHECKING , Any , Callable , Collection
7779from urllib .parse import urlencode
7880
7981from wrapt import wrap_function_wrapper
@@ -123,6 +125,29 @@ def custom_event_context_extractor(lambda_event):
123125 "OTEL_INSTRUMENTATION_AWS_LAMBDA_FLUSH_TIMEOUT"
124126)
125127
128+ if TYPE_CHECKING :
129+ import typing
130+
131+ class LambdaContext (typing .Protocol ):
132+ """Type definition for AWS Lambda context object.
133+
134+ This Protocol defines the interface for the context object passed to Lambda
135+ function handlers, providing information about the invocation, function, and
136+ execution environment.
137+
138+ See Also:
139+ AWS Lambda Context Object documentation:
140+ https://docs.aws.amazon.com/lambda/latest/dg/python-context.html
141+ """
142+
143+ function_name : str
144+ function_version : str
145+ invoked_function_arn : str
146+ memory_limit_in_mb : int
147+ aws_request_id : str
148+ log_group_name : str
149+ log_stream_name : str
150+
126151
127152def _default_event_context_extractor (lambda_event : Any ) -> Context :
128153 """Default way of extracting the context from the Lambda Event.
@@ -264,6 +289,50 @@ def _set_api_gateway_v2_proxy_attributes(
264289 return span
265290
266291
292+ def _get_lambda_context_attributes (
293+ lambda_context : LambdaContext ,
294+ ) -> dict [str , str ]:
295+ """Extracts OpenTelemetry span attributes from AWS Lambda context.
296+
297+ Extract FaaS specific attributes from the AWS Lambda context
298+ according to OpenTelemetry semantic conventions for FaaS & AWS Lambda.
299+
300+ Args:
301+ lambda_context: The AWS Lambda context object.
302+
303+ Returns:
304+ A dictionary mapping of OpenTelemetry attribute names to their values.
305+ """
306+ function_arn_parts : list [str ] = lambda_context .invoked_function_arn .split (
307+ ":"
308+ )
309+ # NOTE: `cloud.account.id` can be parsed from the ARN as the fifth item when splitting on `:`
310+ #
311+ # See more:
312+ # https://github.com/open-telemetry/semantic-conventions/blob/main/docs/faas/aws-lambda.md#all-triggers
313+ aws_account_id : str = function_arn_parts [4 ]
314+ # NOTE: The unmodified function ARN may contain an alias extension e.g.
315+ # `arn:aws:lambda:region:account:function:name:alias`. We can ensure
316+ # the alias extension is not included in the `cloud.resource_id` by keeping
317+ # only the first 7 parts of the original ARN.
318+ #
319+ # See more:
320+ # https://docs.aws.amazon.com/lambda/latest/dg/python-context.html
321+ formatted_function_arn : str = ":" .join (function_arn_parts [:7 ])
322+
323+ # NOTE: The specs mention an exception here, allowing the
324+ # `SpanAttributes.CLOUD_RESOURCE_ID` attribute to be set as a span
325+ # attribute instead of a resource attribute.
326+ #
327+ # See more:
328+ # https://github.com/open-telemetry/semantic-conventions/blob/main/docs/faas/aws-lambda.md#resource-detector
329+ return {
330+ CLOUD_ACCOUNT_ID : aws_account_id ,
331+ CLOUD_RESOURCE_ID : formatted_function_arn ,
332+ FAAS_INVOCATION_ID : lambda_context .aws_request_id ,
333+ }
334+
335+
267336# pylint: disable=too-many-statements
268337def _instrument (
269338 wrapped_module_name ,
@@ -278,38 +347,14 @@ def _instrument(
278347 def _instrumented_lambda_handler_call ( # noqa pylint: disable=too-many-branches
279348 call_wrapped , instance , args , kwargs
280349 ):
281- orig_handler_name = "." .join (
282- [wrapped_module_name , wrapped_function_name ]
283- )
284-
285- lambda_event = args [0 ]
350+ lambda_event : Any = args [0 ]
351+ lambda_context : LambdaContext = args [1 ]
286352
287353 parent_context = _determine_parent_context (
288354 lambda_event ,
289355 event_context_extractor ,
290356 )
291357
292- try :
293- event_source = lambda_event ["Records" ][0 ].get (
294- "eventSource"
295- ) or lambda_event ["Records" ][0 ].get ("EventSource" )
296- if event_source in {
297- "aws:sqs" ,
298- "aws:s3" ,
299- "aws:sns" ,
300- "aws:dynamodb" ,
301- }:
302- # See more:
303- # https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html
304- # https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html
305- # https://docs.aws.amazon.com/AmazonS3/latest/userguide/notification-content-structure.html
306- # https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html
307- span_kind = SpanKind .CONSUMER
308- else :
309- span_kind = SpanKind .SERVER
310- except (IndexError , KeyError , TypeError ):
311- span_kind = SpanKind .SERVER
312-
313358 tracer = get_tracer (
314359 __name__ ,
315360 __version__ ,
@@ -320,38 +365,10 @@ def _instrumented_lambda_handler_call( # noqa pylint: disable=too-many-branches
320365 token = context_api .attach (parent_context )
321366 try :
322367 with tracer .start_as_current_span (
323- name = orig_handler_name ,
324- kind = span_kind ,
368+ name = lambda_context .function_name ,
369+ kind = SpanKind .SERVER ,
370+ attributes = _get_lambda_context_attributes (lambda_context ),
325371 ) as span :
326- if span .is_recording ():
327- lambda_context = args [1 ]
328- # NOTE: The specs mention an exception here, allowing the
329- # `CLOUD_RESOURCE_ID` attribute to be set as a span
330- # attribute instead of a resource attribute.
331- #
332- # See more:
333- # https://github.com/open-telemetry/semantic-conventions/blob/main/docs/faas/aws-lambda.md#resource-detector
334- span .set_attribute (
335- CLOUD_RESOURCE_ID ,
336- lambda_context .invoked_function_arn ,
337- )
338- span .set_attribute (
339- FAAS_INVOCATION_ID ,
340- lambda_context .aws_request_id ,
341- )
342-
343- # NOTE: `cloud.account.id` can be parsed from the ARN as the fifth item when splitting on `:`
344- #
345- # See more:
346- # https://github.com/open-telemetry/semantic-conventions/blob/main/docs/faas/aws-lambda.md#all-triggers
347- account_id = lambda_context .invoked_function_arn .split (
348- ":"
349- )[4 ]
350- span .set_attribute (
351- CLOUD_ACCOUNT_ID ,
352- account_id ,
353- )
354-
355372 exception = None
356373 result = None
357374 try :
0 commit comments