Skip to content

Commit 6faa58c

Browse files
MikeGoldsmithaabmassxrmx
authored
feat(config): add host resource detector support for declarative config (#5002)
* config: add resource and propagator creation from declarative config Implements create_resource() and create_propagator()/configure_propagator() for the declarative file configuration. Resource creation does not read OTEL_RESOURCE_ATTRIBUTES or run any detectors (matches Java/JS SDK behavior). Propagator configuration always calls set_global_textmap to override Python's default tracecontext+baggage, setting a noop CompositePropagator when no propagator is configured. Assisted-by: Claude Sonnet 4.6 * update changelog with PR number Assisted-by: Claude Sonnet 4.6 * fix pylint, pyright and ruff errors in resource/propagator config - _resource.py: refactor _coerce_attribute_value to dispatch table to avoid too-many-return-statements; fix short variable names k/v -> attr_key/attr_val; fix return type of _sdk_default_attributes to dict[str, str] to satisfy pyright - _propagator.py: rename short variable names e -> exc, p -> propagator - test_resource.py: move imports to top level; split TestCreateResource (25 methods) into three focused classes to satisfy too-many-public-methods - test_propagator.py: add pylint disable for protected-access Assisted-by: Claude Sonnet 4.6 * address review feedback: use _DEFAULT_RESOURCE, fix bool_array coercion - replace _sdk_default_attributes() with _DEFAULT_RESOURCE from resources module - move _coerce_bool into dispatch tables for both scalar and array bool types, fixing a bug where bool_array with string values like "false" would coerce incorrectly via plain bool() (non-empty string -> True) - add test for bool_array with string values to cover the bug Assisted-by: Claude Sonnet 4.6 * fix linter * address review feedback: single coercion table, simplify attributes merge - collapse _SCALAR_COERCIONS and _ARRAY_COERCIONS into a single _COERCIONS dict using an _array() factory, reducing _coerce_attribute_value to two lines - process attributes_list before attributes so explicit attributes naturally overwrite list entries without needing an explicit guard Assisted-by: Claude Sonnet 4.6 * use Callable type annotation on _array helper Assisted-by: Claude Sonnet 4.6 * add detection infrastructure foundations for resource detectors Adds _run_detectors() stub and _filter_attributes() to create_resource(), providing the shared scaffolding for detector PRs to build on. Detectors are opt-in: nothing runs unless explicitly listed under detection_development.detectors in the config. The include/exclude attribute filter mirrors other SDK behaviour. Assisted-by: Claude Sonnet 4.6 * move service.name default into base resource Merges service.name=unknown_service into base before running detectors, so detectors (e.g. service) can override it. Previously it was added to config_attrs and merged last, which would have silently overridden any detector-provided service.name. Assisted-by: Claude Sonnet 4.6 * remove unused logging import from _propagator.py Assisted-by: Claude Sonnet 4.6 * wire host resource detector in declarative config Makes _HostResourceDetector public as HostResourceDetector and wires it to detection_development.detectors[].host in _run_detectors(). Assisted-by: Claude Sonnet 4.6 * add changelog entry for host resource detector (#5002) Assisted-by: Claude Sonnet 4.6 * fix import sort in test_resources.py (ruff) Assisted-by: Claude Sonnet 4.6 * revert HostResourceDetector to private _HostResourceDetector Assisted-by: Claude Sonnet 4.6 * Apply suggestion from @xrmx * Update test_resources.py * Update _resource.py --------- Co-authored-by: Aaron Abbott <aaronabbott@google.com> Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
1 parent c0cbfbd commit 6faa58c

File tree

3 files changed

+82
-0
lines changed

3 files changed

+82
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212

1313
## Unreleased
1414

15+
- `opentelemetry-sdk`: Add `host` resource detector support to declarative file configuration via `detection_development.detectors[].host`
16+
([#5002](https://github.com/open-telemetry/opentelemetry-python/pull/5002))
1517
- `opentelemetry-sdk`: Add `container` resource detector support to declarative file configuration via `detection_development.detectors[].container`, using entry point loading of the `opentelemetry-resource-detector-containerid` contrib package
1618
([#5004](https://github.com/open-telemetry/opentelemetry-python/pull/5004))
1719
- `opentelemetry-sdk`: Add `create_tracer_provider`/`configure_tracer_provider` to declarative file configuration, enabling TracerProvider instantiation from config files without reading env vars

opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_resource.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
SERVICE_NAME,
3232
ProcessResourceDetector,
3333
Resource,
34+
_HostResourceDetector,
3435
)
3536
from opentelemetry.util._importlib_metadata import entry_points
3637

@@ -151,6 +152,9 @@ def _run_detectors(
151152
is updated in-place; later detectors overwrite earlier ones for the
152153
same key.
153154
"""
155+
if detector_config.host is not None:
156+
detected_attrs.update(_HostResourceDetector().detect().attributes)
157+
154158
if detector_config.container is not None:
155159
# The container detector is not part of the core SDK. It is provided
156160
# by the opentelemetry-resource-detector-containerid contrib package,
@@ -175,6 +179,7 @@ def _run_detectors(
175179
)
176180
else:
177181
detected_attrs.update(ep.load()().detect().attributes)
182+
178183
if detector_config.process is not None:
179184
detected_attrs.update(ProcessResourceDetector().detect().attributes)
180185

opentelemetry-sdk/tests/_configuration/test_resource.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import os
16+
import socket
1617
import sys
1718
import unittest
1819
from unittest.mock import MagicMock, patch
@@ -23,10 +24,13 @@
2324
AttributeType,
2425
ExperimentalResourceDetection,
2526
ExperimentalResourceDetector,
27+
IncludeExclude,
2628
)
2729
from opentelemetry.sdk._configuration.models import Resource as ResourceConfig
2830
from opentelemetry.sdk.resources import (
2931
CONTAINER_ID,
32+
HOST_ARCH,
33+
HOST_NAME,
3034
PROCESS_PID,
3135
PROCESS_RUNTIME_NAME,
3236
SERVICE_NAME,
@@ -303,6 +307,77 @@ def test_attributes_list_invalid_pair_skipped(self):
303307
self.assertTrue(any("no-equals" in msg for msg in cm.output))
304308

305309

310+
class TestHostResourceDetector(unittest.TestCase):
311+
@staticmethod
312+
def _config_with_host() -> ResourceConfig:
313+
return ResourceConfig(
314+
detection_development=ExperimentalResourceDetection(
315+
detectors=[ExperimentalResourceDetector(host={})]
316+
)
317+
)
318+
319+
def test_host_detector_adds_host_attributes(self):
320+
resource = create_resource(self._config_with_host())
321+
self.assertIn(HOST_NAME, resource.attributes)
322+
self.assertEqual(resource.attributes[HOST_NAME], socket.gethostname())
323+
self.assertIn(HOST_ARCH, resource.attributes)
324+
325+
def test_host_detector_also_includes_sdk_defaults(self):
326+
resource = create_resource(self._config_with_host())
327+
self.assertEqual(resource.attributes[TELEMETRY_SDK_LANGUAGE], "python")
328+
self.assertIn(TELEMETRY_SDK_VERSION, resource.attributes)
329+
330+
def test_host_detector_not_run_when_absent(self):
331+
resource = create_resource(ResourceConfig())
332+
self.assertNotIn(HOST_NAME, resource.attributes)
333+
self.assertNotIn(HOST_ARCH, resource.attributes)
334+
335+
def test_host_detector_not_run_when_detection_development_is_none(self):
336+
resource = create_resource(ResourceConfig(detection_development=None))
337+
self.assertNotIn(HOST_NAME, resource.attributes)
338+
339+
def test_host_detector_not_run_when_detectors_list_empty(self):
340+
config = ResourceConfig(
341+
detection_development=ExperimentalResourceDetection(detectors=[])
342+
)
343+
resource = create_resource(config)
344+
self.assertNotIn(HOST_NAME, resource.attributes)
345+
346+
def test_explicit_attributes_override_host_detector(self):
347+
config = ResourceConfig(
348+
attributes=[
349+
AttributeNameValue(name="host.name", value="custom-host")
350+
],
351+
detection_development=ExperimentalResourceDetection(
352+
detectors=[ExperimentalResourceDetector(host={})]
353+
),
354+
)
355+
resource = create_resource(config)
356+
self.assertEqual(resource.attributes[HOST_NAME], "custom-host")
357+
358+
def test_included_filter_limits_host_attributes(self):
359+
config = ResourceConfig(
360+
detection_development=ExperimentalResourceDetection(
361+
detectors=[ExperimentalResourceDetector(host={})],
362+
attributes=IncludeExclude(included=["host.name"]),
363+
)
364+
)
365+
resource = create_resource(config)
366+
self.assertIn(HOST_NAME, resource.attributes)
367+
self.assertNotIn(HOST_ARCH, resource.attributes)
368+
369+
def test_excluded_filter_removes_host_attributes(self):
370+
config = ResourceConfig(
371+
detection_development=ExperimentalResourceDetection(
372+
detectors=[ExperimentalResourceDetector(host={})],
373+
attributes=IncludeExclude(excluded=["host.name"]),
374+
)
375+
)
376+
resource = create_resource(config)
377+
self.assertNotIn(HOST_NAME, resource.attributes)
378+
self.assertIn(HOST_ARCH, resource.attributes)
379+
380+
306381
class TestContainerResourceDetector(unittest.TestCase):
307382
@staticmethod
308383
def _config_with_container() -> ResourceConfig:

0 commit comments

Comments
 (0)