Skip to content

Commit 7477b10

Browse files
MikeGoldsmithemdnetoxrmx
authored
feat(config): Add LoggerProvider support for declarative config (#4990)
* 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 create_logger_provider/configure_logger_provider for declarative config Implements LoggerProvider instantiation from declarative config files, following the same env-var-suppression pattern as create_meter_provider. BatchLogRecordProcessor defaults use spec values (1000ms schedule_delay), overriding the Python SDK's incorrect 5000ms env-var default. Assisted-by: Claude Sonnet 4.6 * add changelog entry for logger provider declarative config (#4990) Assisted-by: Claude Sonnet 4.6 * fix linter errors * add test verifying OTEL_PROPAGATORS env var is ignored by configure_propagator Assisted-by: Claude Sonnet 4.6 * remove backwards compat re-export of ConfigurationError from _loader.py Import directly from _exceptions.py since this is new code with no existing dependents on the _loader module path. 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 * remove stale SDK bug note fixed in v1.41.0 Assisted-by: Claude Sonnet 4.6 --------- Co-authored-by: Emídio Neto <9735060+emdneto@users.noreply.github.com> Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
1 parent 0eedb27 commit 7477b10

File tree

4 files changed

+658
-0
lines changed

4 files changed

+658
-0
lines changed

CHANGELOG.md

Lines changed: 3 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 `create_logger_provider`/`configure_logger_provider` to declarative file configuration, enabling LoggerProvider instantiation from config files without reading env vars
16+
([#4990](https://github.com/open-telemetry/opentelemetry-python/pull/4990))
1517
- `opentelemetry-sdk`: Add `service` resource detector support to declarative file configuration via `detection_development.detectors[].service`
1618
([#5003](https://github.com/open-telemetry/opentelemetry-python/pull/5003))
1719
- logs: add exception support to Logger emit and LogRecord attributes
@@ -22,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2224

2325
## Version 1.41.0/0.62b0 (2026-04-09)
2426

27+
2528
- `opentelemetry-sdk`: Add `host` resource detector support to declarative file configuration via `detection_development.detectors[].host`
2629
([#5002](https://github.com/open-telemetry/opentelemetry-python/pull/5002))
2730
- `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
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from __future__ import annotations
16+
17+
import logging
18+
from typing import Optional
19+
20+
from opentelemetry._logs import set_logger_provider
21+
from opentelemetry.sdk._configuration._common import _parse_headers
22+
from opentelemetry.sdk._configuration._exceptions import ConfigurationError
23+
from opentelemetry.sdk._configuration.models import (
24+
BatchLogRecordProcessor as BatchLogRecordProcessorConfig,
25+
)
26+
from opentelemetry.sdk._configuration.models import (
27+
LoggerProvider as LoggerProviderConfig,
28+
)
29+
from opentelemetry.sdk._configuration.models import (
30+
LogRecordExporter as LogRecordExporterConfig,
31+
)
32+
from opentelemetry.sdk._configuration.models import (
33+
LogRecordProcessor as LogRecordProcessorConfig,
34+
)
35+
from opentelemetry.sdk._configuration.models import (
36+
OtlpGrpcExporter as OtlpGrpcExporterConfig,
37+
)
38+
from opentelemetry.sdk._configuration.models import (
39+
OtlpHttpExporter as OtlpHttpExporterConfig,
40+
)
41+
from opentelemetry.sdk._configuration.models import (
42+
SimpleLogRecordProcessor as SimpleLogRecordProcessorConfig,
43+
)
44+
from opentelemetry.sdk._logs import LoggerProvider
45+
from opentelemetry.sdk._logs._internal.export import (
46+
BatchLogRecordProcessor,
47+
ConsoleLogRecordExporter,
48+
LogRecordExporter,
49+
SimpleLogRecordProcessor,
50+
)
51+
from opentelemetry.sdk.resources import Resource
52+
53+
_logger = logging.getLogger(__name__)
54+
55+
# BatchLogRecordProcessor defaults per OTel spec (milliseconds).
56+
_DEFAULT_SCHEDULE_DELAY_MILLIS = 1000
57+
_DEFAULT_EXPORT_TIMEOUT_MILLIS = 30000
58+
_DEFAULT_MAX_QUEUE_SIZE = 2048
59+
_DEFAULT_MAX_EXPORT_BATCH_SIZE = 512
60+
61+
62+
def _map_compression(
63+
value: Optional[str], compression_enum: type
64+
) -> Optional[object]:
65+
"""Map a compression string to the given Compression enum value."""
66+
if value is None or value.lower() == "none":
67+
return None
68+
if value.lower() == "gzip":
69+
return compression_enum.Gzip # type: ignore[attr-defined]
70+
raise ConfigurationError(
71+
f"Unsupported compression value '{value}'. Supported values: 'gzip', 'none'."
72+
)
73+
74+
75+
def _create_console_log_exporter() -> ConsoleLogRecordExporter:
76+
"""Create a ConsoleLogRecordExporter."""
77+
return ConsoleLogRecordExporter()
78+
79+
80+
def _create_otlp_http_log_exporter(
81+
config: OtlpHttpExporterConfig,
82+
) -> LogRecordExporter:
83+
"""Create an OTLP HTTP log exporter from config."""
84+
try:
85+
# pylint: disable=import-outside-toplevel,no-name-in-module
86+
from opentelemetry.exporter.otlp.proto.http import ( # type: ignore[import-untyped] # noqa: PLC0415
87+
Compression,
88+
)
89+
from opentelemetry.exporter.otlp.proto.http._log_exporter import ( # type: ignore[import-untyped] # noqa: PLC0415
90+
OTLPLogExporter,
91+
)
92+
except ImportError as exc:
93+
raise ConfigurationError(
94+
"otlp_http log exporter requires 'opentelemetry-exporter-otlp-proto-http'. "
95+
"Install it with: pip install opentelemetry-exporter-otlp-proto-http"
96+
) from exc
97+
98+
compression = _map_compression(config.compression, Compression)
99+
headers = _parse_headers(config.headers, config.headers_list)
100+
timeout = (config.timeout / 1000.0) if config.timeout is not None else None
101+
102+
return OTLPLogExporter( # type: ignore[return-value]
103+
endpoint=config.endpoint,
104+
headers=headers,
105+
timeout=timeout,
106+
compression=compression, # type: ignore[arg-type]
107+
)
108+
109+
110+
def _create_otlp_grpc_log_exporter(
111+
config: OtlpGrpcExporterConfig,
112+
) -> LogRecordExporter:
113+
"""Create an OTLP gRPC log exporter from config."""
114+
try:
115+
# pylint: disable=import-outside-toplevel,no-name-in-module
116+
import grpc # type: ignore[import-untyped] # noqa: PLC0415
117+
118+
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import ( # type: ignore[import-untyped] # noqa: PLC0415
119+
OTLPLogExporter,
120+
)
121+
except ImportError as exc:
122+
raise ConfigurationError(
123+
"otlp_grpc log exporter requires 'opentelemetry-exporter-otlp-proto-grpc'. "
124+
"Install it with: pip install opentelemetry-exporter-otlp-proto-grpc"
125+
) from exc
126+
127+
compression = _map_compression(config.compression, grpc.Compression)
128+
headers = _parse_headers(config.headers, config.headers_list)
129+
timeout = (config.timeout / 1000.0) if config.timeout is not None else None
130+
131+
return OTLPLogExporter( # type: ignore[return-value]
132+
endpoint=config.endpoint,
133+
headers=headers,
134+
timeout=timeout,
135+
compression=compression, # type: ignore[arg-type]
136+
)
137+
138+
139+
def _create_log_record_exporter(
140+
config: LogRecordExporterConfig,
141+
) -> LogRecordExporter:
142+
"""Create a log record exporter from config."""
143+
if config.console is not None:
144+
return _create_console_log_exporter()
145+
if config.otlp_http is not None:
146+
return _create_otlp_http_log_exporter(config.otlp_http)
147+
if config.otlp_grpc is not None:
148+
return _create_otlp_grpc_log_exporter(config.otlp_grpc)
149+
if config.otlp_file_development is not None:
150+
raise ConfigurationError(
151+
"otlp_file_development log exporter is experimental and not yet supported."
152+
)
153+
raise ConfigurationError(
154+
"No exporter type specified in log record exporter config. "
155+
"Supported types: console, otlp_http, otlp_grpc."
156+
)
157+
158+
159+
def _create_batch_log_record_processor(
160+
config: BatchLogRecordProcessorConfig,
161+
) -> BatchLogRecordProcessor:
162+
"""Create a BatchLogRecordProcessor from config.
163+
164+
Passes explicit defaults to suppress OTEL_BLRP_* env var reading.
165+
"""
166+
exporter = _create_log_record_exporter(config.exporter)
167+
schedule_delay = (
168+
config.schedule_delay
169+
if config.schedule_delay is not None
170+
else _DEFAULT_SCHEDULE_DELAY_MILLIS
171+
)
172+
export_timeout = (
173+
config.export_timeout
174+
if config.export_timeout is not None
175+
else _DEFAULT_EXPORT_TIMEOUT_MILLIS
176+
)
177+
max_queue_size = (
178+
config.max_queue_size
179+
if config.max_queue_size is not None
180+
else _DEFAULT_MAX_QUEUE_SIZE
181+
)
182+
max_export_batch_size = (
183+
config.max_export_batch_size
184+
if config.max_export_batch_size is not None
185+
else _DEFAULT_MAX_EXPORT_BATCH_SIZE
186+
)
187+
return BatchLogRecordProcessor(
188+
exporter=exporter,
189+
schedule_delay_millis=float(schedule_delay),
190+
export_timeout_millis=float(export_timeout),
191+
max_queue_size=max_queue_size,
192+
max_export_batch_size=max_export_batch_size,
193+
)
194+
195+
196+
def _create_simple_log_record_processor(
197+
config: SimpleLogRecordProcessorConfig,
198+
) -> SimpleLogRecordProcessor:
199+
"""Create a SimpleLogRecordProcessor from config."""
200+
exporter = _create_log_record_exporter(config.exporter)
201+
return SimpleLogRecordProcessor(exporter)
202+
203+
204+
def _create_log_record_processor(
205+
config: LogRecordProcessorConfig,
206+
) -> BatchLogRecordProcessor | SimpleLogRecordProcessor:
207+
"""Create a log record processor from config."""
208+
if config.batch is not None:
209+
return _create_batch_log_record_processor(config.batch)
210+
if config.simple is not None:
211+
return _create_simple_log_record_processor(config.simple)
212+
raise ConfigurationError(
213+
"No processor type specified in log record processor config. "
214+
"Supported types: batch, simple."
215+
)
216+
217+
218+
def create_logger_provider(
219+
config: Optional[LoggerProviderConfig],
220+
resource: Optional[Resource] = None,
221+
) -> LoggerProvider:
222+
"""Create an SDK LoggerProvider from declarative config.
223+
224+
Does NOT read OTEL_BLRP_* or other env vars for values explicitly
225+
controlled by the config. Absent config values use OTel spec defaults.
226+
227+
Args:
228+
config: LoggerProvider config from the parsed config file, or None.
229+
resource: Resource to attach to the provider.
230+
231+
Returns:
232+
A configured LoggerProvider.
233+
"""
234+
provider = LoggerProvider(resource=resource)
235+
236+
if config is None:
237+
return provider
238+
239+
if config.limits is not None:
240+
_logger.warning(
241+
"log_record_limits are specified in config but are not supported "
242+
"by the Python SDK LoggerProvider constructor; limits will be ignored."
243+
)
244+
245+
for processor_config in config.processors:
246+
provider.add_log_record_processor(
247+
_create_log_record_processor(processor_config)
248+
)
249+
250+
return provider
251+
252+
253+
def configure_logger_provider(
254+
config: Optional[LoggerProviderConfig],
255+
resource: Optional[Resource] = None,
256+
) -> None:
257+
"""Configure the global LoggerProvider from declarative config.
258+
259+
When config is None (logger_provider section absent from config file),
260+
the global is not set — matching Java/JS SDK behavior.
261+
262+
Args:
263+
config: LoggerProvider config from the parsed config file, or None.
264+
resource: Resource to attach to the provider.
265+
"""
266+
if config is None:
267+
return
268+
set_logger_provider(create_logger_provider(config, resource))

opentelemetry-sdk/src/opentelemetry/sdk/_configuration/file/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
"""
2626

2727
from opentelemetry.sdk._configuration._exceptions import ConfigurationError
28+
from opentelemetry.sdk._configuration._logger_provider import (
29+
configure_logger_provider,
30+
create_logger_provider,
31+
)
2832
from opentelemetry.sdk._configuration._meter_provider import (
2933
configure_meter_provider,
3034
create_meter_provider,
@@ -52,6 +56,8 @@
5256
"create_resource",
5357
"create_propagator",
5458
"configure_propagator",
59+
"create_logger_provider",
60+
"configure_logger_provider",
5561
"create_tracer_provider",
5662
"configure_tracer_provider",
5763
"create_meter_provider",

0 commit comments

Comments
 (0)