Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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 @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#3765](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3765))
- Add `rstcheck` to pre-commit to stop introducing invalid RST
([#3777](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3777))
- `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))

- `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).
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,91 @@ 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)

@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)