Skip to content
Draft
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-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:
pass

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,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.

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

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 Down Expand Up @@ -61,3 +64,115 @@ def test_simple_create(self):
self.assertDictEqual(
actual.attributes.copy(), OrderedDict(MockLambdaResourceAttributes)
)

@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]}",
},
clear=True,
)
def test_account_id_from_symlink(self):
"""When the account ID symlink exists, cloud.account.id is set."""
symlink_path = None
try:
tmpdir = tempfile.mkdtemp()
symlink_path = os.path.join(tmpdir, ".otel-account-id")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just make this a fixture, preferably one that uses the existing tmp_path fixture from Pytest?

os.symlink("123456789012", symlink_path)
with patch(
"opentelemetry.sdk.extension.aws.resource._lambda._ACCOUNT_ID_SYMLINK_PATH",
symlink_path,
):
actual = AwsLambdaResourceDetector().detect()
self.assertEqual(
actual.attributes[ResourceAttributes.CLOUD_ACCOUNT_ID],
"123456789012",
)
finally:
if symlink_path and os.path.islink(symlink_path):
os.unlink(symlink_path)
if tmpdir:
os.rmdir(tmpdir)

@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]}",
},
clear=True,
)
def test_account_id_missing_symlink(self):
"""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-account-id-nonexistent",
):
actual = AwsLambdaResourceDetector().detect()
self.assertNotIn(
ResourceAttributes.CLOUD_ACCOUNT_ID, actual.attributes
)

@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]}",
},
clear=True,
)
def test_account_id_preserves_leading_zeros(self):
"""Leading zeros in the account ID are preserved (treated as string)."""
symlink_path = None
try:
tmpdir = tempfile.mkdtemp()
symlink_path = os.path.join(tmpdir, ".otel-account-id")
os.symlink("000123456789", symlink_path)
with patch(
"opentelemetry.sdk.extension.aws.resource._lambda._ACCOUNT_ID_SYMLINK_PATH",
symlink_path,
):
actual = AwsLambdaResourceDetector().detect()
self.assertEqual(
actual.attributes[ResourceAttributes.CLOUD_ACCOUNT_ID],
"000123456789",
)
finally:
if symlink_path and os.path.islink(symlink_path):
os.unlink(symlink_path)
if tmpdir:
os.rmdir(tmpdir)