Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e5d6932
recursively convert parsed dicts to typed dataclasses in loader
MikeGoldsmith Jun 3, 2026
582c37f
rename changelog fragment to PR #5269
MikeGoldsmith Jun 3, 2026
b302f93
tighten typing on conversion module
MikeGoldsmith Jun 3, 2026
3a6fd21
isolate typing.get_type_hints call to placate astroid 3.x on py3.14
MikeGoldsmith Jun 3, 2026
131378c
inline the typing.get_type_hints wrap
MikeGoldsmith Jun 3, 2026
3720621
add configure_sdk orchestrator for declarative config
MikeGoldsmith Jun 3, 2026
fd6c20a
rename changelog fragment to PR #5270
MikeGoldsmith Jun 3, 2026
7066419
honor OTEL_CONFIG_FILE in the SDK configurator
MikeGoldsmith Jun 3, 2026
da80d63
rename changelog fragment to PR #5271
MikeGoldsmith Jun 3, 2026
b6e4702
use ExemplarFilter for enum coercion test fixture; allow 'astroid' in…
MikeGoldsmith Jun 3, 2026
83e17bd
Merge branch 'mike/config-recursive-dict-conversion' into mike/config…
MikeGoldsmith Jun 3, 2026
52365e2
Merge branch 'mike/config-orchestrator' into mike/config-file-env-rou…
MikeGoldsmith Jun 3, 2026
3fc2669
fix lint on test_sdk.py: hoist import, disable no-self-use
MikeGoldsmith Jun 3, 2026
119bc83
Merge branch 'mike/config-orchestrator' into mike/config-file-env-rou…
MikeGoldsmith Jun 3, 2026
417d451
silence pylint/ruff on intentional lazy imports
MikeGoldsmith Jun 3, 2026
41667ca
remove extra blank line after imports (ruff I001)
MikeGoldsmith Jun 3, 2026
297da35
Merge branch 'mike/config-orchestrator' into mike/config-file-env-rou…
MikeGoldsmith Jun 3, 2026
2b2d47b
collapse multi-line @patch decorators (ruff format)
MikeGoldsmith Jun 3, 2026
70c93d9
add end-to-end loader tests covering YAML -> typed config -> factory
MikeGoldsmith Jun 5, 2026
828c54b
Merge branch 'mike/config-recursive-dict-conversion' into mike/config…
MikeGoldsmith Jun 5, 2026
62cdfa4
Merge branch 'mike/config-orchestrator' into mike/config-file-env-rou…
MikeGoldsmith Jun 5, 2026
111fc4a
Merge branch 'main' into mike/config-file-env-routing
MikeGoldsmith Jun 9, 2026
e111ebf
Merge branch 'main' into mike/config-file-env-routing
MikeGoldsmith Jun 16, 2026
73982d9
address review feedback on OTEL_CONFIG_FILE routing
MikeGoldsmith Jun 16, 2026
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
1 change: 1 addition & 0 deletions .changelog/5271.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`opentelemetry-sdk`: the SDK configurator now honors the `OTEL_CONFIG_FILE` environment variable. When set, the SDK loads and applies the referenced declarative configuration file (YAML or JSON) in place of the env-var-based init path.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
)
from opentelemetry.sdk.environment_variables import (
_OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED,
OTEL_CONFIG_FILE,
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL,
OTEL_EXPORTER_OTLP_METRICS_PROTOCOL,
OTEL_EXPORTER_OTLP_PROTOCOL,
Expand Down Expand Up @@ -720,4 +721,24 @@ class _OTelSDKConfigurator(_BaseConfigurator):
"""

def _configure(self, **kwargs):
if config_file := environ.get(OTEL_CONFIG_FILE):
# Imported lazily so that the SDK does not require the optional
# file-configuration extras (pyyaml, jsonschema) unless a config
# file is actually requested.
# pylint: disable=import-outside-toplevel
from opentelemetry.sdk._configuration._sdk import ( # noqa: PLC0415
configure_sdk,
)
from opentelemetry.sdk._configuration.file._loader import ( # noqa: PLC0415
load_config_file,
)

if kwargs:
_logger.warning(
"%s is set; ignoring configurator kwargs: %s",
OTEL_CONFIG_FILE,
sorted(kwargs),
)
configure_sdk(load_config_file(config_file))
return
_initialize_components(**kwargs)
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@
Default: "false"
"""

OTEL_CONFIG_FILE = "OTEL_CONFIG_FILE"
"""
.. envvar:: OTEL_CONFIG_FILE

The :envvar:`OTEL_CONFIG_FILE` environment variable points the SDK at a
declarative configuration file (YAML or JSON). When set, the file is the
sole source of SDK configuration; other ``OTEL_*`` environment variables
are ignored except where referenced via ``${env:VAR}`` substitution inside
the file. See the OpenTelemetry declarative configuration specification
for details.
"""

OTEL_RESOURCE_ATTRIBUTES = "OTEL_RESOURCE_ATTRIBUTES"
"""
.. envvar:: OTEL_RESOURCE_ATTRIBUTES
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

# Tests access private members of SDK classes to assert correct configuration.
# pylint: disable=protected-access,no-self-use

import unittest
from unittest.mock import patch

from opentelemetry.sdk._configuration import _OTelSDKConfigurator
from opentelemetry.sdk._configuration._exceptions import ConfigurationError
from opentelemetry.sdk.environment_variables import OTEL_CONFIG_FILE


class TestConfiguratorFileRouting(unittest.TestCase):
def tearDown(self):
Comment thread
MikeGoldsmith marked this conversation as resolved.
# _BaseConfigurator caches instances via a singleton; reset so sibling
# tests (e.g. test_configurator.py's CustomConfigurator subclass) are
# not affected by this class's singleton state.
_OTelSDKConfigurator._instance = None

@patch.dict("os.environ", {}, clear=True)
@patch("opentelemetry.sdk._configuration._initialize_components")
def test_env_var_unset_runs_env_var_path(self, mock_init_components):
_OTelSDKConfigurator()._configure(auto_instrumentation_version="X")
mock_init_components.assert_called_once_with(
auto_instrumentation_version="X"
)

@patch.dict("os.environ", {OTEL_CONFIG_FILE: "/tmp/otel.yaml"})
@patch("opentelemetry.sdk._configuration._sdk.configure_sdk")
@patch("opentelemetry.sdk._configuration.file._loader.load_config_file")
@patch("opentelemetry.sdk._configuration._initialize_components")
def test_env_var_set_routes_to_declarative_path(
self, mock_init_components, mock_load, mock_configure_sdk
):
sentinel_config = object()
mock_load.return_value = sentinel_config

_OTelSDKConfigurator()._configure()

mock_load.assert_called_once_with("/tmp/otel.yaml")
mock_configure_sdk.assert_called_once_with(sentinel_config)
mock_init_components.assert_not_called()

@patch.dict("os.environ", {OTEL_CONFIG_FILE: "/does/not/exist.yaml"})
@patch("opentelemetry.sdk._configuration._initialize_components")
def test_env_var_set_missing_file_propagates(self, mock_init_components):
with self.assertRaises(ConfigurationError):
_OTelSDKConfigurator()._configure()
mock_init_components.assert_not_called()

@patch.dict("os.environ", {OTEL_CONFIG_FILE: "/tmp/otel.yaml"})
@patch("opentelemetry.sdk._configuration._sdk.configure_sdk")
@patch("opentelemetry.sdk._configuration.file._loader.load_config_file")
def test_env_var_set_with_kwargs_warns_and_ignores(
self, mock_load, mock_configure_sdk
):
mock_load.return_value = object()

with self.assertLogs(
"opentelemetry.sdk._configuration", level="WARNING"
) as captured:
_OTelSDKConfigurator()._configure(
sampler="X", auto_instrumentation_version="Y"
)

self.assertTrue(
any(
"OTEL_CONFIG_FILE" in msg and "sampler" in msg
for msg in captured.output
),
f"Expected warning about ignored kwargs, got: {captured.output}",
)
mock_configure_sdk.assert_called_once()

@patch.dict("os.environ", {}, clear=True)
@patch("opentelemetry.sdk._configuration._initialize_components")
def test_distro_override_pattern_still_works(self, mock_init_components):
class CustomConfigurator(_OTelSDKConfigurator):
def _configure(self, **kwargs):
kwargs["sampler"] = "TEST_SAMPLER"
super()._configure(**kwargs)

CustomConfigurator()._configure(auto_instrumentation_version="V")

mock_init_components.assert_called_once_with(
auto_instrumentation_version="V", sampler="TEST_SAMPLER"
)
Loading