Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#3777](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3777))
- `opentelemetry-exporter-credential-provider-gcp`: create this package which provides support for supplying your machine's Application Default Credentials (https://cloud.google.com/docs/authentication/application-default-credentials) to the OTLP Exporters created automatically by OpenTelemetry Python's auto instrumentation. These credentials authorize OTLP traces to be sent to `telemetry.googleapis.com`.
[#3766](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3766).
- `opentelemetry-instrumentation-botocore`: botocore: Add AWS_LAMBDA_RESOURCE_MAPPING_ID Semantic Convention Support for AWS Lambda SDK
([#3800](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3800))

## Version 1.37.0/0.58b0 (2025-09-11)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,27 @@
import inspect
import json
import re
from typing import Dict
from typing import Dict, Final

from opentelemetry.instrumentation.botocore.extensions.types import (
_AttributeMapT,
_AwsSdkCallContext,
_AwsSdkExtension,
_BotocoreInstrumentorContext,
_BotoResultT,
)
from opentelemetry.propagate import inject
from opentelemetry.semconv._incubating.attributes.aws_attributes import (
AWS_LAMBDA_RESOURCE_MAPPING_ID,
)
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.trace.span import Span

# Work is underway to add these two keys to the SemConv AWS registry, in line with other AWS resources.
# https://github.com/open-telemetry/semantic-conventions/blob/main/docs/registry/attributes/aws.md#amazon-lambda-attributes
AWS_LAMBDA_FUNCTION_ARN: Final = "aws.lambda.function.arn"
AWS_LAMBDA_FUNCTION_NAME: Final = "aws.lambda.function.name"


class _LambdaOperation(abc.ABC):
@classmethod
Expand Down Expand Up @@ -68,12 +77,16 @@ def extract_attributes(
)
attributes[SpanAttributes.FAAS_INVOKED_REGION] = call_context.region

@classmethod
def _parse_function_name(cls, call_context: _AwsSdkCallContext):
@staticmethod
def _parse_function_name(call_context: _AwsSdkCallContext):
function_name_or_arn = call_context.params.get("FunctionName")
matches = cls.ARN_LAMBDA_PATTERN.match(function_name_or_arn)
function_name = matches.group(1)
return function_name_or_arn if function_name is None else function_name
if function_name_or_arn is None:
return None
matches = _OpInvoke.ARN_LAMBDA_PATTERN.match(function_name_or_arn)
if matches:
function_name = matches.group(1)
return function_name if function_name else function_name_or_arn
return function_name_or_arn

@classmethod
def before_service_call(cls, call_context: _AwsSdkCallContext, span: Span):
Expand Down Expand Up @@ -115,6 +128,13 @@ def __init__(self, call_context: _AwsSdkCallContext):
self._op = _OPERATION_MAPPING.get(call_context.operation)

def extract_attributes(self, attributes: _AttributeMapT):
function_name = _OpInvoke._parse_function_name(self._call_context)
if function_name:
attributes[AWS_LAMBDA_FUNCTION_NAME] = function_name
resource_mapping_id = self._call_context.params.get("UUID")
if resource_mapping_id:
attributes[AWS_LAMBDA_RESOURCE_MAPPING_ID] = resource_mapping_id

if self._op is None:
return

Expand All @@ -127,3 +147,20 @@ def before_service_call(
return

self._op.before_service_call(self._call_context, span)

def on_success(
self,
span: Span,
result: _BotoResultT,
instrumentor_context: _BotocoreInstrumentorContext,
):
resource_mapping_id = result.get("UUID")
if resource_mapping_id:
span.set_attribute(
AWS_LAMBDA_RESOURCE_MAPPING_ID, resource_mapping_id
)

lambda_configuration = result.get("Configuration", {})
function_arn = lambda_configuration.get("FunctionArn")
if function_arn:
span.set_attribute(AWS_LAMBDA_FUNCTION_ARN, function_arn)
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import json
import sys
import zipfile
from typing import Final
from unittest import mock

import botocore.session
Expand All @@ -27,11 +28,17 @@
_LambdaExtension,
)
from opentelemetry.propagate import get_global_textmap, set_global_textmap
from opentelemetry.semconv._incubating.attributes.aws_attributes import (
AWS_LAMBDA_RESOURCE_MAPPING_ID,
)
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.test.mock_textmap import MockTextMapPropagator
from opentelemetry.test.test_base import TestBase
from opentelemetry.trace.span import Span

AWS_LAMBDA_FUNCTION_ARN: Final = "aws.lambda.function.arn"
AWS_LAMBDA_FUNCTION_NAME: Final = "aws.lambda.function.name"


def get_as_zip_file(file_name, content):
zip_output = io.BytesIO()
Expand Down Expand Up @@ -126,6 +133,18 @@ def _create_lambda_function(self, function_name: str, function_code: str):
Publish=True,
)

def _create_sqs_queue_and_get_arn(self) -> str:
"""Helper method to create SQS queue and return ARN"""
session = botocore.session.get_session()
session.set_credentials(
access_key="access-key", secret_key="secret-key"
)
sqs_client = session.create_client("sqs", region_name=self.region)
sqs_client.create_queue(
QueueName="MyTestQueue.fifo", Attributes={"FifoQueue": "true"}
)
return f"arn:aws:sqs:{self.region}:123456789012:MyTestQueue.fifo"

@mark.skip(reason="Docker error, unblocking builds for now.")
@mark.skipif(
sys.platform == "win32",
Expand Down Expand Up @@ -185,3 +204,94 @@ def test_invoke_parse_arn(self):
self.assertEqual(
function_name, attributes[SpanAttributes.FAAS_INVOKED_NAME]
)

@mock_aws
def test_get_function(self):
function_name = "lambda-function-name-foo"
self._create_lambda_function(
function_name, return_headers_lambda_str()
)

self.memory_exporter.clear()
self.client.get_function(FunctionName=function_name)
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(1, len(spans))
span = spans[0]
self.assertEqual(
"GetFunction", span.attributes[SpanAttributes.RPC_METHOD]
)
self.assertEqual(
"lambda-function-name-foo",
span.attributes[AWS_LAMBDA_FUNCTION_NAME],
)

function_arn = span.attributes.get(AWS_LAMBDA_FUNCTION_ARN)
self.assertIsNotNone(function_arn)
self.assertIn("lambda-function-name-foo", function_arn)
self.assertIsNone(span.attributes.get(AWS_LAMBDA_RESOURCE_MAPPING_ID))

@mock_aws
def test_create_event_source_mapping(self):
function_name = "MyLambdaFnFoo"
self._create_lambda_function(
function_name, return_headers_lambda_str()
)

queue_arn = self._create_sqs_queue_and_get_arn()
self.memory_exporter.clear()
response = self.client.create_event_source_mapping(
EventSourceArn=queue_arn, FunctionName=function_name, BatchSize=10
)
expected_uuid = response["UUID"]
self.assertIsNotNone(expected_uuid)
self.assertTrue(expected_uuid)

spans = self.memory_exporter.get_finished_spans()
self.assertEqual(1, len(spans))
span = spans[0]
self.assertEqual(
"CreateEventSourceMapping",
span.attributes[SpanAttributes.RPC_METHOD],
)
self.assertEqual(
"MyLambdaFnFoo", span.attributes[AWS_LAMBDA_FUNCTION_NAME]
)

uuid = span.attributes.get(AWS_LAMBDA_RESOURCE_MAPPING_ID)
self.assertIsNotNone(uuid)
self.assertEqual(expected_uuid, uuid)

@mock_aws
def test_get_event_source_mapping(self):
function_name = "MyLambdaFnBar"
self._create_lambda_function(
function_name, return_headers_lambda_str()
)

queue_arn = self._create_sqs_queue_and_get_arn()

# Create event source mapping first
create_response = self.client.create_event_source_mapping(
EventSourceArn=queue_arn, FunctionName=function_name, BatchSize=10
)
mapping_uuid = create_response["UUID"]

self.memory_exporter.clear()
response = self.client.get_event_source_mapping(UUID=mapping_uuid)
expected_uuid = response["UUID"]
self.assertIsNotNone(expected_uuid)
self.assertTrue(expected_uuid)

spans = self.memory_exporter.get_finished_spans()
self.assertEqual(1, len(spans))
span = spans[0]
self.assertEqual(
"GetEventSourceMapping",
span.attributes[SpanAttributes.RPC_METHOD],
)

uuid = span.attributes.get(AWS_LAMBDA_RESOURCE_MAPPING_ID)
self.assertIsNotNone(uuid)
self.assertEqual(expected_uuid, uuid)
self.assertIsNone(span.attributes.get(AWS_LAMBDA_FUNCTION_ARN))
self.assertIsNone(span.attributes.get(AWS_LAMBDA_FUNCTION_NAME))