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 @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- `opentelemetry-sdk-extension-aws`: Read `cloud.account.id` from symlink created by the OTel Lambda Extension in the Lambda resource detector
([#4183](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4183))
- `opentelemetry-instrumentation-asgi`: Add exemplars for `http.server.request.duration` and `http.server.duration` metrics
([#3739](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3739))
- `opentelemetry-instrumentation-wsgi`: Add exemplars for `http.server.request.duration` and `http.server.duration` metrics
Expand Down
3 changes: 3 additions & 0 deletions sdk-extension/opentelemetry-sdk-extension-aws/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

- Read `cloud.account.id` from symlink created by the OTel Lambda Extension in the Lambda resource detector
([#4183](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4183))

## Version 2.1.0 (2024-12-24)

- Make ec2 resource detector silent when loaded outside AWS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

import logging
import os
from os import environ

from opentelemetry.sdk.resources import Resource, ResourceDetector
Expand All @@ -24,6 +25,8 @@

logger = logging.getLogger(__name__)

_ACCOUNT_ID_SYMLINK_PATH = "/tmp/.otel-aws-account-id"


class AwsLambdaResourceDetector(ResourceDetector):
"""Detects attribute values only available when the app is running on AWS
Expand All @@ -34,25 +37,31 @@ class AwsLambdaResourceDetector(ResourceDetector):

def detect(self) -> "Resource":
try:
return Resource(
{
ResourceAttributes.CLOUD_PROVIDER: CloudProviderValues.AWS.value,
ResourceAttributes.CLOUD_PLATFORM: CloudPlatformValues.AWS_LAMBDA.value,
ResourceAttributes.CLOUD_REGION: environ["AWS_REGION"],
ResourceAttributes.FAAS_NAME: environ[
"AWS_LAMBDA_FUNCTION_NAME"
],
ResourceAttributes.FAAS_VERSION: environ[
"AWS_LAMBDA_FUNCTION_VERSION"
],
ResourceAttributes.FAAS_INSTANCE: environ[
"AWS_LAMBDA_LOG_STREAM_NAME"
],
ResourceAttributes.FAAS_MAX_MEMORY: int(
environ["AWS_LAMBDA_FUNCTION_MEMORY_SIZE"]
),
}
)
attributes = {
ResourceAttributes.CLOUD_PROVIDER: CloudProviderValues.AWS.value,
ResourceAttributes.CLOUD_PLATFORM: CloudPlatformValues.AWS_LAMBDA.value,
ResourceAttributes.CLOUD_REGION: environ["AWS_REGION"],
ResourceAttributes.FAAS_NAME: environ[
"AWS_LAMBDA_FUNCTION_NAME"
],
ResourceAttributes.FAAS_VERSION: environ[
"AWS_LAMBDA_FUNCTION_VERSION"
],
ResourceAttributes.FAAS_INSTANCE: environ[
"AWS_LAMBDA_LOG_STREAM_NAME"
],
ResourceAttributes.FAAS_MAX_MEMORY: int(
environ["AWS_LAMBDA_FUNCTION_MEMORY_SIZE"]
),
}

try:
account_id = os.readlink(_ACCOUNT_ID_SYMLINK_PATH)
attributes[ResourceAttributes.CLOUD_ACCOUNT_ID] = account_id
except OSError:
logger.debug("cloud.account.id not available via symlink")

return Resource(attributes)
# pylint: disable=broad-except
except Exception as exception:
if self.raise_on_error:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import unittest
from collections import OrderedDict
from unittest.mock import patch

import pytest

from opentelemetry.sdk.extension.aws.resource._lambda import ( # pylint: disable=no-name-in-module
AwsLambdaResourceDetector,
_ACCOUNT_ID_SYMLINK_PATH,
)
from opentelemetry.semconv.resource import (
CloudPlatformValues,
Expand All @@ -35,29 +39,68 @@
ResourceAttributes.FAAS_MAX_MEMORY: 128,
}

MOCK_LAMBDA_ENV = {
"AWS_REGION": MockLambdaResourceAttributes[ResourceAttributes.CLOUD_REGION],
"AWS_LAMBDA_FUNCTION_NAME": MockLambdaResourceAttributes[ResourceAttributes.FAAS_NAME],
"AWS_LAMBDA_FUNCTION_VERSION": MockLambdaResourceAttributes[ResourceAttributes.FAAS_VERSION],
"AWS_LAMBDA_LOG_STREAM_NAME": MockLambdaResourceAttributes[ResourceAttributes.FAAS_INSTANCE],
"AWS_LAMBDA_FUNCTION_MEMORY_SIZE": f"{MockLambdaResourceAttributes[ResourceAttributes.FAAS_MAX_MEMORY]}",
}


class AwsLambdaResourceDetectorTest(unittest.TestCase):
@patch.dict(
"os.environ",
{
"AWS_REGION": MockLambdaResourceAttributes[
ResourceAttributes.CLOUD_REGION
],
"AWS_LAMBDA_FUNCTION_NAME": MockLambdaResourceAttributes[
ResourceAttributes.FAAS_NAME
],
"AWS_LAMBDA_FUNCTION_VERSION": MockLambdaResourceAttributes[
ResourceAttributes.FAAS_VERSION
],
"AWS_LAMBDA_LOG_STREAM_NAME": MockLambdaResourceAttributes[
ResourceAttributes.FAAS_INSTANCE
],
"AWS_LAMBDA_FUNCTION_MEMORY_SIZE": f"{MockLambdaResourceAttributes[ResourceAttributes.FAAS_MAX_MEMORY]}",
},
MOCK_LAMBDA_ENV,
clear=True,
)
def test_simple_create(self):
actual = AwsLambdaResourceDetector().detect()
self.assertDictEqual(
actual.attributes.copy(), OrderedDict(MockLambdaResourceAttributes)
)


@pytest.fixture
def account_id_symlink(tmp_path):
"""Create a symlink at a temporary path and patch the detector to use it."""
def _create(account_id):
symlink_path = tmp_path / ".otel-aws-account-id"
os.symlink(account_id, symlink_path)
return str(symlink_path)
return _create


@patch.dict("os.environ", MOCK_LAMBDA_ENV, clear=True)
def test_account_id_from_symlink(account_id_symlink):
"""When the account ID symlink exists, cloud.account.id is set."""
symlink_path = account_id_symlink("123456789012")
with patch(
"opentelemetry.sdk.extension.aws.resource._lambda._ACCOUNT_ID_SYMLINK_PATH",
symlink_path,
):
actual = AwsLambdaResourceDetector().detect()
assert actual.attributes[ResourceAttributes.CLOUD_ACCOUNT_ID] == "123456789012"


@patch.dict("os.environ", MOCK_LAMBDA_ENV, clear=True)
def test_account_id_missing_symlink():
"""When the symlink does not exist, cloud.account.id is absent and no exception is raised."""
with patch(
"opentelemetry.sdk.extension.aws.resource._lambda._ACCOUNT_ID_SYMLINK_PATH",
"/tmp/.otel-aws-account-id-nonexistent",
):
actual = AwsLambdaResourceDetector().detect()
assert ResourceAttributes.CLOUD_ACCOUNT_ID not in actual.attributes


@patch.dict("os.environ", MOCK_LAMBDA_ENV, clear=True)
def test_account_id_preserves_leading_zeros(account_id_symlink):
"""Leading zeros in the account ID are preserved (treated as string)."""
symlink_path = account_id_symlink("000123456789")
with patch(
"opentelemetry.sdk.extension.aws.resource._lambda._ACCOUNT_ID_SYMLINK_PATH",
symlink_path,
):
actual = AwsLambdaResourceDetector().detect()
assert actual.attributes[ResourceAttributes.CLOUD_ACCOUNT_ID] == "000123456789"
Loading