Skip to content

Commit 18761d7

Browse files
authored
Update Resource merge key conflict precedence (open-telemetry#1544)
1 parent c026c0f commit 18761d7

File tree

3 files changed

+53
-14
lines changed

3 files changed

+53
-14
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7979
([#1536](https://github.com/open-telemetry/opentelemetry-python/pull/1536))
8080
- Fix TraceState to adhere to specs
8181
([#1502](https://github.com/open-telemetry/opentelemetry-python/pull/1502))
82+
- Update Resource `merge` key conflict precedence
83+
([#1544](https://github.com/open-telemetry/opentelemetry-python/pull/1544))
8284

8385
### Removed
8486
- `opentelemetry-api` Remove ThreadLocalRuntimeContext since python3.4 is not supported.

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

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -155,16 +155,27 @@
155155

156156

157157
class Resource:
158+
"""A Resource is an immutable representation of the entity producing telemetry as Attributes.
159+
"""
160+
158161
def __init__(self, attributes: Attributes):
159162
self._attributes = attributes.copy()
160163

161164
@staticmethod
162165
def create(attributes: typing.Optional[Attributes] = None) -> "Resource":
166+
"""Creates a new `Resource` from attributes.
167+
168+
Args:
169+
attributes: Optional zero or more key-value pairs.
170+
171+
Returns:
172+
The newly-created Resource.
173+
"""
163174
if not attributes:
164-
resource = _DEFAULT_RESOURCE
165-
else:
166-
resource = _DEFAULT_RESOURCE.merge(Resource(attributes))
167-
resource = resource.merge(OTELResourceDetector().detect())
175+
attributes = {}
176+
resource = _DEFAULT_RESOURCE.merge(
177+
OTELResourceDetector().detect()
178+
).merge(Resource(attributes))
168179
if not resource.attributes.get(SERVICE_NAME, None):
169180
default_service_name = "unknown_service"
170181
process_executable_name = resource.attributes.get(
@@ -186,11 +197,19 @@ def attributes(self) -> Attributes:
186197
return self._attributes.copy()
187198

188199
def merge(self, other: "Resource") -> "Resource":
200+
"""Merges this resource and an updating resource into a new `Resource`.
201+
202+
If a key exists on both the old and updating resource, the value of the
203+
updating resource will override the old resource value.
204+
205+
Args:
206+
other: The other resource to be merged.
207+
208+
Returns:
209+
The newly-created Resource.
210+
"""
189211
merged_attributes = self.attributes
190-
# pylint: disable=protected-access
191-
for key, value in other._attributes.items():
192-
if key not in merged_attributes or merged_attributes[key] == "":
193-
merged_attributes[key] = value
212+
merged_attributes.update(other.attributes)
194213
return Resource(merged_attributes)
195214

196215
def __eq__(self, other: object) -> bool:

opentelemetry-sdk/tests/resources/test_resources.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def test_resource_merge_empty_string(self):
9999
)
100100
self.assertEqual(
101101
left.merge(right),
102-
resources.Resource({"service": "ui", "host": "service-host"}),
102+
resources.Resource({"service": "not-ui", "host": "service-host"}),
103103
)
104104

105105
def test_immutability(self):
@@ -154,7 +154,6 @@ def test_aggregated_resources_with_static_resource(self):
154154
static_resource,
155155
)
156156

157-
# Static resource values should never be overwritten
158157
resource_detector = mock.Mock(spec=resources.ResourceDetector)
159158
resource_detector.detect.return_value = resources.Resource(
160159
{"static_key": "try_to_overwrite_existing_value", "key": "value"}
@@ -163,7 +162,12 @@ def test_aggregated_resources_with_static_resource(self):
163162
resources.get_aggregated_resources(
164163
[resource_detector], initial_resource=static_resource
165164
),
166-
resources.Resource({"static_key": "static_value", "key": "value"}),
165+
resources.Resource(
166+
{
167+
"static_key": "try_to_overwrite_existing_value",
168+
"key": "value",
169+
}
170+
),
167171
)
168172

169173
def test_aggregated_resources_multiple_detectors(self):
@@ -184,16 +188,15 @@ def test_aggregated_resources_multiple_detectors(self):
184188
}
185189
)
186190

187-
# New values should not overwrite existing values
188191
self.assertEqual(
189192
resources.get_aggregated_resources(
190193
[resource_detector1, resource_detector2, resource_detector3]
191194
),
192195
resources.Resource(
193196
{
194197
"key1": "value1",
195-
"key2": "value2",
196-
"key3": "value3",
198+
"key2": "try_to_overwrite_existing_value",
199+
"key3": "try_to_overwrite_existing_value",
197200
"key4": "value4",
198201
}
199202
),
@@ -216,6 +219,21 @@ def test_resource_detector_raise_error(self):
216219
Exception, resources.get_aggregated_resources, [resource_detector]
217220
)
218221

222+
@mock.patch.dict(
223+
os.environ,
224+
{"OTEL_RESOURCE_ATTRIBUTES": "key1=env_value1,key2=env_value2"},
225+
)
226+
def test_env_priority(self):
227+
resource_env = resources.Resource.create()
228+
self.assertEqual(resource_env.attributes["key1"], "env_value1")
229+
self.assertEqual(resource_env.attributes["key2"], "env_value2")
230+
231+
resource_env_override = resources.Resource.create(
232+
{"key1": "value1", "key2": "value2"}
233+
)
234+
self.assertEqual(resource_env_override.attributes["key1"], "value1")
235+
self.assertEqual(resource_env_override.attributes["key2"], "value2")
236+
219237

220238
class TestOTELResourceDetector(unittest.TestCase):
221239
def setUp(self) -> None:

0 commit comments

Comments
 (0)