Skip to content

Commit 89ff60e

Browse files
authored
[opentelemetry-sdk] Fix overwriting of the service.instance.id which has been populated through the resource detectors (#5660)
* Fix overwritting of the service.instance.id which has been populated from the user provided values from the resource detectors * Add CHANGELOG * Address feedback, move the ServiceInstanceIdResourceDetector to the top of the list * Fix lint and spellcheck * Address feedback * Condese tests * Fix lint * Fix lint
1 parent 2e88971 commit 89ff60e

3 files changed

Lines changed: 77 additions & 10 deletions

File tree

.changelog/5660.fixed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
`opentelemetry-sdk`: fix overriding of the service.instance.id which has been populated from the user provided values through the resource detectors

opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -670,18 +670,17 @@ def _build_resource_detectors() -> list["ResourceDetector"]:
670670
Fast path: if no extra detectors are configured, returns only the two
671671
built-in detectors without scanning entry_points.
672672
673-
"service_instance" (ServiceInstanceIdResourceDetector) and "otel"
674-
(OTELResourceDetector) are always appended as defaults. "otel" is last so
673+
"service_instance" (ServiceInstanceIdResourceDetector) is prepended unless
674+
it is explicitly configured. "otel" (OTELResourceDetector) is last so
675675
that OTEL_RESOURCE_ATTRIBUTES and OTEL_SERVICE_NAME take highest merge
676-
priority, but an explicit position in OTEL_EXPERIMENTAL_RESOURCE_DETECTORS
677-
is respected for either name.
676+
priority, but an explicit position in
677+
OTEL_EXPERIMENTAL_RESOURCE_DETECTORS is respected for either name.
678678
"""
679-
detector_names: list[str] = list(
680-
dict.fromkeys(
681-
[name.strip() for name in environ.get(OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, "").split(",") if name.strip()]
682-
+ ["service_instance", "otel"]
683-
)
684-
)
679+
configured_detector_names = [
680+
name.strip() for name in environ.get(OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, "").split(",") if name.strip()
681+
]
682+
default_detector_names = [] if "service_instance" in configured_detector_names else ["service_instance"]
683+
detector_names: list[str] = list(dict.fromkeys(default_detector_names + configured_detector_names + ["otel"]))
685684

686685
# Fast path: only the two built-in detectors — no entry_points scan needed.
687686
if detector_names == ["service_instance", "otel"]:

opentelemetry-sdk/tests/resources/test_resources.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import unittest
1212
import uuid
1313
from concurrent.futures import TimeoutError
14+
from functools import partial
1415
from logging import ERROR, WARNING
1516
from os import environ
1617
from unittest.mock import MagicMock, Mock, call, mock_open, patch
@@ -1247,6 +1248,72 @@ def tearDown(self) -> None:
12471248
def test_is_process_dependent(self):
12481249
self.assertTrue(ServiceInstanceIdResourceDetector().is_process_dependent())
12491250

1251+
@patch.dict(environ, {}, clear=True)
1252+
def test_resource_attributes_override_service_instance_id(self):
1253+
resource = Resource.create({SERVICE_INSTANCE_ID: "resource-instance-id"})
1254+
1255+
self.assertEqual(resource.attributes[SERVICE_INSTANCE_ID], "resource-instance-id")
1256+
1257+
@patch.dict(
1258+
environ,
1259+
{OTEL_RESOURCE_ATTRIBUTES: "service.instance.id=environment-instance-id"},
1260+
clear=True,
1261+
)
1262+
def test_environment_resource_attributes_override_service_instance_id(self):
1263+
resource = Resource.create()
1264+
1265+
self.assertEqual(resource.attributes[SERVICE_INSTANCE_ID], "environment-instance-id")
1266+
1267+
def test_service_instance_detector_ordering(self):
1268+
test_cases = (
1269+
("", False, True),
1270+
("mock", True, False),
1271+
("mock,service_instance", True, True),
1272+
)
1273+
1274+
def entry_points_side_effect(entry_point, *args, **kwargs):
1275+
if kwargs.get("name") == "mock":
1276+
return [entry_point]
1277+
return real_entry_points(*args, **kwargs)
1278+
1279+
for detector_names, includes_custom_detector, expects_generated_id in test_cases:
1280+
with self.subTest(detector_names=detector_names):
1281+
custom_detector = Mock(spec=ResourceDetector)
1282+
custom_detector.detect.return_value = Resource(
1283+
{
1284+
SERVICE_INSTANCE_ID: "configured-instance-id",
1285+
"custom.detector": "value",
1286+
}
1287+
)
1288+
entry_point = Mock(**{"load.return_value": Mock(return_value=custom_detector)})
1289+
1290+
with patch.dict(
1291+
environ,
1292+
{OTEL_EXPERIMENTAL_RESOURCE_DETECTORS: detector_names},
1293+
clear=True,
1294+
):
1295+
if includes_custom_detector:
1296+
with patch(
1297+
"opentelemetry.util._importlib_metadata.entry_points",
1298+
side_effect=partial(entry_points_side_effect, entry_point),
1299+
):
1300+
resource = Resource.create()
1301+
custom_detector.detect.assert_called_once()
1302+
self.assertEqual(resource.attributes["custom.detector"], "value")
1303+
else:
1304+
resource = Resource.create()
1305+
1306+
if expects_generated_id:
1307+
self.assertEqual(
1308+
uuid.UUID(resource.attributes[SERVICE_INSTANCE_ID]).version,
1309+
4,
1310+
)
1311+
else:
1312+
self.assertEqual(
1313+
resource.attributes[SERVICE_INSTANCE_ID],
1314+
"configured-instance-id",
1315+
)
1316+
12501317
def test_detect_value_is_valid_uuid4(self):
12511318
_resources_module._service_instance_id = None
12521319
_resources_module._service_instance_id_pid = None

0 commit comments

Comments
 (0)