Skip to content

Commit f764e45

Browse files
feat(config): Add TracerProvider support for declarative config (#4985)
* 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 * add TracerProvider creation from declarative config Implements create_tracer_provider() and configure_tracer_provider() for the declarative configuration pipeline (tracking issue #3631 step 5). Key behaviors: - Never reads OTEL_TRACES_SAMPLER or OTEL_SPAN_*_LIMIT env vars; absent config fields use OTel spec defaults (matching Java SDK behavior) - Default sampler is ParentBased(root=ALWAYS_ON) per the OTel spec - SpanLimits absent fields use hardcoded defaults (128) not env vars - configure_tracer_provider(None) is a no-op per spec/Java/JS behavior - OTLP exporter fields pass None through so the exporter reads its own env vars for unspecified values - Lazy imports for optional OTLP packages with ConfigurationError on missing - Supports all 4 ParentBased delegate samplers Assisted-by: Claude Sonnet 4.6 * add changelog entry for PR #4985 Assisted-by: Claude Sonnet 4.6 * fix CI lint/type failures in tracer provider config - add # noqa: PLC0415 to lazy OTLP imports (ruff also enforces this) - move SDK imports to top-level (BatchSpanProcessor, etc.) - convert test helper methods to @staticmethod to satisfy no-self-use - add pylint: disable=protected-access for private member access in tests - fix return type annotation on _create_span_processor Assisted-by: Claude Sonnet 4.6 * fix pylint no-self-use on TestCreateSampler._make_provider Assisted-by: Claude Sonnet 4.6 * use allowlist for bool coercion in declarative config resource Assisted-by: Claude Sonnet 4.6 * address review feedback: simplify resource filter and propagator loading Assisted-by: Claude Sonnet 4.6 * fix ruff formatting Assisted-by: Claude Sonnet 4.6 * fix pyright: wrap EntryPoints in iter() for next() compatibility Assisted-by: Claude Sonnet 4.6
1 parent e301732 commit f764e45

File tree

6 files changed

+762
-19
lines changed

6 files changed

+762
-19
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212

1313
## Unreleased
1414

15-
- Enabled the flake8-tidy-import plugins rules for the ruff linter. These rules throw warnings for relative imports in the modules.
15+
- `opentelemetry-sdk`: Add `create_tracer_provider`/`configure_tracer_provider` to declarative file configuration, enabling TracerProvider instantiation from config files without reading env vars
16+
([#4985](https://github.com/open-telemetry/opentelemetry-python/pull/4985))
17+
- Enabled the flake8-tidy-import plugins rules for the ruff linter. These rules throw warnings for relative imports in the modules.
1618
([#5019](https://github.com/open-telemetry/opentelemetry-python/pull/5019))
1719
- `opentelemetry-sdk`: Fix `AttributeError` in `ExplicitBucketHistogramAggregation` when applied to non-Histogram instruments without explicit boundaries
1820
([#5034](https://github.com/open-telemetry/opentelemetry-python/pull/5034))

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

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,16 @@
3636
def _load_entry_point_propagator(name: str) -> TextMapPropagator:
3737
"""Load a propagator by name from the opentelemetry_propagator entry point group."""
3838
try:
39-
eps = list(entry_points(group="opentelemetry_propagator", name=name))
40-
if not eps:
39+
ep = next(
40+
iter(entry_points(group="opentelemetry_propagator", name=name)),
41+
None,
42+
)
43+
if not ep:
4144
raise ConfigurationError(
4245
f"Propagator '{name}' not found. "
4346
"It may not be installed or may be misspelled."
4447
)
45-
return eps[0].load()()
48+
return ep.load()()
4649
except ConfigurationError:
4750
raise
4851
except Exception as exc:
@@ -85,29 +88,24 @@ def create_propagator(
8588
if config is None:
8689
return CompositePropagator([])
8790

88-
propagators: list[TextMapPropagator] = []
89-
seen_types: set[type] = set()
90-
91-
def _add_deduped(propagator: TextMapPropagator) -> None:
92-
if type(propagator) not in seen_types:
93-
seen_types.add(type(propagator))
94-
propagators.append(propagator)
91+
propagators: dict[type[TextMapPropagator], TextMapPropagator] = {}
9592

9693
# Process structured composite list
9794
if config.composite:
9895
for entry in config.composite:
9996
for propagator in _propagators_from_textmap_config(entry):
100-
_add_deduped(propagator)
97+
propagators.setdefault(type(propagator), propagator)
10198

10299
# Process composite_list (comma-separated propagator names via entry_points)
103100
if config.composite_list:
104101
for name in config.composite_list.split(","):
105102
name = name.strip()
106103
if not name or name.lower() == "none":
107104
continue
108-
_add_deduped(_load_entry_point_propagator(name))
105+
propagator = _load_entry_point_propagator(name)
106+
propagators.setdefault(type(propagator), propagator)
109107

110-
return CompositePropagator(propagators)
108+
return CompositePropagator(list(propagators.values()))
111109

112110

113111
def configure_propagator(config: Optional[PropagatorConfig]) -> None:

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,9 @@ def _filter_attributes(
173173
if not included and not excluded:
174174
return attrs
175175

176-
effective_included = included if included else None # [] → include all
177-
178176
result: dict[str, object] = {}
179177
for key, value in attrs.items():
180-
if effective_included is not None and not any(
181-
fnmatch.fnmatch(key, pat) for pat in effective_included
182-
):
178+
if included and not any(fnmatch.fnmatch(key, pat) for pat in included):
183179
continue
184180
if excluded and any(fnmatch.fnmatch(key, pat) for pat in excluded):
185181
continue

0 commit comments

Comments
 (0)