From 094839107d7fbd8c137647e9463267ba7db03f5a Mon Sep 17 00:00:00 2001 From: Diego Hurtado Date: Fri, 26 Jun 2026 15:00:56 -0600 Subject: [PATCH 1/4] feat(config): wire top-level attribute_limits into per-signal providers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parses config.attribute_limits in configure_sdk() and passes it as a global fallback to create_tracer_provider() and create_logger_provider(). Per-signal limits (tracer_provider.limits / logger_provider.limits) always take precedence; absent fields fall back to the global value, then to OTel spec defaults. For logs, adds log_record_limits to the LoggerProvider constructor, threads it through Logger, and applies it when constructing each ReadWriteLogRecord — mirroring how SpanLimits flows through TracerProvider. --- .../sdk/_configuration/_logger_provider.py | 62 +++++++++++++++--- .../opentelemetry/sdk/_configuration/_sdk.py | 10 ++- .../sdk/_configuration/_tracer_provider.py | 50 ++++++++++----- .../sdk/_logs/_internal/__init__.py | 9 +++ .../_configuration/test_logger_provider.py | 64 +++++++++++++------ .../tests/_configuration/test_sdk.py | 4 +- .../_configuration/test_tracer_provider.py | 48 ++++++++++++++ 7 files changed, 200 insertions(+), 47 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_logger_provider.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_logger_provider.py index e2ecdfa3a8..204e0d881e 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_logger_provider.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_logger_provider.py @@ -13,9 +13,13 @@ load_entry_point, ) from opentelemetry.sdk._configuration._exceptions import ConfigurationError +from opentelemetry.sdk._configuration.models import AttributeLimits from opentelemetry.sdk._configuration.models import ( BatchLogRecordProcessor as BatchLogRecordProcessorConfig, ) +from opentelemetry.sdk._configuration.models import ( + LogRecordLimits as LogRecordLimitsConfig, +) from opentelemetry.sdk._configuration.models import ( ExperimentalOtlpFileExporter as ExperimentalOtlpFileExporterConfig, ) @@ -38,6 +42,7 @@ SimpleLogRecordProcessor as SimpleLogRecordProcessorConfig, ) from opentelemetry.sdk._logs import LoggerProvider +from opentelemetry.sdk._logs._internal import LogRecordLimits from opentelemetry.sdk._logs._internal.export import ( BatchLogRecordProcessor, ConsoleLogRecordExporter, @@ -48,6 +53,8 @@ _logger = logging.getLogger(__name__) +_DEFAULT_OTEL_LOG_ATTRIBUTE_COUNT_LIMIT = 128 + # BatchLogRecordProcessor defaults per OTel spec (milliseconds). _DEFAULT_SCHEDULE_DELAY_MILLIS = 1000 _DEFAULT_EXPORT_TIMEOUT_MILLIS = 30000 @@ -232,9 +239,38 @@ def _create_log_record_processor( ) +def _create_log_record_limits( + config: LogRecordLimitsConfig, + global_limits: AttributeLimits | None = None, +) -> LogRecordLimits: + """Create LogRecordLimits from config. + + Absent fields fall back to global_limits (if provided), then to OTel spec + defaults (128 for counts, unlimited for lengths). + Explicit values suppress env-var reading — matching Java SDK behavior. + """ + attribute_count_limit = config.attribute_count_limit + if attribute_count_limit is None and global_limits is not None: + attribute_count_limit = global_limits.attribute_count_limit + + attribute_value_length_limit = config.attribute_value_length_limit + if attribute_value_length_limit is None and global_limits is not None: + attribute_value_length_limit = global_limits.attribute_value_length_limit + + return LogRecordLimits( + max_attributes=( + attribute_count_limit + if attribute_count_limit is not None + else _DEFAULT_OTEL_LOG_ATTRIBUTE_COUNT_LIMIT + ), + max_attribute_length=attribute_value_length_limit, + ) + + def create_logger_provider( config: LoggerProviderConfig | None, resource: Resource | None = None, + global_attribute_limits: AttributeLimits | None = None, ) -> LoggerProvider: """Create an SDK LoggerProvider from declarative config. @@ -244,21 +280,26 @@ def create_logger_provider( Args: config: LoggerProvider config from the parsed config file, or None. resource: Resource to attach to the provider. + global_attribute_limits: Top-level attribute_limits from the root config, + used as a fallback when per-signal limits are not specified. Returns: A configured LoggerProvider. """ - provider = LoggerProvider(resource=resource) + if config is not None and config.limits is not None: + log_record_limits = _create_log_record_limits( + config.limits, global_attribute_limits + ) + else: + log_record_limits = _create_log_record_limits( + LogRecordLimitsConfig(), global_attribute_limits + ) + + provider = LoggerProvider(resource=resource, log_record_limits=log_record_limits) if config is None: return provider - if config.limits is not None: - _logger.warning( - "log_record_limits are specified in config but are not supported " - "by the Python SDK LoggerProvider constructor; limits will be ignored." - ) - for processor_config in config.processors: provider.add_log_record_processor( _create_log_record_processor(processor_config) @@ -270,6 +311,7 @@ def create_logger_provider( def configure_logger_provider( config: LoggerProviderConfig | None, resource: Resource | None = None, + global_attribute_limits: AttributeLimits | None = None, ) -> None: """Configure the global LoggerProvider from declarative config. @@ -279,7 +321,11 @@ def configure_logger_provider( Args: config: LoggerProvider config from the parsed config file, or None. resource: Resource to attach to the provider. + global_attribute_limits: Top-level attribute_limits from the root config, + used as a fallback when per-signal limits are not specified. """ if config is None: return - set_logger_provider(create_logger_provider(config, resource)) + set_logger_provider( + create_logger_provider(config, resource, global_attribute_limits) + ) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_sdk.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_sdk.py index efa26aa936..10fc8d767b 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_sdk.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_sdk.py @@ -23,6 +23,7 @@ from opentelemetry.sdk._configuration._tracer_provider import ( configure_tracer_provider, ) +from opentelemetry.sdk._configuration.models import AttributeLimits from opentelemetry.sdk._configuration.models import OpenTelemetryConfiguration _logger = logging.getLogger(__name__) @@ -57,8 +58,13 @@ def configure_sdk(config: OpenTelemetryConfiguration) -> None: ) return + global_attribute_limits: AttributeLimits | None = config.attribute_limits resource = create_resource(config.resource) - configure_tracer_provider(config.tracer_provider, resource) + configure_tracer_provider( + config.tracer_provider, resource, global_attribute_limits + ) configure_meter_provider(config.meter_provider, resource) - configure_logger_provider(config.logger_provider, resource) + configure_logger_provider( + config.logger_provider, resource, global_attribute_limits + ) configure_propagator(config.propagator) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_tracer_provider.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_tracer_provider.py index f6c3a06c7f..2717191f47 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_tracer_provider.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_tracer_provider.py @@ -40,6 +40,9 @@ from opentelemetry.sdk._configuration.models import ( SpanExporter as SpanExporterConfig, ) +from opentelemetry.sdk._configuration.models import ( + AttributeLimits, +) from opentelemetry.sdk._configuration.models import ( SpanLimits as SpanLimitsConfig, ) @@ -373,16 +376,28 @@ def _create_parent_based_sampler(config: ParentBasedSamplerConfig) -> Sampler: return ParentBased(**kwargs) -def _create_span_limits(config: SpanLimitsConfig) -> SpanLimits: +def _create_span_limits( + config: SpanLimitsConfig, + global_limits: AttributeLimits | None = None, +) -> SpanLimits: """Create SpanLimits from config. - Absent fields use the OTel spec defaults (128 for counts, unlimited for lengths). + Absent fields fall back to global_limits (if provided), then to OTel spec + defaults (128 for counts, unlimited for lengths). Explicit values suppress env-var reading — matching Java SDK behavior. """ + attribute_count_limit = config.attribute_count_limit + if attribute_count_limit is None and global_limits is not None: + attribute_count_limit = global_limits.attribute_count_limit + + attribute_value_length_limit = config.attribute_value_length_limit + if attribute_value_length_limit is None and global_limits is not None: + attribute_value_length_limit = global_limits.attribute_value_length_limit + return SpanLimits( max_span_attributes=( - config.attribute_count_limit - if config.attribute_count_limit is not None + attribute_count_limit + if attribute_count_limit is not None else _DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT ), max_events=( @@ -405,13 +420,14 @@ def _create_span_limits(config: SpanLimitsConfig) -> SpanLimits: if config.link_attribute_count_limit is not None else _DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT ), - max_attribute_length=config.attribute_value_length_limit, + max_attribute_length=attribute_value_length_limit, ) def create_tracer_provider( config: TracerProviderConfig | None, resource: Resource | None = None, + global_attribute_limits: AttributeLimits | None = None, ) -> TracerProvider: """Create an SDK TracerProvider from declarative config. @@ -422,6 +438,8 @@ def create_tracer_provider( Args: config: TracerProvider config from the parsed config file, or None. resource: Resource to attach to the provider. + global_attribute_limits: Top-level attribute_limits from the root config, + used as a fallback when per-signal limits are not specified. Returns: A configured TracerProvider. @@ -431,17 +449,12 @@ def create_tracer_provider( if config is not None and config.sampler is not None else _DEFAULT_SAMPLER ) - span_limits = ( - _create_span_limits(config.limits) - if config is not None and config.limits is not None - else SpanLimits( - max_span_attributes=_DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, - max_events=_DEFAULT_OTEL_SPAN_EVENT_COUNT_LIMIT, - max_links=_DEFAULT_OTEL_SPAN_LINK_COUNT_LIMIT, - max_event_attributes=_DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, - max_link_attributes=_DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, + if config is not None and config.limits is not None: + span_limits = _create_span_limits(config.limits, global_attribute_limits) + else: + span_limits = _create_span_limits( + SpanLimitsConfig(), global_attribute_limits ) - ) provider = TracerProvider( resource=resource, @@ -459,6 +472,7 @@ def create_tracer_provider( def configure_tracer_provider( config: TracerProviderConfig | None, resource: Resource | None = None, + global_attribute_limits: AttributeLimits | None = None, ) -> None: """Configure the global TracerProvider from declarative config. @@ -469,7 +483,11 @@ def configure_tracer_provider( Args: config: TracerProvider config from the parsed config file, or None. resource: Resource to attach to the provider. + global_attribute_limits: Top-level attribute_limits from the root config, + used as a fallback when per-signal limits are not specified. """ if config is None: return - trace.set_tracer_provider(create_tracer_provider(config, resource)) + trace.set_tracer_provider( + create_tracer_provider(config, resource, global_attribute_limits) + ) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 74b759c328..5eafbae572 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -295,11 +295,13 @@ def _from_api_log_record( record: LogRecord, resource: Resource, instrumentation_scope: InstrumentationScope | None = None, + limits: LogRecordLimits | None = None, ) -> ReadWriteLogRecord: return cls( log_record=record, resource=resource, instrumentation_scope=instrumentation_scope, + **({} if limits is None else {"limits": limits}), ) @@ -671,6 +673,7 @@ def __init__( *, logger_metrics: LoggerMetricsT, _logger_config: _LoggerConfig, + log_record_limits: LogRecordLimits | None = None, ): super().__init__( instrumentation_scope.name, @@ -683,6 +686,7 @@ def __init__( self._instrumentation_scope = instrumentation_scope self._logger_metrics = logger_metrics self._logger_config = _logger_config + self._log_record_limits = log_record_limits or LogRecordLimits() def _is_enabled(self) -> bool: return self._logger_config.is_enabled @@ -728,6 +732,7 @@ def emit( record=record, resource=self._resource, instrumentation_scope=self._instrumentation_scope, + limits=self._log_record_limits, ) else: _set_log_record_exception_attributes(record.log_record) @@ -750,6 +755,7 @@ def emit( record=log_record, resource=self._resource, instrumentation_scope=self._instrumentation_scope, + limits=self._log_record_limits, ) self._logger_metrics.emit_log() @@ -783,6 +789,7 @@ def __init__( *, meter_provider: MeterProvider | None = None, _logger_configurator: _LoggerConfiguratorT | None = None, + log_record_limits: LogRecordLimits | None = None, ): if resource is None: self._resource = Resource.create({}) @@ -802,6 +809,7 @@ def __init__( self._logger_configurator = ( _logger_configurator or _default_logger_configurator ) + self._log_record_limits = log_record_limits or LogRecordLimits() self._at_exit_handler = None if shutdown_on_exit: self._at_exit_handler = atexit.register(self.shutdown) @@ -829,6 +837,7 @@ def _get_logger_no_cache( scope, logger_metrics=self._logger_metrics, _logger_config=self._apply_logger_configurator(scope), + log_record_limits=self._log_record_limits, ) def _get_logger_cached( diff --git a/opentelemetry-sdk/tests/_configuration/test_logger_provider.py b/opentelemetry-sdk/tests/_configuration/test_logger_provider.py index 1835880cf3..b075525c5b 100644 --- a/opentelemetry-sdk/tests/_configuration/test_logger_provider.py +++ b/opentelemetry-sdk/tests/_configuration/test_logger_provider.py @@ -467,29 +467,55 @@ def test_otlp_grpc_exporter_endpoint(self): class TestLogRecordLimits(unittest.TestCase): - def test_limits_logs_warning(self): + def test_default_limits(self): + provider = create_logger_provider(None) + self.assertEqual(provider._log_record_limits.max_attributes, 128) + self.assertIsNone(provider._log_record_limits.max_attribute_length) + + def test_limits_from_config(self): config = LoggerProviderConfig( processors=[], - limits=LogRecordLimitsConfig(attribute_count_limit=64), - ) - with self.assertLogs( - "opentelemetry.sdk._configuration._logger_provider", - level="WARNING", - ) as cm: - create_logger_provider(config) - self.assertTrue( - any("limits" in msg for msg in cm.output), - "Expected warning about unsupported limits", + limits=LogRecordLimitsConfig( + attribute_count_limit=64, + attribute_value_length_limit=256, + ), ) + provider = create_logger_provider(config) + self.assertEqual(provider._log_record_limits.max_attributes, 64) + self.assertEqual(provider._log_record_limits.max_attribute_length, 256) - @staticmethod - def test_no_limits_no_warning(): - config = LoggerProviderConfig(processors=[]) - with patch( - "opentelemetry.sdk._configuration._logger_provider._logger" - ) as mock_logger: - create_logger_provider(config) - mock_logger.warning.assert_not_called() + def test_global_attribute_count_limit_used_when_no_per_signal_limits(self): + from opentelemetry.sdk._configuration.models import AttributeLimits + + global_limits = AttributeLimits(attribute_count_limit=42) + provider = create_logger_provider(None, global_attribute_limits=global_limits) + self.assertEqual(provider._log_record_limits.max_attributes, 42) + + def test_global_attribute_value_length_limit_used_when_no_per_signal_limits(self): + from opentelemetry.sdk._configuration.models import AttributeLimits + + global_limits = AttributeLimits(attribute_value_length_limit=64) + provider = create_logger_provider(None, global_attribute_limits=global_limits) + self.assertEqual(provider._log_record_limits.max_attribute_length, 64) + + def test_per_signal_limits_override_global(self): + from opentelemetry.sdk._configuration.models import AttributeLimits + + global_limits = AttributeLimits( + attribute_count_limit=100, attribute_value_length_limit=200 + ) + config = LoggerProviderConfig( + processors=[], + limits=LogRecordLimitsConfig( + attribute_count_limit=7, + attribute_value_length_limit=16, + ), + ) + provider = create_logger_provider( + config, global_attribute_limits=global_limits + ) + self.assertEqual(provider._log_record_limits.max_attributes, 7) + self.assertEqual(provider._log_record_limits.max_attribute_length, 16) if __name__ == "__main__": diff --git a/opentelemetry-sdk/tests/_configuration/test_sdk.py b/opentelemetry-sdk/tests/_configuration/test_sdk.py index b223ad344c..f46df7f7c2 100644 --- a/opentelemetry-sdk/tests/_configuration/test_sdk.py +++ b/opentelemetry-sdk/tests/_configuration/test_sdk.py @@ -68,9 +68,9 @@ def test_calls_each_signal_with_resource( configure_sdk(config) mock_create_resource.assert_called_once_with(resource_cfg) - mock_tracer.assert_called_once_with(tracer_cfg, sentinel_resource) + mock_tracer.assert_called_once_with(tracer_cfg, sentinel_resource, None) mock_meter.assert_called_once_with(None, sentinel_resource) - mock_logger.assert_called_once_with(None, sentinel_resource) + mock_logger.assert_called_once_with(None, sentinel_resource, None) mock_propagator.assert_called_once_with(propagator_cfg) @patch("opentelemetry.sdk._configuration._sdk.configure_propagator") diff --git a/opentelemetry-sdk/tests/_configuration/test_tracer_provider.py b/opentelemetry-sdk/tests/_configuration/test_tracer_provider.py index abac735fe4..c39384b922 100644 --- a/opentelemetry-sdk/tests/_configuration/test_tracer_provider.py +++ b/opentelemetry-sdk/tests/_configuration/test_tracer_provider.py @@ -15,6 +15,7 @@ create_tracer_provider, ) from opentelemetry.sdk._configuration.file._loader import ConfigurationError +from opentelemetry.sdk._configuration.models import AttributeLimits from opentelemetry.sdk._configuration.models import ( BatchSpanProcessor as BatchSpanProcessorConfig, ) @@ -852,3 +853,50 @@ def test_absent_limits_do_not_read_env_vars(self): provider = self._create_with_limits(SpanLimitsConfig()) self.assertEqual(provider._span_limits.max_span_attributes, 128) self.assertEqual(provider._span_limits.max_events, 128) + + +class TestGlobalAttributeLimitsFallback(unittest.TestCase): + # pylint: disable=no-self-use + + def test_global_attribute_count_limit_used_when_no_per_signal_limits(self): + global_limits = AttributeLimits(attribute_count_limit=42) + provider = create_tracer_provider( + TracerProviderConfig(processors=[]), + global_attribute_limits=global_limits, + ) + self.assertEqual(provider._span_limits.max_span_attributes, 42) + + def test_global_attribute_value_length_limit_used_when_no_per_signal_limits( + self, + ): + global_limits = AttributeLimits(attribute_value_length_limit=64) + provider = create_tracer_provider( + TracerProviderConfig(processors=[]), + global_attribute_limits=global_limits, + ) + self.assertEqual(provider._span_limits.max_attribute_length, 64) + + def test_per_signal_limits_take_precedence_over_global(self): + global_limits = AttributeLimits( + attribute_count_limit=99, + attribute_value_length_limit=99, + ) + provider = create_tracer_provider( + TracerProviderConfig( + processors=[], + limits=SpanLimitsConfig( + attribute_count_limit=7, + attribute_value_length_limit=16, + ), + ), + global_attribute_limits=global_limits, + ) + self.assertEqual(provider._span_limits.max_span_attributes, 7) + self.assertEqual(provider._span_limits.max_attribute_length, 16) + + def test_global_limits_absent_uses_sdk_defaults(self): + provider = create_tracer_provider( + TracerProviderConfig(processors=[]), + ) + self.assertEqual(provider._span_limits.max_span_attributes, 128) + self.assertIsNone(provider._span_limits.max_attribute_length) From 26697303f0b46994a7871d93113abc1288b92f9a Mon Sep 17 00:00:00 2001 From: Diego Hurtado Date: Fri, 26 Jun 2026 15:22:57 -0600 Subject: [PATCH 2/4] fix(lint): move AttributeLimits import to top level, apply ruff format --- .../sdk/_configuration/_logger_provider.py | 14 +++++++---- .../opentelemetry/sdk/_configuration/_sdk.py | 6 +++-- .../sdk/_configuration/_tracer_provider.py | 14 +++++++---- .../_configuration/test_logger_provider.py | 25 ++++++++++--------- 4 files changed, 35 insertions(+), 24 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_logger_provider.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_logger_provider.py index 204e0d881e..d3ba06fa67 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_logger_provider.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_logger_provider.py @@ -17,9 +17,6 @@ from opentelemetry.sdk._configuration.models import ( BatchLogRecordProcessor as BatchLogRecordProcessorConfig, ) -from opentelemetry.sdk._configuration.models import ( - LogRecordLimits as LogRecordLimitsConfig, -) from opentelemetry.sdk._configuration.models import ( ExperimentalOtlpFileExporter as ExperimentalOtlpFileExporterConfig, ) @@ -29,6 +26,9 @@ from opentelemetry.sdk._configuration.models import ( LogRecordExporter as LogRecordExporterConfig, ) +from opentelemetry.sdk._configuration.models import ( + LogRecordLimits as LogRecordLimitsConfig, +) from opentelemetry.sdk._configuration.models import ( LogRecordProcessor as LogRecordProcessorConfig, ) @@ -255,7 +255,9 @@ def _create_log_record_limits( attribute_value_length_limit = config.attribute_value_length_limit if attribute_value_length_limit is None and global_limits is not None: - attribute_value_length_limit = global_limits.attribute_value_length_limit + attribute_value_length_limit = ( + global_limits.attribute_value_length_limit + ) return LogRecordLimits( max_attributes=( @@ -295,7 +297,9 @@ def create_logger_provider( LogRecordLimitsConfig(), global_attribute_limits ) - provider = LoggerProvider(resource=resource, log_record_limits=log_record_limits) + provider = LoggerProvider( + resource=resource, log_record_limits=log_record_limits + ) if config is None: return provider diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_sdk.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_sdk.py index 10fc8d767b..3d413f62d5 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_sdk.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_sdk.py @@ -23,8 +23,10 @@ from opentelemetry.sdk._configuration._tracer_provider import ( configure_tracer_provider, ) -from opentelemetry.sdk._configuration.models import AttributeLimits -from opentelemetry.sdk._configuration.models import OpenTelemetryConfiguration +from opentelemetry.sdk._configuration.models import ( + AttributeLimits, + OpenTelemetryConfiguration, +) _logger = logging.getLogger(__name__) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_tracer_provider.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_tracer_provider.py index 2717191f47..70b00da24f 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_tracer_provider.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_tracer_provider.py @@ -13,6 +13,9 @@ load_entry_point, ) from opentelemetry.sdk._configuration._exceptions import ConfigurationError +from opentelemetry.sdk._configuration.models import ( + AttributeLimits, +) from opentelemetry.sdk._configuration.models import ( ExperimentalComposableRuleBasedSampler as RuleBasedSamplerConfig, ) @@ -40,9 +43,6 @@ from opentelemetry.sdk._configuration.models import ( SpanExporter as SpanExporterConfig, ) -from opentelemetry.sdk._configuration.models import ( - AttributeLimits, -) from opentelemetry.sdk._configuration.models import ( SpanLimits as SpanLimitsConfig, ) @@ -392,7 +392,9 @@ def _create_span_limits( attribute_value_length_limit = config.attribute_value_length_limit if attribute_value_length_limit is None and global_limits is not None: - attribute_value_length_limit = global_limits.attribute_value_length_limit + attribute_value_length_limit = ( + global_limits.attribute_value_length_limit + ) return SpanLimits( max_span_attributes=( @@ -450,7 +452,9 @@ def create_tracer_provider( else _DEFAULT_SAMPLER ) if config is not None and config.limits is not None: - span_limits = _create_span_limits(config.limits, global_attribute_limits) + span_limits = _create_span_limits( + config.limits, global_attribute_limits + ) else: span_limits = _create_span_limits( SpanLimitsConfig(), global_attribute_limits diff --git a/opentelemetry-sdk/tests/_configuration/test_logger_provider.py b/opentelemetry-sdk/tests/_configuration/test_logger_provider.py index b075525c5b..55a34e1a36 100644 --- a/opentelemetry-sdk/tests/_configuration/test_logger_provider.py +++ b/opentelemetry-sdk/tests/_configuration/test_logger_provider.py @@ -22,6 +22,10 @@ create_logger_provider, ) from opentelemetry.sdk._configuration.file._loader import ConfigurationError +from opentelemetry.sdk._configuration.models import ( + AttributeLimits, + NameStringValuePair, +) from opentelemetry.sdk._configuration.models import ( BatchLogRecordProcessor as BatchLogRecordProcessorConfig, ) @@ -40,9 +44,6 @@ from opentelemetry.sdk._configuration.models import ( LogRecordProcessor as LogRecordProcessorConfig, ) -from opentelemetry.sdk._configuration.models import ( - NameStringValuePair, -) from opentelemetry.sdk._configuration.models import ( OtlpGrpcExporter as OtlpGrpcExporterConfig, ) @@ -485,22 +486,22 @@ def test_limits_from_config(self): self.assertEqual(provider._log_record_limits.max_attribute_length, 256) def test_global_attribute_count_limit_used_when_no_per_signal_limits(self): - from opentelemetry.sdk._configuration.models import AttributeLimits - global_limits = AttributeLimits(attribute_count_limit=42) - provider = create_logger_provider(None, global_attribute_limits=global_limits) + provider = create_logger_provider( + None, global_attribute_limits=global_limits + ) self.assertEqual(provider._log_record_limits.max_attributes, 42) - def test_global_attribute_value_length_limit_used_when_no_per_signal_limits(self): - from opentelemetry.sdk._configuration.models import AttributeLimits - + def test_global_attribute_value_length_limit_used_when_no_per_signal_limits( + self, + ): global_limits = AttributeLimits(attribute_value_length_limit=64) - provider = create_logger_provider(None, global_attribute_limits=global_limits) + provider = create_logger_provider( + None, global_attribute_limits=global_limits + ) self.assertEqual(provider._log_record_limits.max_attribute_length, 64) def test_per_signal_limits_override_global(self): - from opentelemetry.sdk._configuration.models import AttributeLimits - global_limits = AttributeLimits( attribute_count_limit=100, attribute_value_length_limit=200 ) From 9e6b9b0e23224a286a2cfca3b75efe37c2d991db Mon Sep 17 00:00:00 2001 From: Diego Hurtado Date: Fri, 26 Jun 2026 15:23:16 -0600 Subject: [PATCH 3/4] chore: add changelog fragment for #5365 --- .changelog/5365.added | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/5365.added diff --git a/.changelog/5365.added b/.changelog/5365.added new file mode 100644 index 0000000000..c522a00668 --- /dev/null +++ b/.changelog/5365.added @@ -0,0 +1 @@ +`opentelemetry-sdk`: wire top-level `attribute_limits` into per-signal providers via declarative config; add `log_record_limits` support to `LoggerProvider` \ No newline at end of file From 1f0393f288ec4ebd3179bc218a1fdee941ed3944 Mon Sep 17 00:00:00 2001 From: Diego Hurtado Date: Fri, 26 Jun 2026 15:28:05 -0600 Subject: [PATCH 4/4] Fix ruff --- .../src/opentelemetry/sdk/trace/export/__init__.py | 2 +- opentelemetry-sdk/tests/_configuration/test_sdk.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py index 067fb3ded3..ad8f57840a 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py @@ -333,7 +333,7 @@ def __init__( out: typing.IO = sys.stdout, formatter: collections.abc.Callable[ [ReadableSpan], str - ] = lambda span: (span.to_json() + linesep), + ] = lambda span: span.to_json() + linesep, ): self.out = out self.formatter = formatter diff --git a/opentelemetry-sdk/tests/_configuration/test_sdk.py b/opentelemetry-sdk/tests/_configuration/test_sdk.py index f46df7f7c2..94e66540b7 100644 --- a/opentelemetry-sdk/tests/_configuration/test_sdk.py +++ b/opentelemetry-sdk/tests/_configuration/test_sdk.py @@ -68,7 +68,9 @@ def test_calls_each_signal_with_resource( configure_sdk(config) mock_create_resource.assert_called_once_with(resource_cfg) - mock_tracer.assert_called_once_with(tracer_cfg, sentinel_resource, None) + mock_tracer.assert_called_once_with( + tracer_cfg, sentinel_resource, None + ) mock_meter.assert_called_once_with(None, sentinel_resource) mock_logger.assert_called_once_with(None, sentinel_resource, None) mock_propagator.assert_called_once_with(propagator_cfg)