Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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 @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

- `opentelemetry-sdk`: Add `container` resource detector support to declarative file configuration via `detection_development.detectors[].container`, using a lazy import of the `opentelemetry-resource-detector-containerid` contrib package
([#5004](https://github.com/open-telemetry/opentelemetry-python/pull/5004))
- `opentelemetry-sdk`: Add `create_resource` and `create_propagator`/`configure_propagator` to declarative file configuration, enabling Resource and propagator instantiation from config files without reading env vars
([#4979](https://github.com/open-telemetry/opentelemetry-python/pull/4979))
- `opentelemetry-sdk`: Map Python `CRITICAL` log level to OTel `FATAL` severity text per the specification
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,28 @@ def _run_detectors(
is updated in-place; later detectors overwrite earlier ones for the
same key.
"""
if detector_config.container is not None:
# The container detector is not part of the core SDK. It is provided
# by the opentelemetry-resource-detector-containerid contrib package.
# We attempt a lazy import so the core SDK has no hard dependency on
# contrib; if the package is absent we log an actionable warning rather
# than raising an error. Other SDKs (e.g. JS) similarly skip container
# detection when no implementation is available. See also:
# https://github.com/open-telemetry/opentelemetry-configuration/issues/570
try:
from opentelemetry.resource.detector.containerid import ( # type: ignore[import-not-found] # noqa: PLC0415 # pylint: disable=import-outside-toplevel,no-name-in-module
ContainerResourceDetector,
)

detected_attrs.update(
ContainerResourceDetector().detect().attributes
)
except ImportError:
_logger.warning(
"container resource detector requested but "
"'opentelemetry-resource-detector-containerid' is not "
"installed; install it to enable container detection"
)


def _filter_attributes(
Expand Down
90 changes: 89 additions & 1 deletion opentelemetry-sdk/tests/_configuration/test_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@

import os
import unittest
from unittest.mock import patch
from unittest.mock import MagicMock, patch

from opentelemetry.sdk._configuration._resource import create_resource
from opentelemetry.sdk._configuration.models import (
AttributeNameValue,
AttributeType,
ExperimentalResourceDetection,
ExperimentalResourceDetector,
)
from opentelemetry.sdk._configuration.models import Resource as ResourceConfig
from opentelemetry.sdk.resources import (
CONTAINER_ID,
SERVICE_NAME,
TELEMETRY_SDK_LANGUAGE,
TELEMETRY_SDK_NAME,
Expand Down Expand Up @@ -295,3 +298,88 @@ def test_attributes_list_invalid_pair_skipped(self):
self.assertEqual(resource.attributes["foo"], "bar")
self.assertNotIn("no-equals", resource.attributes)
self.assertTrue(any("no-equals" in msg for msg in cm.output))


class TestContainerResourceDetector(unittest.TestCase):
@staticmethod
def _config_with_container() -> ResourceConfig:
return ResourceConfig(
detection_development=ExperimentalResourceDetection(
detectors=[ExperimentalResourceDetector(container={})]
)
)

def test_container_detector_not_run_when_absent(self):
resource = create_resource(ResourceConfig())
self.assertNotIn(CONTAINER_ID, resource.attributes)

def test_container_detector_not_run_when_detection_development_is_none(
self,
):
resource = create_resource(ResourceConfig(detection_development=None))
self.assertNotIn(CONTAINER_ID, resource.attributes)

def test_container_detector_not_run_when_detectors_list_empty(self):
config = ResourceConfig(
detection_development=ExperimentalResourceDetection(detectors=[])
)
resource = create_resource(config)
self.assertNotIn(CONTAINER_ID, resource.attributes)

def test_container_detector_warns_when_package_missing(self):
"""A warning is logged when the contrib package is not installed."""
with patch.dict(
"sys.modules",
{"opentelemetry.resource.detector.containerid": None},
):
with self.assertLogs(
"opentelemetry.sdk._configuration._resource", level="WARNING"
) as cm:
resource = create_resource(self._config_with_container())
self.assertNotIn(CONTAINER_ID, resource.attributes)
self.assertTrue(
any(
"opentelemetry-resource-detector-containerid" in msg
for msg in cm.output
)
)

def test_container_detector_uses_contrib_when_available(self):
"""When the contrib package is installed, container.id is detected."""
mock_resource = Resource({CONTAINER_ID: "abc123"})
mock_detector = MagicMock()
mock_detector.return_value.detect.return_value = mock_resource
mock_module = MagicMock()
mock_module.ContainerResourceDetector = mock_detector

with patch.dict(
"sys.modules",
{"opentelemetry.resource.detector.containerid": mock_module},
):
resource = create_resource(self._config_with_container())

self.assertEqual(resource.attributes[CONTAINER_ID], "abc123")

def test_explicit_attributes_override_container_detector(self):
"""Config attributes win over detector-provided values."""
mock_resource = Resource({CONTAINER_ID: "detected-id"})
mock_detector = MagicMock()
mock_detector.return_value.detect.return_value = mock_resource
mock_module = MagicMock()
mock_module.ContainerResourceDetector = mock_detector

config = ResourceConfig(
attributes=[
AttributeNameValue(name="container.id", value="explicit-id")
],
detection_development=ExperimentalResourceDetection(
detectors=[ExperimentalResourceDetector(container={})]
),
)
with patch.dict(
"sys.modules",
{"opentelemetry.resource.detector.containerid": mock_module},
):
resource = create_resource(config)

self.assertEqual(resource.attributes[CONTAINER_ID], "explicit-id")
Loading