From 549f0f067ce5b685516943e920686db6b22a73ea Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Mon, 6 Oct 2025 15:08:44 -0700 Subject: [PATCH 01/34] Implement filtering logic for min_severity and trace_based parameters --- CHANGELOG.md | 1 + .../sdk/_logs/_internal/__init__.py | 29 +++ opentelemetry-sdk/tests/logs/test_logs.py | 199 +++++++++++++++++- 3 files changed, 228 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70b13589e3..beea0c9abe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#4737](https://github.com/open-telemetry/opentelemetry-python/pull/4737)) - logs: add warnings for classes that would be deprecated and renamed in 1.39.0 ([#4771](https://github.com/open-telemetry/opentelemetry-python/pull/4771)) +- Add `minimum_severity` and `trace_based` logger parameters to filter logs ## Version 1.37.0/0.58b0 (2025-09-11) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 9e2d3f7d7f..b845217621 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -685,6 +685,8 @@ def __init__( ConcurrentMultiLogRecordProcessor, ], instrumentation_scope: InstrumentationScope, + min_severity_level: SeverityNumber = SeverityNumber.UNSPECIFIED, + trace_based: bool = False, ): super().__init__( instrumentation_scope.name, @@ -695,6 +697,8 @@ def __init__( self._resource = resource self._multi_log_record_processor = multi_log_record_processor self._instrumentation_scope = instrumentation_scope + self._min_severity_level = min_severity_level + self._trace_based = trace_based @property def resource(self): @@ -757,6 +761,10 @@ def emit( record = LogRecord._from_api_log_record( record=record, resource=self._resource ) + if is_less_than_min_severity(record, self._min_severity_level): + return + if should_drop_logs_for_trace_based(record, self._trace_based): + return log_data = LogData(record, self._instrumentation_scope) @@ -771,6 +779,8 @@ def __init__( multi_log_record_processor: SynchronousMultiLogRecordProcessor | ConcurrentMultiLogRecordProcessor | None = None, + min_severity_level: SeverityNumber = SeverityNumber.UNSPECIFIED, + trace_based: bool = False, ): if resource is None: self._resource = Resource.create({}) @@ -786,6 +796,8 @@ def __init__( self._at_exit_handler = atexit.register(self.shutdown) self._logger_cache = {} self._logger_cache_lock = Lock() + self._min_severity_level = min_severity_level + self._trace_based = trace_based @property def resource(self): @@ -807,6 +819,8 @@ def _get_logger_no_cache( schema_url, attributes, ), + self._min_severity_level, + self._trace_based, ) def _get_logger_cached( @@ -933,3 +947,18 @@ def std_to_otel(levelno: int) -> SeverityNumber: if levelno > 53: return SeverityNumber.FATAL4 return _STD_TO_OTEL[levelno] + +def is_less_than_min_severity(record: LogRecord, min_severity: SeverityNumber) -> bool: + if record.severity_number is not None: + if min_severity is not None and min_severity != SeverityNumber.UNSPECIFIED and record.severity_number.value < min_severity.value: + return True + return False + +def should_drop_logs_for_trace_based(record: LogRecord, trace_state_enabled: bool) -> bool: + if trace_state_enabled: + if record.context is not None: + span = get_current_span(record.context) + span_context = span.get_span_context() + if span_context.is_valid and not span_context.trace_flags.sampled: + return True + return False diff --git a/opentelemetry-sdk/tests/logs/test_logs.py b/opentelemetry-sdk/tests/logs/test_logs.py index e4849e07a2..b6cbc81034 100644 --- a/opentelemetry-sdk/tests/logs/test_logs.py +++ b/opentelemetry-sdk/tests/logs/test_logs.py @@ -29,6 +29,9 @@ NoOpLogger, SynchronousMultiLogRecordProcessor, ) +from opentelemetry._logs import ( + SeverityNumber, +) from opentelemetry.sdk.environment_variables import OTEL_SDK_DISABLED from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.util.instrumentation import InstrumentationScope @@ -74,6 +77,8 @@ def test_get_logger(self): self.assertEqual( logger._instrumentation_scope.attributes, {"key": "value"} ) + self.assertEqual(logger._min_severity_level, SeverityNumber.UNSPECIFIED) + self.assertFalse(logger._trace_based) @patch.dict("os.environ", {OTEL_SDK_DISABLED: "true"}) def test_get_logger_with_sdk_disabled(self): @@ -83,7 +88,7 @@ def test_get_logger_with_sdk_disabled(self): @patch.object(Resource, "create") def test_logger_provider_init(self, resource_patch): - logger_provider = LoggerProvider() + logger_provider = LoggerProvider(min_severity_level=SeverityNumber.DEBUG4, trace_based=True) resource_patch.assert_called_once() self.assertIsNotNone(logger_provider._resource) self.assertTrue( @@ -92,6 +97,8 @@ def test_logger_provider_init(self, resource_patch): SynchronousMultiLogRecordProcessor, ) ) + self.assertEqual(logger_provider._min_severity_level, SeverityNumber.DEBUG4) + self.assertTrue(logger_provider._trace_based) self.assertIsNotNone(logger_provider._at_exit_handler) @@ -171,3 +178,193 @@ def test_can_emit_with_keywords_arguments(self): self.assertEqual(log_record.attributes, {"some": "attributes"}) self.assertEqual(log_record.event_name, "event_name") self.assertEqual(log_record.resource, logger.resource) + + def test_emit_logrecord_with_min_severity_filtering(self): + """Test that logs below minimum severity are filtered out""" + logger, log_record_processor_mock = self._get_logger() + logger._min_severity_level = SeverityNumber.DEBUG4 + + log_record_info = LogRecord( + observed_timestamp=0, + body="info log line", + severity_number=SeverityNumber.DEBUG, + severity_text="DEBUG", + ) + + logger.emit(log_record_info) + log_record_processor_mock.on_emit.assert_not_called() + + log_record_processor_mock.reset_mock() + + log_record_error = LogRecord( + observed_timestamp=0, + body="error log line", + severity_number=SeverityNumber.ERROR, + severity_text="ERROR", + ) + + logger.emit(log_record_error) + + log_record_processor_mock.on_emit.assert_called_once() + log_data = log_record_processor_mock.on_emit.call_args.args[0] + self.assertTrue(isinstance(log_data.log_record, LogRecord)) + self.assertEqual(log_data.log_record.severity_number, SeverityNumber.ERROR) + + def test_emit_logrecord_with_min_severity_unspecified(self): + """Test that when min severity is UNSPECIFIED, all logs are emitted""" + logger, log_record_processor_mock = self._get_logger() + log_record = LogRecord( + observed_timestamp=0, + body="debug log line", + severity_number=SeverityNumber.DEBUG, + severity_text="DEBUG", + ) + logger.emit(log_record) + log_record_processor_mock.on_emit.assert_called_once() + + def test_emit_logrecord_with_trace_based_filtering(self): + """Test that logs are filtered based on trace sampling state""" + logger, log_record_processor_mock = self._get_logger() + logger._trace_based = True + + mock_span_context = Mock() + mock_span_context.is_valid = True + mock_span_context.trace_flags.sampled = False + + mock_span = Mock() + mock_span.get_span_context.return_value = mock_span_context + + mock_context = Mock() + + with patch('opentelemetry.sdk._logs._internal.get_current_span', return_value=mock_span): + log_record = LogRecord( + observed_timestamp=0, + body="should be dropped", + severity_number=SeverityNumber.INFO, + severity_text="INFO", + context=mock_context, + ) + + logger.emit(log_record) + log_record_processor_mock.on_emit.assert_not_called() + + log_record_processor_mock.reset_mock() + + mock_span_context = Mock() + mock_span_context.is_valid = True + mock_span_context.trace_flags.sampled = True + + mock_span = Mock() + mock_span.get_span_context.return_value = mock_span_context + + def test_emit_logrecord_trace_filtering_disabled(self): + """Test that when trace-based filtering is disabled, all logs are emitted""" + logger, log_record_processor_mock = self._get_logger() + + mock_span_context = Mock() + mock_span_context.is_valid = False + mock_span_context.trace_flags.sampled = False + + mock_span = Mock() + mock_span.get_span_context.return_value = mock_span_context + + mock_context = Mock() + + with patch('opentelemetry.sdk._logs._internal.get_current_span', return_value=mock_span): + log_record = LogRecord( + observed_timestamp=0, + body="should be emitted when filtering disabled", + severity_number=SeverityNumber.INFO, + severity_text="INFO", + context=mock_context, + ) + + logger.emit(log_record) + log_record_processor_mock.on_emit.assert_called_once() + + def test_emit_logrecord_trace_filtering_edge_cases(self): + """Test edge cases for trace-based filtering""" + logger, log_record_processor_mock = self._get_logger() + logger._trace_based = True + + mock_span_context = Mock() + mock_span_context.is_valid = False + mock_span_context.trace_flags.sampled = True + + mock_span = Mock() + mock_span.get_span_context.return_value = mock_span_context + + mock_context = Mock() + + with patch('opentelemetry.sdk._logs._internal.get_current_span', return_value=mock_span): + log_record = LogRecord( + observed_timestamp=0, + body="invalid but sampled", + severity_number=SeverityNumber.INFO, + severity_text="INFO", + context=mock_context, + ) + + logger.emit(log_record) + log_record_processor_mock.on_emit.assert_called_once() + + log_record_processor_mock.reset_mock() + + mock_span_context = Mock() + mock_span_context.is_valid = True + mock_span_context.trace_flags.sampled = False + + mock_span = Mock() + mock_span.get_span_context.return_value = mock_span_context + + with patch('opentelemetry.sdk._logs._internal.get_current_span', return_value=mock_span): + log_record = LogRecord( + observed_timestamp=0, + body="valid but not sampled", + severity_number=SeverityNumber.INFO, + severity_text="INFO", + context=mock_context, + ) + + logger.emit(log_record) + log_record_processor_mock.on_emit.assert_not_called() + + def test_emit_both_min_severity_and_trace_based_filtering(self): + """Test that both min severity and trace-based filtering work together""" + logger, log_record_processor_mock = self._get_logger() + logger._min_severity_level = SeverityNumber.WARN + logger._trace_based = True + + mock_span_context = Mock() + mock_span_context.is_valid = True + mock_span_context.trace_flags.sampled = True + + mock_span = Mock() + mock_span.get_span_context.return_value = mock_span_context + + mock_context = Mock() + + with patch('opentelemetry.sdk._logs._internal.get_current_span', return_value=mock_span): + log_record_info = LogRecord( + observed_timestamp=0, + body="info log line", + severity_number=SeverityNumber.INFO, + severity_text="INFO", + context=mock_context, + ) + + logger.emit(log_record_info) + log_record_processor_mock.on_emit.assert_not_called() + + log_record_processor_mock.reset_mock() + + log_record_error = LogRecord( + observed_timestamp=0, + body="error log line", + severity_number=SeverityNumber.ERROR, + severity_text="ERROR", + context=mock_context, + ) + + logger.emit(log_record_error) + log_record_processor_mock.on_emit.assert_called_once() From 6db2394a7aad0a0cfdcfc5da6398b203f07ec926 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Mon, 6 Oct 2025 15:11:19 -0700 Subject: [PATCH 02/34] Updated CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index beea0c9abe..64129fb1f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - logs: add warnings for classes that would be deprecated and renamed in 1.39.0 ([#4771](https://github.com/open-telemetry/opentelemetry-python/pull/4771)) - Add `minimum_severity` and `trace_based` logger parameters to filter logs + ([#4765](https://github.com/open-telemetry/opentelemetry-python/pull/4765)) ## Version 1.37.0/0.58b0 (2025-09-11) From ff857b14881d11663693d9faebaeb7a0a2343593 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Mon, 6 Oct 2025 15:17:35 -0700 Subject: [PATCH 03/34] Retrigger CI/CD pipeline From 7f13cff5cda9942c36d1fc62a1c39b9811ef157e Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Mon, 6 Oct 2025 15:21:23 -0700 Subject: [PATCH 04/34] Remove re-import statement --- opentelemetry-sdk/tests/logs/test_logs.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/opentelemetry-sdk/tests/logs/test_logs.py b/opentelemetry-sdk/tests/logs/test_logs.py index b6cbc81034..3ee32336fa 100644 --- a/opentelemetry-sdk/tests/logs/test_logs.py +++ b/opentelemetry-sdk/tests/logs/test_logs.py @@ -29,9 +29,6 @@ NoOpLogger, SynchronousMultiLogRecordProcessor, ) -from opentelemetry._logs import ( - SeverityNumber, -) from opentelemetry.sdk.environment_variables import OTEL_SDK_DISABLED from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.util.instrumentation import InstrumentationScope From bf80f9696976316fe3d0b967f662aeda759f5500 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Mon, 6 Oct 2025 15:29:35 -0700 Subject: [PATCH 05/34] Fix lint --- .../sdk/_logs/_internal/__init__.py | 16 ++++++-- opentelemetry-sdk/tests/logs/test_logs.py | 41 +++++++++++++++---- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index b845217621..7aa36da51b 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -948,13 +948,23 @@ def std_to_otel(levelno: int) -> SeverityNumber: return SeverityNumber.FATAL4 return _STD_TO_OTEL[levelno] -def is_less_than_min_severity(record: LogRecord, min_severity: SeverityNumber) -> bool: + +def is_less_than_min_severity( + record: LogRecord, min_severity: SeverityNumber +) -> bool: if record.severity_number is not None: - if min_severity is not None and min_severity != SeverityNumber.UNSPECIFIED and record.severity_number.value < min_severity.value: + if ( + min_severity is not None + and min_severity != SeverityNumber.UNSPECIFIED + and record.severity_number.value < min_severity.value + ): return True return False -def should_drop_logs_for_trace_based(record: LogRecord, trace_state_enabled: bool) -> bool: + +def should_drop_logs_for_trace_based( + record: LogRecord, trace_state_enabled: bool +) -> bool: if trace_state_enabled: if record.context is not None: span = get_current_span(record.context) diff --git a/opentelemetry-sdk/tests/logs/test_logs.py b/opentelemetry-sdk/tests/logs/test_logs.py index 3ee32336fa..8bf691f6ed 100644 --- a/opentelemetry-sdk/tests/logs/test_logs.py +++ b/opentelemetry-sdk/tests/logs/test_logs.py @@ -74,7 +74,9 @@ def test_get_logger(self): self.assertEqual( logger._instrumentation_scope.attributes, {"key": "value"} ) - self.assertEqual(logger._min_severity_level, SeverityNumber.UNSPECIFIED) + self.assertEqual( + logger._min_severity_level, SeverityNumber.UNSPECIFIED + ) self.assertFalse(logger._trace_based) @patch.dict("os.environ", {OTEL_SDK_DISABLED: "true"}) @@ -85,7 +87,9 @@ def test_get_logger_with_sdk_disabled(self): @patch.object(Resource, "create") def test_logger_provider_init(self, resource_patch): - logger_provider = LoggerProvider(min_severity_level=SeverityNumber.DEBUG4, trace_based=True) + logger_provider = LoggerProvider( + min_severity_level=SeverityNumber.DEBUG4, trace_based=True + ) resource_patch.assert_called_once() self.assertIsNotNone(logger_provider._resource) self.assertTrue( @@ -94,7 +98,9 @@ def test_logger_provider_init(self, resource_patch): SynchronousMultiLogRecordProcessor, ) ) - self.assertEqual(logger_provider._min_severity_level, SeverityNumber.DEBUG4) + self.assertEqual( + logger_provider._min_severity_level, SeverityNumber.DEBUG4 + ) self.assertTrue(logger_provider._trace_based) self.assertIsNotNone(logger_provider._at_exit_handler) @@ -205,7 +211,9 @@ def test_emit_logrecord_with_min_severity_filtering(self): log_record_processor_mock.on_emit.assert_called_once() log_data = log_record_processor_mock.on_emit.call_args.args[0] self.assertTrue(isinstance(log_data.log_record, LogRecord)) - self.assertEqual(log_data.log_record.severity_number, SeverityNumber.ERROR) + self.assertEqual( + log_data.log_record.severity_number, SeverityNumber.ERROR + ) def test_emit_logrecord_with_min_severity_unspecified(self): """Test that when min severity is UNSPECIFIED, all logs are emitted""" @@ -233,7 +241,10 @@ def test_emit_logrecord_with_trace_based_filtering(self): mock_context = Mock() - with patch('opentelemetry.sdk._logs._internal.get_current_span', return_value=mock_span): + with patch( + "opentelemetry.sdk._logs._internal.get_current_span", + return_value=mock_span, + ): log_record = LogRecord( observed_timestamp=0, body="should be dropped", @@ -267,7 +278,10 @@ def test_emit_logrecord_trace_filtering_disabled(self): mock_context = Mock() - with patch('opentelemetry.sdk._logs._internal.get_current_span', return_value=mock_span): + with patch( + "opentelemetry.sdk._logs._internal.get_current_span", + return_value=mock_span, + ): log_record = LogRecord( observed_timestamp=0, body="should be emitted when filtering disabled", @@ -293,7 +307,10 @@ def test_emit_logrecord_trace_filtering_edge_cases(self): mock_context = Mock() - with patch('opentelemetry.sdk._logs._internal.get_current_span', return_value=mock_span): + with patch( + "opentelemetry.sdk._logs._internal.get_current_span", + return_value=mock_span, + ): log_record = LogRecord( observed_timestamp=0, body="invalid but sampled", @@ -314,7 +331,10 @@ def test_emit_logrecord_trace_filtering_edge_cases(self): mock_span = Mock() mock_span.get_span_context.return_value = mock_span_context - with patch('opentelemetry.sdk._logs._internal.get_current_span', return_value=mock_span): + with patch( + "opentelemetry.sdk._logs._internal.get_current_span", + return_value=mock_span, + ): log_record = LogRecord( observed_timestamp=0, body="valid but not sampled", @@ -341,7 +361,10 @@ def test_emit_both_min_severity_and_trace_based_filtering(self): mock_context = Mock() - with patch('opentelemetry.sdk._logs._internal.get_current_span', return_value=mock_span): + with patch( + "opentelemetry.sdk._logs._internal.get_current_span", + return_value=mock_span, + ): log_record_info = LogRecord( observed_timestamp=0, body="info log line", From bdae3e0c10882c0fed4191d2d85e4ef876bd294d Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 7 Oct 2025 08:31:17 -0700 Subject: [PATCH 06/34] Addressed comments --- .../sdk/_logs/_internal/__init__.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 7aa36da51b..b6565d0fe9 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -763,7 +763,7 @@ def emit( ) if is_less_than_min_severity(record, self._min_severity_level): return - if should_drop_logs_for_trace_based(record, self._trace_based): + if should_drop_logs_for_unsampled_trace(record, self._trace_based): return log_data = LogData(record, self._instrumentation_scope) @@ -952,6 +952,11 @@ def std_to_otel(levelno: int) -> SeverityNumber: def is_less_than_min_severity( record: LogRecord, min_severity: SeverityNumber ) -> bool: + """ + Check if the log record's severity number is less than the minimum severity level. If a log record's severity number is + specified (i.e. not `0`) and is less than the configured `minimum_severity`, the log record MUST be dropped by the `Logger`. + Log records with an unspecified severity (i.e. `0`) are not affected by this parameter and therefore bypass minimum severity filtering. + """ if record.severity_number is not None: if ( min_severity is not None @@ -962,10 +967,16 @@ def is_less_than_min_severity( return False -def should_drop_logs_for_trace_based( - record: LogRecord, trace_state_enabled: bool +def should_drop_logs_for_unsampled_trace( + record: LogRecord, trace_based: bool ) -> bool: - if trace_state_enabled: + """ + Determines whether the logger should only process log records associated with sampled traces. + If not explicitly set, the `trace_based` parameter is defaulted to `false`. If `trace_based` is `true`, log records associated with unsampled traces MUST + be dropped by the `Logger`. A log record is considered associated with an unsampled trace if it has a valid `SpanId` and its `TraceFlags` indicate that the trace is unsampled. + Log records that aren't associated with a trace context are not affected by this parameter and therefore bypass trace-based filtering. + """ + if trace_based: if record.context is not None: span = get_current_span(record.context) span_context = span.get_span_context() From 63826b26893b9827aeb443a192fe043f091b63e0 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 7 Oct 2025 08:43:14 -0700 Subject: [PATCH 07/34] Fix indentation --- .../src/opentelemetry/sdk/_logs/_internal/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index b6565d0fe9..25eb7b68f7 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -972,9 +972,10 @@ def should_drop_logs_for_unsampled_trace( ) -> bool: """ Determines whether the logger should only process log records associated with sampled traces. - If not explicitly set, the `trace_based` parameter is defaulted to `false`. If `trace_based` is `true`, log records associated with unsampled traces MUST - be dropped by the `Logger`. A log record is considered associated with an unsampled trace if it has a valid `SpanId` and its `TraceFlags` indicate that the trace is unsampled. - Log records that aren't associated with a trace context are not affected by this parameter and therefore bypass trace-based filtering. + If not explicitly set, the `trace_based` parameter is set to `false`. If `trace_based` is `true`, log records associated with unsampled traces + are dropped by the `Logger`. A log record is considered associated with an unsampled trace if it has a valid `SpanId` and its `TraceFlags` indicate + that the trace is unsampled. A log record that isn't associated with a trace context is not affected by this parameter and therefore bypasses + trace-based filtering. """ if trace_based: if record.context is not None: From c580f92e07f4563222b76bbc3e639b9c1ecad706 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 7 Oct 2025 08:51:59 -0700 Subject: [PATCH 08/34] Fix ruff --- .../src/opentelemetry/sdk/_logs/_internal/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 25eb7b68f7..0439ecb0fe 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -972,9 +972,9 @@ def should_drop_logs_for_unsampled_trace( ) -> bool: """ Determines whether the logger should only process log records associated with sampled traces. - If not explicitly set, the `trace_based` parameter is set to `false`. If `trace_based` is `true`, log records associated with unsampled traces - are dropped by the `Logger`. A log record is considered associated with an unsampled trace if it has a valid `SpanId` and its `TraceFlags` indicate - that the trace is unsampled. A log record that isn't associated with a trace context is not affected by this parameter and therefore bypasses + If not explicitly set, the `trace_based` parameter is set to `false`. If `trace_based` is `true`, log records associated with unsampled traces + are dropped by the `Logger`. A log record is considered associated with an unsampled trace if it has a valid `SpanId` and its `TraceFlags` indicate + that the trace is unsampled. A log record that isn't associated with a trace context is not affected by this parameter and therefore bypasses trace-based filtering. """ if trace_based: From 2fb7f0a0c8cfe7802eb4bc2dc7708a7497d16c88 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 7 Oct 2025 09:00:50 -0700 Subject: [PATCH 09/34] Retrigger CI/CD pipeline From 4c4b337a319aa52e07a8d86b958b970b122c9dfd Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 7 Oct 2025 09:18:58 -0700 Subject: [PATCH 10/34] Retrigger CI/CD pipeline From c8ecfff48dc1a85ccb4ce31ba7f84bdbbd0a6e0a Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 7 Oct 2025 09:25:48 -0700 Subject: [PATCH 11/34] Retrigger CI/CD pipeline From 11f8cb5fbf9b77a4e659d567049cd81bdb30220f Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 7 Oct 2025 10:46:26 -0700 Subject: [PATCH 12/34] Retrigger CI/CD pipeline From 45822b574167b1b62b65852cb9fec695b8dd6b42 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 7 Oct 2025 10:52:12 -0700 Subject: [PATCH 13/34] fix documentation for min_severity method --- .../sdk/_logs/_internal/__init__.py | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 0439ecb0fe..9da22c4563 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -952,10 +952,16 @@ def std_to_otel(levelno: int) -> SeverityNumber: def is_less_than_min_severity( record: LogRecord, min_severity: SeverityNumber ) -> bool: - """ - Check if the log record's severity number is less than the minimum severity level. If a log record's severity number is - specified (i.e. not `0`) and is less than the configured `minimum_severity`, the log record MUST be dropped by the `Logger`. - Log records with an unspecified severity (i.e. `0`) are not affected by this parameter and therefore bypass minimum severity filtering. + """Check if the log record's severity number is less than the minimum severity level. + + Args: + record: The log record to be processed. + min_severity: The minimum severity level. + + Returns: + True if the log record's severity number is less than the minimum + severity level, False otherwise. Log records with an unspecified severity (i.e. `0`) are not + affected by this parameter and therefore bypass minimum severity filtering. """ if record.severity_number is not None: if ( @@ -970,13 +976,6 @@ def is_less_than_min_severity( def should_drop_logs_for_unsampled_trace( record: LogRecord, trace_based: bool ) -> bool: - """ - Determines whether the logger should only process log records associated with sampled traces. - If not explicitly set, the `trace_based` parameter is set to `false`. If `trace_based` is `true`, log records associated with unsampled traces - are dropped by the `Logger`. A log record is considered associated with an unsampled trace if it has a valid `SpanId` and its `TraceFlags` indicate - that the trace is unsampled. A log record that isn't associated with a trace context is not affected by this parameter and therefore bypasses - trace-based filtering. - """ if trace_based: if record.context is not None: span = get_current_span(record.context) From 5fb6bc5dcb6d640b131a274613ccc7bc48c41d11 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 7 Oct 2025 11:40:06 -0700 Subject: [PATCH 14/34] Fix doc --- .../opentelemetry/sdk/_logs/_internal/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 9da22c4563..2b79606fa6 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -954,14 +954,14 @@ def is_less_than_min_severity( ) -> bool: """Check if the log record's severity number is less than the minimum severity level. - Args: - record: The log record to be processed. - min_severity: The minimum severity level. + Args: + record: The log record to be processed. + min_severity: The minimum severity level. - Returns: - True if the log record's severity number is less than the minimum - severity level, False otherwise. Log records with an unspecified severity (i.e. `0`) are not - affected by this parameter and therefore bypass minimum severity filtering. + Returns: + True if the log record's severity number is less than the minimum + severity level, False otherwise. Log records with an unspecified severity (i.e. `0`) + are not affected by this parameter and therefore bypass minimum severity filtering. """ if record.severity_number is not None: if ( From 8bb4d154998f77e612cbac34e941f4dc6e7a53ea Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 7 Oct 2025 11:54:06 -0700 Subject: [PATCH 15/34] Test if removing docstrings fixes failures --- .../src/opentelemetry/sdk/_logs/_internal/__init__.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 2b79606fa6..000a0b0262 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -952,17 +952,6 @@ def std_to_otel(levelno: int) -> SeverityNumber: def is_less_than_min_severity( record: LogRecord, min_severity: SeverityNumber ) -> bool: - """Check if the log record's severity number is less than the minimum severity level. - - Args: - record: The log record to be processed. - min_severity: The minimum severity level. - - Returns: - True if the log record's severity number is less than the minimum - severity level, False otherwise. Log records with an unspecified severity (i.e. `0`) - are not affected by this parameter and therefore bypass minimum severity filtering. - """ if record.severity_number is not None: if ( min_severity is not None From e89621ae5c8c2fb276d6fed399aea927171a44d2 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 7 Oct 2025 12:58:37 -0700 Subject: [PATCH 16/34] Retrigger CI/CD pipeline From 4eaecaa3908b315261a1e9301a75e59656f289f9 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 7 Oct 2025 13:11:47 -0700 Subject: [PATCH 17/34] Revert to previous state --- .../src/opentelemetry/sdk/_logs/_internal/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 000a0b0262..7aa36da51b 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -763,7 +763,7 @@ def emit( ) if is_less_than_min_severity(record, self._min_severity_level): return - if should_drop_logs_for_unsampled_trace(record, self._trace_based): + if should_drop_logs_for_trace_based(record, self._trace_based): return log_data = LogData(record, self._instrumentation_scope) @@ -962,10 +962,10 @@ def is_less_than_min_severity( return False -def should_drop_logs_for_unsampled_trace( - record: LogRecord, trace_based: bool +def should_drop_logs_for_trace_based( + record: LogRecord, trace_state_enabled: bool ) -> bool: - if trace_based: + if trace_state_enabled: if record.context is not None: span = get_current_span(record.context) span_context = span.get_span_context() From 000cad0f5f9690efc6fbd6eb1cec05f687bc1540 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 7 Oct 2025 14:29:27 -0700 Subject: [PATCH 18/34] Add docstrings and rename function --- .../sdk/_logs/_internal/__init__.py | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 7aa36da51b..1fcedd5d59 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -763,7 +763,7 @@ def emit( ) if is_less_than_min_severity(record, self._min_severity_level): return - if should_drop_logs_for_trace_based(record, self._trace_based): + if should_drop_logs_for_unsampled_traces(record, self._trace_based): return log_data = LogData(record, self._instrumentation_scope) @@ -952,6 +952,17 @@ def std_to_otel(levelno: int) -> SeverityNumber: def is_less_than_min_severity( record: LogRecord, min_severity: SeverityNumber ) -> bool: + """Checks if the log record's severity number is less than the minimum severity level. + + Args: + record: The log record to be processed. + min_severity: The minimum severity level. + + Returns: + True if the log record's severity number is less than the minimum + severity level, False otherwise. Log records with an unspecified severity (i.e. `0`) + are not affected by this parameter and therefore bypass minimum severity filtering. + """ if record.severity_number is not None: if ( min_severity is not None @@ -962,10 +973,25 @@ def is_less_than_min_severity( return False -def should_drop_logs_for_trace_based( - record: LogRecord, trace_state_enabled: bool +def should_drop_logs_for_unsampled_traces( + record: LogRecord, trace_based_flag: bool ) -> bool: - if trace_state_enabled: + """Determines whether the logger should drop log records associated with unsampled traces. + + If `trace_based` is `true`, log records associated with unsampled traces are dropped by the `Logger`. + A log record is considered associated with an unsampled trace if it has a valid `SpanId` and its + `TraceFlags` indicate that the trace is unsampled. A log record that isn't associated with a trace + context is not affected by this parameter and therefore bypasses trace-based filtering. + + Args: + record: The log record to be processed. + trace_based_flag: A boolean flag indicating whether trace-based filtering is enabled. If not explicitly set, + the `trace_based` parameter is set to `false` + + Returns: + True if the log record should be dropped due to being associated with an unsampled trace. + """ + if trace_based_flag: if record.context is not None: span = get_current_span(record.context) span_context = span.get_span_context() From c766a111eb2d1482016be32b8dfb4ccb8444e10b Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 7 Oct 2025 14:32:41 -0700 Subject: [PATCH 19/34] Retrigger CI/CD pipeline From 311b4678bbd9d8128b1310741a23f2c997988b81 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 7 Oct 2025 14:40:07 -0700 Subject: [PATCH 20/34] Retrigger CI/CD pipeline From 709f59446bba033580200ec538b92d771ec6c82a Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Mon, 20 Oct 2025 11:06:51 -0700 Subject: [PATCH 21/34] Add LoggerConfigurator --- .../sdk/_logs/_internal/__init__.py | 158 +++++++++++- opentelemetry-sdk/tests/logs/test_logs.py | 232 ++++++++++++++++-- 2 files changed, 366 insertions(+), 24 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 1fcedd5d59..e2bc8aff4a 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -22,6 +22,7 @@ import threading import traceback import warnings +import fnmatch from os import environ from threading import Lock from time import time_ns @@ -61,6 +62,8 @@ _logger = logging.getLogger(__name__) +LoggerConfigurator = Callable[[InstrumentationScope], "LoggerConfig | None"] + _DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT = 128 _ENV_VALUE_UNSET = "" @@ -96,6 +99,77 @@ class LogDeprecatedInitWarning(UserWarning): warnings.simplefilter("once", LogDeprecatedInitWarning) +class LoggerConfig: + def __init__( + self, + disabled: bool = False, + minimum_severity: SeverityNumber = SeverityNumber.UNSPECIFIED, + trace_based: bool = False, + ): + """Initialize LoggerConfig with specified parameters. + + Args: + disabled: A boolean indication of whether the logger is enabled. + If not explicitly set, defaults to False (i.e. Loggers are enabled by default). + If True, the logger behaves equivalently to a No-op Logger. + minimum_severity: A SeverityNumber indicating the minimum severity level + for log records to be processed. If not explicitly set, defaults to UNSPECIFIED (0). + If a log record's SeverityNumber is specified and is less than the configured + minimum_severity, the log record is dropped by the Logger. + trace_based: A boolean indication of whether the logger should only + process log records associated with sampled traces. If not explicitly set, + defaults to False. If True, log records associated with unsampled traces + are dropped by the Logger. + """ + self.disabled = disabled + self.minimum_severity = minimum_severity + self.trace_based = trace_based + + def __repr__(self): + return ( + f"LoggerConfig(disabled={self.disabled}, " + f"minimum_severity={self.minimum_severity}, " + f"trace_based={self.trace_based})" + ) + + +def create_logger_configurator_by_name( + logger_configs: dict[str, LoggerConfig] +) -> LoggerConfigurator: + """Create a LoggerConfigurator that selects configuration based on logger name. + + Args: + logger_configs: A dictionary mapping logger names to LoggerConfig instances. + Loggers not found in this mapping will use the default config. + + Returns: + A LoggerConfigurator function that can be used with LoggerProvider. + """ + def configurator(scope: InstrumentationScope) -> LoggerConfig | None: + return logger_configs.get(scope.name) + return configurator + + +def create_logger_configurator_with_pattern( + patterns: list[tuple[str, LoggerConfig]] +) -> LoggerConfigurator: + """Create a LoggerConfigurator that matches logger names using patterns. + + Args: + patterns: A list of (pattern, config) tuples. Patterns are matched in order, + and the first match is used. Use '*' as a wildcard. + + Returns: + A LoggerConfigurator function that can be used with LoggerProvider. + """ + def configurator(scope: InstrumentationScope) -> LoggerConfig | None: + for pattern, config in patterns: + if fnmatch.fnmatch(scope.name, pattern): + return config + return None + return configurator + + class LogLimits: """This class is based on a SpanLimits class in the Tracing module. @@ -685,9 +759,15 @@ def __init__( ConcurrentMultiLogRecordProcessor, ], instrumentation_scope: InstrumentationScope, + config: LoggerConfig | None = None, min_severity_level: SeverityNumber = SeverityNumber.UNSPECIFIED, trace_based: bool = False, ): + if config is not None: + self._config = config + else: + self._config = LoggerConfig() + super().__init__( instrumentation_scope.name, instrumentation_scope.version, @@ -704,6 +784,23 @@ def __init__( def resource(self): return self._resource + @property + def config(self): + return self._config + + @property + def instrumentation_scope(self): + """Get the instrumentation scope for this logger.""" + return self._instrumentation_scope + + def update_config(self, config: LoggerConfig) -> None: + """Update the logger's configuration. + + Args: + config: The new LoggerConfig to use. + """ + self._config = config + @overload def emit( self, @@ -766,7 +863,16 @@ def emit( if should_drop_logs_for_unsampled_traces(record, self._trace_based): return - log_data = LogData(record, self._instrumentation_scope) + if self._config.disabled: + return + + if is_less_than_min_severity(record, self._config.minimum_severity): + return + + if should_drop_logs_for_unsampled_traces(record, self._config.trace_based): + return + + log_data = LogData(record, self._instrumentation_scope) self._multi_log_record_processor.on_emit(log_data) @@ -781,6 +887,7 @@ def __init__( | None = None, min_severity_level: SeverityNumber = SeverityNumber.UNSPECIFIED, trace_based: bool = False, + logger_configurator: LoggerConfigurator | None = None, ): if resource is None: self._resource = Resource.create({}) @@ -799,6 +906,17 @@ def __init__( self._min_severity_level = min_severity_level self._trace_based = trace_based + if logger_configurator is not None: + self._logger_configurator = logger_configurator + else: + def default_configurator(scope: InstrumentationScope) -> LoggerConfig: + return LoggerConfig( + disabled=self._disabled, + minimum_severity=self._min_severity_level, + trace_based=self._trace_based, + ) + self._logger_configurator = default_configurator + @property def resource(self): return self._resource @@ -810,17 +928,24 @@ def _get_logger_no_cache( schema_url: str | None = None, attributes: _ExtendedAttributes | None = None, ) -> Logger: + instrumentation_scope = InstrumentationScope( + name, + version, + schema_url, + attributes, + ) + config = self._logger_configurator(instrumentation_scope) + if config is None: + config = LoggerConfig( + disabled=self._disabled, + minimum_severity=self._min_severity_level, + trace_based=self._trace_based, + ) return Logger( self._resource, self._multi_log_record_processor, - InstrumentationScope( - name, - version, - schema_url, - attributes, - ), - self._min_severity_level, - self._trace_based, + instrumentation_scope, + config=config, ) def _get_logger_cached( @@ -868,6 +993,21 @@ def add_log_record_processor( log_record_processor ) + def set_logger_configurator(self, configurator: LoggerConfigurator) -> None: + """Update the logger configurator and apply the new configuration to all existing loggers. + """ + with self._logger_cache_lock: + self._logger_configurator = configurator + for logger in self._logger_cache.values(): + new_config = configurator(logger.instrumentation_scope) + if new_config is None: + new_config = LoggerConfig( + disabled=self._disabled, + minimum_severity=self._min_severity_level, + trace_based=self._trace_based, + ) + logger.update_config(new_config) + def shutdown(self): """Shuts down the log processors.""" self._multi_log_record_processor.shutdown() diff --git a/opentelemetry-sdk/tests/logs/test_logs.py b/opentelemetry-sdk/tests/logs/test_logs.py index 8bf691f6ed..64327c8c1e 100644 --- a/opentelemetry-sdk/tests/logs/test_logs.py +++ b/opentelemetry-sdk/tests/logs/test_logs.py @@ -19,6 +19,7 @@ from opentelemetry._logs import LogRecord as APILogRecord from opentelemetry._logs import SeverityNumber +from opentelemetry._logs import NoOpLogger from opentelemetry.context import get_current from opentelemetry.sdk._logs import ( Logger, @@ -26,7 +27,7 @@ LogRecord, ) from opentelemetry.sdk._logs._internal import ( - NoOpLogger, + LoggerConfig, SynchronousMultiLogRecordProcessor, ) from opentelemetry.sdk.environment_variables import OTEL_SDK_DISABLED @@ -74,10 +75,6 @@ def test_get_logger(self): self.assertEqual( logger._instrumentation_scope.attributes, {"key": "value"} ) - self.assertEqual( - logger._min_severity_level, SeverityNumber.UNSPECIFIED - ) - self.assertFalse(logger._trace_based) @patch.dict("os.environ", {OTEL_SDK_DISABLED: "true"}) def test_get_logger_with_sdk_disabled(self): @@ -107,7 +104,7 @@ def test_logger_provider_init(self, resource_patch): class TestLogger(unittest.TestCase): @staticmethod - def _get_logger(): + def _get_logger(config=None): log_record_processor_mock = Mock() logger = Logger( resource=Resource.create({}), @@ -118,6 +115,7 @@ def _get_logger(): "schema_url", {"an": "attribute"}, ), + config=config, ) return logger, log_record_processor_mock @@ -184,8 +182,8 @@ def test_can_emit_with_keywords_arguments(self): def test_emit_logrecord_with_min_severity_filtering(self): """Test that logs below minimum severity are filtered out""" - logger, log_record_processor_mock = self._get_logger() - logger._min_severity_level = SeverityNumber.DEBUG4 + config = LoggerConfig(minimum_severity=SeverityNumber.DEBUG4) + logger, log_record_processor_mock = self._get_logger(config) log_record_info = LogRecord( observed_timestamp=0, @@ -229,8 +227,8 @@ def test_emit_logrecord_with_min_severity_unspecified(self): def test_emit_logrecord_with_trace_based_filtering(self): """Test that logs are filtered based on trace sampling state""" - logger, log_record_processor_mock = self._get_logger() - logger._trace_based = True + config = LoggerConfig(trace_based=True) + logger, log_record_processor_mock = self._get_logger(config) mock_span_context = Mock() mock_span_context.is_valid = True @@ -295,8 +293,8 @@ def test_emit_logrecord_trace_filtering_disabled(self): def test_emit_logrecord_trace_filtering_edge_cases(self): """Test edge cases for trace-based filtering""" - logger, log_record_processor_mock = self._get_logger() - logger._trace_based = True + config = LoggerConfig(trace_based=True) + logger, log_record_processor_mock = self._get_logger(config) mock_span_context = Mock() mock_span_context.is_valid = False @@ -348,9 +346,11 @@ def test_emit_logrecord_trace_filtering_edge_cases(self): def test_emit_both_min_severity_and_trace_based_filtering(self): """Test that both min severity and trace-based filtering work together""" - logger, log_record_processor_mock = self._get_logger() - logger._min_severity_level = SeverityNumber.WARN - logger._trace_based = True + config = LoggerConfig( + minimum_severity=SeverityNumber.WARN, + trace_based=True + ) + logger, log_record_processor_mock = self._get_logger(config) mock_span_context = Mock() mock_span_context.is_valid = True @@ -388,3 +388,205 @@ def test_emit_both_min_severity_and_trace_based_filtering(self): logger.emit(log_record_error) log_record_processor_mock.on_emit.assert_called_once() + + def test_emit_logrecord_with_disabled_logger(self): + """Test that disabled loggers don't emit any logs""" + config = LoggerConfig(disabled=True) + logger, log_record_processor_mock = self._get_logger(config) + + log_record = LogRecord( + observed_timestamp=0, + body="this should be dropped", + severity_number=SeverityNumber.ERROR, + severity_text="ERROR", + ) + + logger.emit(log_record) + log_record_processor_mock.on_emit.assert_not_called() + + def test_logger_config_property(self): + """Test that logger config property works correctly""" + config = LoggerConfig( + disabled=True, + minimum_severity=SeverityNumber.WARN, + trace_based=True + ) + logger, _ = self._get_logger(config) + + self.assertEqual(logger.config.disabled, True) + self.assertEqual(logger.config.minimum_severity, SeverityNumber.WARN) + self.assertEqual(logger.config.trace_based, True) + + def test_logger_configurator_behavior(self): + """Test LoggerConfigurator functionality including custom configurators and dynamic updates""" + + logger_configs = { + "test.database": LoggerConfig(minimum_severity=SeverityNumber.ERROR), + "test.auth": LoggerConfig(disabled=True), + "test.performance": LoggerConfig(trace_based=True) + } + + from opentelemetry.sdk._logs._internal import create_logger_configurator_by_name # pylint:disable=import-outside-toplevel + configurator = create_logger_configurator_by_name(logger_configs) + + provider = LoggerProvider(logger_configurator=configurator) + + + db_logger = provider.get_logger("test.database") + self.assertEqual(db_logger.config.minimum_severity, SeverityNumber.ERROR) + self.assertFalse(db_logger.config.disabled) + self.assertFalse(db_logger.config.trace_based) + + auth_logger = provider.get_logger("test.auth") + self.assertTrue(auth_logger.config.disabled) + + perf_logger = provider.get_logger("test.performance") + self.assertTrue(perf_logger.config.trace_based) + + other_logger = provider.get_logger("test.other") + self.assertEqual(other_logger.config.minimum_severity, SeverityNumber.UNSPECIFIED) + self.assertFalse(other_logger.config.disabled) + self.assertFalse(other_logger.config.trace_based) + + def test_logger_configurator_pattern_matching(self): + """Test LoggerConfigurator with pattern matching""" + from opentelemetry.sdk._logs._internal import create_logger_configurator_with_pattern + + patterns = [ + ("test.database.*", LoggerConfig(minimum_severity=SeverityNumber.ERROR)), + ("test.*.debug", LoggerConfig(disabled=True)), + ("test.*", LoggerConfig(trace_based=True)), + ("*", LoggerConfig(minimum_severity=SeverityNumber.WARN)) + ] + + configurator = create_logger_configurator_with_pattern(patterns) + provider = LoggerProvider(logger_configurator=configurator) + + db_logger = provider.get_logger("test.database.connection") + self.assertEqual(db_logger.config.minimum_severity, SeverityNumber.ERROR) + + debug_logger = provider.get_logger("test.module.debug") + self.assertTrue(debug_logger.config.disabled) + + general_logger = provider.get_logger("test.module") + self.assertTrue(general_logger.config.trace_based) + + other_logger = provider.get_logger("other.module") + self.assertEqual(other_logger.config.minimum_severity, SeverityNumber.WARN) + + def test_logger_configurator_dynamic_updates(self): + """Test that LoggerConfigurator updates apply to existing loggers""" + initial_configs = { + "test.module": LoggerConfig(minimum_severity=SeverityNumber.INFO) + } + + from opentelemetry.sdk._logs._internal import create_logger_configurator_by_name # pylint:disable=import-outside-toplevel + initial_configurator = create_logger_configurator_by_name(initial_configs) + + provider = LoggerProvider(logger_configurator=initial_configurator) + + logger = provider.get_logger("test.module") + self.assertEqual(logger.config.minimum_severity, SeverityNumber.INFO) + self.assertFalse(logger.config.disabled) + + updated_configs = { + "test.module": LoggerConfig(minimum_severity=SeverityNumber.ERROR, disabled=True) + } + updated_configurator = create_logger_configurator_by_name(updated_configs) + + provider.set_logger_configurator(updated_configurator) + + self.assertEqual(logger.config.minimum_severity, SeverityNumber.ERROR) + self.assertTrue(logger.config.disabled) + + new_logger = provider.get_logger("test.module") + self.assertEqual(new_logger.config.minimum_severity, SeverityNumber.ERROR) + self.assertTrue(new_logger.config.disabled) + + def test_logger_configurator_returns_none(self): + """Test LoggerConfigurator that returns None falls back to default""" + def none_configurator(scope): + return None + + provider = LoggerProvider( + logger_configurator=none_configurator, + min_severity_level=SeverityNumber.WARN, + trace_based=True + ) + + logger = provider.get_logger("test.module") + + self.assertEqual(logger.config.minimum_severity, SeverityNumber.WARN) + self.assertTrue(logger.config.trace_based) + self.assertFalse(logger.config.disabled) + + def test_logger_configurator_with_filtering(self): + """Test that LoggerConfigurator configs are properly applied during filtering""" + def selective_configurator(scope): + if scope.name == "disabled.logger": + return LoggerConfig(disabled=True) + if scope.name == "error.logger": + return LoggerConfig(minimum_severity=SeverityNumber.ERROR) + if scope.name == "trace.logger": + return LoggerConfig(trace_based=True) + return LoggerConfig() + + provider = LoggerProvider(logger_configurator=selective_configurator) + + disabled_logger = provider.get_logger("disabled.logger") + log_record_processor_mock = Mock() + disabled_logger._multi_log_record_processor = log_record_processor_mock + + log_record = LogRecord( + observed_timestamp=0, + body="should not emit", + severity_number=SeverityNumber.INFO, + ) + disabled_logger.emit(log_record) + log_record_processor_mock.on_emit.assert_not_called() + + error_logger = provider.get_logger("error.logger") + log_record_processor_mock = Mock() + error_logger._multi_log_record_processor = log_record_processor_mock + + info_record = LogRecord( + observed_timestamp=0, + body="info message", + severity_number=SeverityNumber.INFO, + ) + error_logger.emit(info_record) + log_record_processor_mock.on_emit.assert_not_called() + + error_record = LogRecord( + observed_timestamp=0, + body="error message", + severity_number=SeverityNumber.ERROR, + ) + error_logger.emit(error_record) + log_record_processor_mock.on_emit.assert_called_once() + + trace_logger = provider.get_logger("trace.logger") + log_record_processor_mock = Mock() + trace_logger._multi_log_record_processor = log_record_processor_mock + + mock_span_context = Mock() + mock_span_context.is_valid = True + mock_span_context.trace_flags.sampled = False + + mock_span = Mock() + mock_span.get_span_context.return_value = mock_span_context + + mock_context = Mock() + + with patch( + "opentelemetry.sdk._logs._internal.get_current_span", + return_value=mock_span, + ): + trace_record = LogRecord( + observed_timestamp=0, + body="unsampled trace message", + severity_number=SeverityNumber.INFO, + context=mock_context, + ) + trace_logger.emit(trace_record) + log_record_processor_mock.on_emit.assert_not_called() From 3e8204e15f451e293191783163e45775cb86458b Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Mon, 20 Oct 2025 11:18:12 -0700 Subject: [PATCH 22/34] Fix lint --- .../src/opentelemetry/sdk/_logs/_internal/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index e2bc8aff4a..8679d40e13 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -858,10 +858,6 @@ def emit( record = LogRecord._from_api_log_record( record=record, resource=self._resource ) - if is_less_than_min_severity(record, self._min_severity_level): - return - if should_drop_logs_for_unsampled_traces(record, self._trace_based): - return if self._config.disabled: return From 2de7586b04d4172e8a66b7152a97dcb3ba5d1148 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Mon, 20 Oct 2025 11:42:27 -0700 Subject: [PATCH 23/34] Retrigger CI/CD pipeline From 499b9ac2aca10b407872e93dbfd2ef6976a0aa84 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Mon, 20 Oct 2025 12:00:04 -0700 Subject: [PATCH 24/34] Fix ruff --- .../sdk/_logs/_internal/__init__.py | 45 +++++++----- opentelemetry-sdk/tests/logs/test_logs.py | 72 +++++++++++++------ 2 files changed, 78 insertions(+), 39 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 8679d40e13..269ab94884 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -17,12 +17,12 @@ import atexit import base64 import concurrent.futures +import fnmatch import json import logging import threading import traceback import warnings -import fnmatch from os import environ from threading import Lock from time import time_ns @@ -107,18 +107,18 @@ def __init__( trace_based: bool = False, ): """Initialize LoggerConfig with specified parameters. - + Args: disabled: A boolean indication of whether the logger is enabled. If not explicitly set, defaults to False (i.e. Loggers are enabled by default). If True, the logger behaves equivalently to a No-op Logger. minimum_severity: A SeverityNumber indicating the minimum severity level for log records to be processed. If not explicitly set, defaults to UNSPECIFIED (0). - If a log record's SeverityNumber is specified and is less than the configured + If a log record's SeverityNumber is specified and is less than the configured minimum_severity, the log record is dropped by the Logger. - trace_based: A boolean indication of whether the logger should only - process log records associated with sampled traces. If not explicitly set, - defaults to False. If True, log records associated with unsampled traces + trace_based: A boolean indication of whether the logger should only + process log records associated with sampled traces. If not explicitly set, + defaults to False. If True, log records associated with unsampled traces are dropped by the Logger. """ self.disabled = disabled @@ -134,39 +134,43 @@ def __repr__(self): def create_logger_configurator_by_name( - logger_configs: dict[str, LoggerConfig] + logger_configs: dict[str, LoggerConfig], ) -> LoggerConfigurator: """Create a LoggerConfigurator that selects configuration based on logger name. Args: logger_configs: A dictionary mapping logger names to LoggerConfig instances. Loggers not found in this mapping will use the default config. - + Returns: A LoggerConfigurator function that can be used with LoggerProvider. """ + def configurator(scope: InstrumentationScope) -> LoggerConfig | None: return logger_configs.get(scope.name) + return configurator def create_logger_configurator_with_pattern( - patterns: list[tuple[str, LoggerConfig]] + patterns: list[tuple[str, LoggerConfig]], ) -> LoggerConfigurator: """Create a LoggerConfigurator that matches logger names using patterns. - + Args: patterns: A list of (pattern, config) tuples. Patterns are matched in order, and the first match is used. Use '*' as a wildcard. - + Returns: A LoggerConfigurator function that can be used with LoggerProvider. """ + def configurator(scope: InstrumentationScope) -> LoggerConfig | None: for pattern, config in patterns: if fnmatch.fnmatch(scope.name, pattern): return config return None + return configurator @@ -795,7 +799,7 @@ def instrumentation_scope(self): def update_config(self, config: LoggerConfig) -> None: """Update the logger's configuration. - + Args: config: The new LoggerConfig to use. """ @@ -865,7 +869,9 @@ def emit( if is_less_than_min_severity(record, self._config.minimum_severity): return - if should_drop_logs_for_unsampled_traces(record, self._config.trace_based): + if should_drop_logs_for_unsampled_traces( + record, self._config.trace_based + ): return log_data = LogData(record, self._instrumentation_scope) @@ -905,12 +911,16 @@ def __init__( if logger_configurator is not None: self._logger_configurator = logger_configurator else: - def default_configurator(scope: InstrumentationScope) -> LoggerConfig: + + def default_configurator( + scope: InstrumentationScope, + ) -> LoggerConfig: return LoggerConfig( disabled=self._disabled, minimum_severity=self._min_severity_level, trace_based=self._trace_based, ) + self._logger_configurator = default_configurator @property @@ -989,9 +999,10 @@ def add_log_record_processor( log_record_processor ) - def set_logger_configurator(self, configurator: LoggerConfigurator) -> None: - """Update the logger configurator and apply the new configuration to all existing loggers. - """ + def set_logger_configurator( + self, configurator: LoggerConfigurator + ) -> None: + """Update the logger configurator and apply the new configuration to all existing loggers.""" with self._logger_cache_lock: self._logger_configurator = configurator for logger in self._logger_cache.values(): diff --git a/opentelemetry-sdk/tests/logs/test_logs.py b/opentelemetry-sdk/tests/logs/test_logs.py index 64327c8c1e..9c18b36ff1 100644 --- a/opentelemetry-sdk/tests/logs/test_logs.py +++ b/opentelemetry-sdk/tests/logs/test_logs.py @@ -18,8 +18,7 @@ from unittest.mock import Mock, patch from opentelemetry._logs import LogRecord as APILogRecord -from opentelemetry._logs import SeverityNumber -from opentelemetry._logs import NoOpLogger +from opentelemetry._logs import NoOpLogger, SeverityNumber from opentelemetry.context import get_current from opentelemetry.sdk._logs import ( Logger, @@ -347,8 +346,7 @@ def test_emit_logrecord_trace_filtering_edge_cases(self): def test_emit_both_min_severity_and_trace_based_filtering(self): """Test that both min severity and trace-based filtering work together""" config = LoggerConfig( - minimum_severity=SeverityNumber.WARN, - trace_based=True + minimum_severity=SeverityNumber.WARN, trace_based=True ) logger, log_record_processor_mock = self._get_logger(config) @@ -409,7 +407,7 @@ def test_logger_config_property(self): config = LoggerConfig( disabled=True, minimum_severity=SeverityNumber.WARN, - trace_based=True + trace_based=True, ) logger, _ = self._get_logger(config) @@ -421,19 +419,25 @@ def test_logger_configurator_behavior(self): """Test LoggerConfigurator functionality including custom configurators and dynamic updates""" logger_configs = { - "test.database": LoggerConfig(minimum_severity=SeverityNumber.ERROR), + "test.database": LoggerConfig( + minimum_severity=SeverityNumber.ERROR + ), "test.auth": LoggerConfig(disabled=True), - "test.performance": LoggerConfig(trace_based=True) + "test.performance": LoggerConfig(trace_based=True), } - from opentelemetry.sdk._logs._internal import create_logger_configurator_by_name # pylint:disable=import-outside-toplevel + from opentelemetry.sdk._logs._internal import ( + create_logger_configurator_by_name, # pylint:disable=import-outside-toplevel + ) + configurator = create_logger_configurator_by_name(logger_configs) provider = LoggerProvider(logger_configurator=configurator) - db_logger = provider.get_logger("test.database") - self.assertEqual(db_logger.config.minimum_severity, SeverityNumber.ERROR) + self.assertEqual( + db_logger.config.minimum_severity, SeverityNumber.ERROR + ) self.assertFalse(db_logger.config.disabled) self.assertFalse(db_logger.config.trace_based) @@ -444,26 +448,35 @@ def test_logger_configurator_behavior(self): self.assertTrue(perf_logger.config.trace_based) other_logger = provider.get_logger("test.other") - self.assertEqual(other_logger.config.minimum_severity, SeverityNumber.UNSPECIFIED) + self.assertEqual( + other_logger.config.minimum_severity, SeverityNumber.UNSPECIFIED + ) self.assertFalse(other_logger.config.disabled) self.assertFalse(other_logger.config.trace_based) def test_logger_configurator_pattern_matching(self): """Test LoggerConfigurator with pattern matching""" - from opentelemetry.sdk._logs._internal import create_logger_configurator_with_pattern + from opentelemetry.sdk._logs._internal import ( + create_logger_configurator_with_pattern, + ) patterns = [ - ("test.database.*", LoggerConfig(minimum_severity=SeverityNumber.ERROR)), + ( + "test.database.*", + LoggerConfig(minimum_severity=SeverityNumber.ERROR), + ), ("test.*.debug", LoggerConfig(disabled=True)), ("test.*", LoggerConfig(trace_based=True)), - ("*", LoggerConfig(minimum_severity=SeverityNumber.WARN)) + ("*", LoggerConfig(minimum_severity=SeverityNumber.WARN)), ] configurator = create_logger_configurator_with_pattern(patterns) provider = LoggerProvider(logger_configurator=configurator) db_logger = provider.get_logger("test.database.connection") - self.assertEqual(db_logger.config.minimum_severity, SeverityNumber.ERROR) + self.assertEqual( + db_logger.config.minimum_severity, SeverityNumber.ERROR + ) debug_logger = provider.get_logger("test.module.debug") self.assertTrue(debug_logger.config.disabled) @@ -472,7 +485,9 @@ def test_logger_configurator_pattern_matching(self): self.assertTrue(general_logger.config.trace_based) other_logger = provider.get_logger("other.module") - self.assertEqual(other_logger.config.minimum_severity, SeverityNumber.WARN) + self.assertEqual( + other_logger.config.minimum_severity, SeverityNumber.WARN + ) def test_logger_configurator_dynamic_updates(self): """Test that LoggerConfigurator updates apply to existing loggers""" @@ -480,8 +495,13 @@ def test_logger_configurator_dynamic_updates(self): "test.module": LoggerConfig(minimum_severity=SeverityNumber.INFO) } - from opentelemetry.sdk._logs._internal import create_logger_configurator_by_name # pylint:disable=import-outside-toplevel - initial_configurator = create_logger_configurator_by_name(initial_configs) + from opentelemetry.sdk._logs._internal import ( + create_logger_configurator_by_name, # pylint:disable=import-outside-toplevel + ) + + initial_configurator = create_logger_configurator_by_name( + initial_configs + ) provider = LoggerProvider(logger_configurator=initial_configurator) @@ -490,9 +510,13 @@ def test_logger_configurator_dynamic_updates(self): self.assertFalse(logger.config.disabled) updated_configs = { - "test.module": LoggerConfig(minimum_severity=SeverityNumber.ERROR, disabled=True) + "test.module": LoggerConfig( + minimum_severity=SeverityNumber.ERROR, disabled=True + ) } - updated_configurator = create_logger_configurator_by_name(updated_configs) + updated_configurator = create_logger_configurator_by_name( + updated_configs + ) provider.set_logger_configurator(updated_configurator) @@ -500,18 +524,21 @@ def test_logger_configurator_dynamic_updates(self): self.assertTrue(logger.config.disabled) new_logger = provider.get_logger("test.module") - self.assertEqual(new_logger.config.minimum_severity, SeverityNumber.ERROR) + self.assertEqual( + new_logger.config.minimum_severity, SeverityNumber.ERROR + ) self.assertTrue(new_logger.config.disabled) def test_logger_configurator_returns_none(self): """Test LoggerConfigurator that returns None falls back to default""" + def none_configurator(scope): return None provider = LoggerProvider( logger_configurator=none_configurator, min_severity_level=SeverityNumber.WARN, - trace_based=True + trace_based=True, ) logger = provider.get_logger("test.module") @@ -522,6 +549,7 @@ def none_configurator(scope): def test_logger_configurator_with_filtering(self): """Test that LoggerConfigurator configs are properly applied during filtering""" + def selective_configurator(scope): if scope.name == "disabled.logger": return LoggerConfig(disabled=True) From 1f913ebe9d1b6765027ba9236739df4598fd9e9f Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Mon, 20 Oct 2025 12:20:20 -0700 Subject: [PATCH 25/34] fix tests --- .../src/opentelemetry/_logs/__init__.py | 2 + .../sdk/_logs/_internal/__init__.py | 3 ++ opentelemetry-sdk/tests/logs/test_logs.py | 37 ++++++++----------- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/_logs/__init__.py b/opentelemetry-api/src/opentelemetry/_logs/__init__.py index 6215da2eb5..d445b8a212 100644 --- a/opentelemetry-api/src/opentelemetry/_logs/__init__.py +++ b/opentelemetry-api/src/opentelemetry/_logs/__init__.py @@ -39,6 +39,7 @@ LogRecord, NoOpLogger, NoOpLoggerProvider, + LoggerConfig, get_logger, get_logger_provider, set_logger_provider, @@ -51,6 +52,7 @@ "LogRecord", "NoOpLogger", "NoOpLoggerProvider", + "LoggerConfig", "get_logger", "get_logger_provider", "set_logger_provider", diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 269ab94884..9f9964a61a 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -11,6 +11,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +# pylint: disable=too-many-lines + from __future__ import annotations import abc diff --git a/opentelemetry-sdk/tests/logs/test_logs.py b/opentelemetry-sdk/tests/logs/test_logs.py index 9c18b36ff1..f9a5dcd3b3 100644 --- a/opentelemetry-sdk/tests/logs/test_logs.py +++ b/opentelemetry-sdk/tests/logs/test_logs.py @@ -32,6 +32,10 @@ from opentelemetry.sdk.environment_variables import OTEL_SDK_DISABLED from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.util.instrumentation import InstrumentationScope +from opentelemetry.sdk._logs._internal import ( + create_logger_configurator_by_name, + create_logger_configurator_with_pattern, +) class TestLoggerProvider(unittest.TestCase): @@ -426,10 +430,6 @@ def test_logger_configurator_behavior(self): "test.performance": LoggerConfig(trace_based=True), } - from opentelemetry.sdk._logs._internal import ( - create_logger_configurator_by_name, # pylint:disable=import-outside-toplevel - ) - configurator = create_logger_configurator_by_name(logger_configs) provider = LoggerProvider(logger_configurator=configurator) @@ -456,10 +456,6 @@ def test_logger_configurator_behavior(self): def test_logger_configurator_pattern_matching(self): """Test LoggerConfigurator with pattern matching""" - from opentelemetry.sdk._logs._internal import ( - create_logger_configurator_with_pattern, - ) - patterns = [ ( "test.database.*", @@ -495,10 +491,6 @@ def test_logger_configurator_dynamic_updates(self): "test.module": LoggerConfig(minimum_severity=SeverityNumber.INFO) } - from opentelemetry.sdk._logs._internal import ( - create_logger_configurator_by_name, # pylint:disable=import-outside-toplevel - ) - initial_configurator = create_logger_configurator_by_name( initial_configs ) @@ -547,19 +539,20 @@ def none_configurator(scope): self.assertTrue(logger.config.trace_based) self.assertFalse(logger.config.disabled) + @staticmethod + def _selective_configurator(scope): + if scope.name == "disabled.logger": + return LoggerConfig(disabled=True) + if scope.name == "error.logger": + return LoggerConfig(minimum_severity=SeverityNumber.ERROR) + if scope.name == "trace.logger": + return LoggerConfig(trace_based=True) + return LoggerConfig() + def test_logger_configurator_with_filtering(self): """Test that LoggerConfigurator configs are properly applied during filtering""" - def selective_configurator(scope): - if scope.name == "disabled.logger": - return LoggerConfig(disabled=True) - if scope.name == "error.logger": - return LoggerConfig(minimum_severity=SeverityNumber.ERROR) - if scope.name == "trace.logger": - return LoggerConfig(trace_based=True) - return LoggerConfig() - - provider = LoggerProvider(logger_configurator=selective_configurator) + provider = LoggerProvider(logger_configurator=self._selective_configurator) disabled_logger = provider.get_logger("disabled.logger") log_record_processor_mock = Mock() From 372d63daae351c825bf39326212ef138740a9f1a Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Mon, 20 Oct 2025 12:32:09 -0700 Subject: [PATCH 26/34] Fix declarations --- opentelemetry-api/src/opentelemetry/_logs/__init__.py | 2 -- opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/_logs/__init__.py b/opentelemetry-api/src/opentelemetry/_logs/__init__.py index d445b8a212..6215da2eb5 100644 --- a/opentelemetry-api/src/opentelemetry/_logs/__init__.py +++ b/opentelemetry-api/src/opentelemetry/_logs/__init__.py @@ -39,7 +39,6 @@ LogRecord, NoOpLogger, NoOpLoggerProvider, - LoggerConfig, get_logger, get_logger_provider, set_logger_provider, @@ -52,7 +51,6 @@ "LogRecord", "NoOpLogger", "NoOpLoggerProvider", - "LoggerConfig", "get_logger", "get_logger_provider", "set_logger_provider", diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py index dbb108b7db..3b8651e4e1 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py @@ -23,6 +23,7 @@ LogLimits, LogRecord, LogRecordProcessor, + LoggerConfig, ) __all__ = [ @@ -35,4 +36,5 @@ "LogRecordProcessor", "LogDeprecatedInitWarning", "LogDroppedAttributesWarning", + "LoggerConfig", ] From d1733683950308f8b118250da5f14d0176d3f83c Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Mon, 20 Oct 2025 12:51:04 -0700 Subject: [PATCH 27/34] Fix ruff --- .../src/opentelemetry/sdk/_logs/__init__.py | 2 +- opentelemetry-sdk/tests/logs/test_logs.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py index 3b8651e4e1..4aa5957e87 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py @@ -18,12 +18,12 @@ LogDeprecatedInitWarning, LogDroppedAttributesWarning, Logger, + LoggerConfig, LoggerProvider, LoggingHandler, LogLimits, LogRecord, LogRecordProcessor, - LoggerConfig, ) __all__ = [ diff --git a/opentelemetry-sdk/tests/logs/test_logs.py b/opentelemetry-sdk/tests/logs/test_logs.py index f9a5dcd3b3..3c74b4975b 100644 --- a/opentelemetry-sdk/tests/logs/test_logs.py +++ b/opentelemetry-sdk/tests/logs/test_logs.py @@ -28,14 +28,12 @@ from opentelemetry.sdk._logs._internal import ( LoggerConfig, SynchronousMultiLogRecordProcessor, + create_logger_configurator_by_name, + create_logger_configurator_with_pattern, ) from opentelemetry.sdk.environment_variables import OTEL_SDK_DISABLED from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.util.instrumentation import InstrumentationScope -from opentelemetry.sdk._logs._internal import ( - create_logger_configurator_by_name, - create_logger_configurator_with_pattern, -) class TestLoggerProvider(unittest.TestCase): @@ -552,7 +550,9 @@ def _selective_configurator(scope): def test_logger_configurator_with_filtering(self): """Test that LoggerConfigurator configs are properly applied during filtering""" - provider = LoggerProvider(logger_configurator=self._selective_configurator) + provider = LoggerProvider( + logger_configurator=self._selective_configurator + ) disabled_logger = provider.get_logger("disabled.logger") log_record_processor_mock = Mock() From 6bef9310bde79d2ed1b0f457441aa4560f66d7de Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 5 Nov 2025 12:00:43 -0800 Subject: [PATCH 28/34] Fix filter labels --- CHANGELOG.md | 2 +- .../sdk/_logs/_internal/__init__.py | 52 +++++++------- opentelemetry-sdk/tests/logs/test_logs.py | 70 +++++++++---------- 3 files changed, 62 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64129fb1f4..ee09d6cf3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#4737](https://github.com/open-telemetry/opentelemetry-python/pull/4737)) - logs: add warnings for classes that would be deprecated and renamed in 1.39.0 ([#4771](https://github.com/open-telemetry/opentelemetry-python/pull/4771)) -- Add `minimum_severity` and `trace_based` logger parameters to filter logs +- Add `minimum_severity level` and `trace_based sampling` logger parameters to filter logs ([#4765](https://github.com/open-telemetry/opentelemetry-python/pull/4765)) ## Version 1.37.0/0.58b0 (2025-09-11) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 9f9964a61a..eac6b7076a 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -106,8 +106,8 @@ class LoggerConfig: def __init__( self, disabled: bool = False, - minimum_severity: SeverityNumber = SeverityNumber.UNSPECIFIED, - trace_based: bool = False, + minimum_severity_level: SeverityNumber = SeverityNumber.UNSPECIFIED, + trace_based_sampling: bool = False, ): """Initialize LoggerConfig with specified parameters. @@ -115,24 +115,24 @@ def __init__( disabled: A boolean indication of whether the logger is enabled. If not explicitly set, defaults to False (i.e. Loggers are enabled by default). If True, the logger behaves equivalently to a No-op Logger. - minimum_severity: A SeverityNumber indicating the minimum severity level + minimum_severity_level: A SeverityNumber indicating the minimum severity level for log records to be processed. If not explicitly set, defaults to UNSPECIFIED (0). If a log record's SeverityNumber is specified and is less than the configured - minimum_severity, the log record is dropped by the Logger. - trace_based: A boolean indication of whether the logger should only + minimum_severity_level, the log record is dropped by the Logger. + trace_based_sampling: A boolean indication of whether the logger should only process log records associated with sampled traces. If not explicitly set, defaults to False. If True, log records associated with unsampled traces are dropped by the Logger. """ self.disabled = disabled - self.minimum_severity = minimum_severity - self.trace_based = trace_based + self.minimum_severity_level = minimum_severity_level + self.trace_based_sampling = trace_based_sampling def __repr__(self): return ( f"LoggerConfig(disabled={self.disabled}, " - f"minimum_severity={self.minimum_severity}, " - f"trace_based={self.trace_based})" + f"minimum_severity_level={self.minimum_severity_level}, " + f"trace_based_sampling={self.trace_based_sampling})" ) @@ -768,7 +768,7 @@ def __init__( instrumentation_scope: InstrumentationScope, config: LoggerConfig | None = None, min_severity_level: SeverityNumber = SeverityNumber.UNSPECIFIED, - trace_based: bool = False, + trace_based_sampling: bool = False, ): if config is not None: self._config = config @@ -785,7 +785,7 @@ def __init__( self._multi_log_record_processor = multi_log_record_processor self._instrumentation_scope = instrumentation_scope self._min_severity_level = min_severity_level - self._trace_based = trace_based + self._trace_based_sampling = trace_based_sampling @property def resource(self): @@ -869,11 +869,11 @@ def emit( if self._config.disabled: return - if is_less_than_min_severity(record, self._config.minimum_severity): + if is_less_than_min_severity(record, self._config.minimum_severity_level): return if should_drop_logs_for_unsampled_traces( - record, self._config.trace_based + record, self._config.trace_based_sampling ): return @@ -891,7 +891,7 @@ def __init__( | ConcurrentMultiLogRecordProcessor | None = None, min_severity_level: SeverityNumber = SeverityNumber.UNSPECIFIED, - trace_based: bool = False, + trace_based_sampling: bool = False, logger_configurator: LoggerConfigurator | None = None, ): if resource is None: @@ -909,7 +909,7 @@ def __init__( self._logger_cache = {} self._logger_cache_lock = Lock() self._min_severity_level = min_severity_level - self._trace_based = trace_based + self._trace_based_sampling = trace_based_sampling if logger_configurator is not None: self._logger_configurator = logger_configurator @@ -920,8 +920,8 @@ def default_configurator( ) -> LoggerConfig: return LoggerConfig( disabled=self._disabled, - minimum_severity=self._min_severity_level, - trace_based=self._trace_based, + minimum_severity_level=self._min_severity_level, + trace_based_sampling=self._trace_based_sampling, ) self._logger_configurator = default_configurator @@ -947,8 +947,8 @@ def _get_logger_no_cache( if config is None: config = LoggerConfig( disabled=self._disabled, - minimum_severity=self._min_severity_level, - trace_based=self._trace_based, + minimum_severity_level=self._min_severity_level, + trace_based_sampling=self._trace_based_sampling, ) return Logger( self._resource, @@ -1013,8 +1013,8 @@ def set_logger_configurator( if new_config is None: new_config = LoggerConfig( disabled=self._disabled, - minimum_severity=self._min_severity_level, - trace_based=self._trace_based, + minimum_severity_level=self._min_severity_level, + trace_based_sampling=self._trace_based_sampling, ) logger.update_config(new_config) @@ -1124,24 +1124,24 @@ def is_less_than_min_severity( def should_drop_logs_for_unsampled_traces( - record: LogRecord, trace_based_flag: bool + record: LogRecord, trace_based_sampling_flag: bool ) -> bool: """Determines whether the logger should drop log records associated with unsampled traces. - If `trace_based` is `true`, log records associated with unsampled traces are dropped by the `Logger`. + If `trace_based_sampling` is `true`, log records associated with unsampled traces are dropped by the `Logger`. A log record is considered associated with an unsampled trace if it has a valid `SpanId` and its `TraceFlags` indicate that the trace is unsampled. A log record that isn't associated with a trace context is not affected by this parameter and therefore bypasses trace-based filtering. Args: record: The log record to be processed. - trace_based_flag: A boolean flag indicating whether trace-based filtering is enabled. If not explicitly set, - the `trace_based` parameter is set to `false` + trace_based_sampling_flag: A boolean flag indicating whether trace-based filtering is enabled. If not explicitly set, + the `trace_based_sampling` parameter is set to `false` Returns: True if the log record should be dropped due to being associated with an unsampled trace. """ - if trace_based_flag: + if trace_based_sampling_flag: if record.context is not None: span = get_current_span(record.context) span_context = span.get_span_context() diff --git a/opentelemetry-sdk/tests/logs/test_logs.py b/opentelemetry-sdk/tests/logs/test_logs.py index 3c74b4975b..d458f6fc2d 100644 --- a/opentelemetry-sdk/tests/logs/test_logs.py +++ b/opentelemetry-sdk/tests/logs/test_logs.py @@ -86,7 +86,7 @@ def test_get_logger_with_sdk_disabled(self): @patch.object(Resource, "create") def test_logger_provider_init(self, resource_patch): logger_provider = LoggerProvider( - min_severity_level=SeverityNumber.DEBUG4, trace_based=True + min_severity_level=SeverityNumber.DEBUG4, trace_based_sampling=True ) resource_patch.assert_called_once() self.assertIsNotNone(logger_provider._resource) @@ -99,7 +99,7 @@ def test_logger_provider_init(self, resource_patch): self.assertEqual( logger_provider._min_severity_level, SeverityNumber.DEBUG4 ) - self.assertTrue(logger_provider._trace_based) + self.assertTrue(logger_provider._trace_based_sampling) self.assertIsNotNone(logger_provider._at_exit_handler) @@ -183,7 +183,7 @@ def test_can_emit_with_keywords_arguments(self): def test_emit_logrecord_with_min_severity_filtering(self): """Test that logs below minimum severity are filtered out""" - config = LoggerConfig(minimum_severity=SeverityNumber.DEBUG4) + config = LoggerConfig(minimum_severity_level=SeverityNumber.DEBUG4) logger, log_record_processor_mock = self._get_logger(config) log_record_info = LogRecord( @@ -226,9 +226,9 @@ def test_emit_logrecord_with_min_severity_unspecified(self): logger.emit(log_record) log_record_processor_mock.on_emit.assert_called_once() - def test_emit_logrecord_with_trace_based_filtering(self): + def test_emit_logrecord_with_trace_based_sampling_filtering(self): """Test that logs are filtered based on trace sampling state""" - config = LoggerConfig(trace_based=True) + config = LoggerConfig(trace_based_sampling=True) logger, log_record_processor_mock = self._get_logger(config) mock_span_context = Mock() @@ -294,7 +294,7 @@ def test_emit_logrecord_trace_filtering_disabled(self): def test_emit_logrecord_trace_filtering_edge_cases(self): """Test edge cases for trace-based filtering""" - config = LoggerConfig(trace_based=True) + config = LoggerConfig(trace_based_sampling=True) logger, log_record_processor_mock = self._get_logger(config) mock_span_context = Mock() @@ -345,10 +345,10 @@ def test_emit_logrecord_trace_filtering_edge_cases(self): logger.emit(log_record) log_record_processor_mock.on_emit.assert_not_called() - def test_emit_both_min_severity_and_trace_based_filtering(self): + def test_emit_both_min_severity_and_trace_based_sampling_filtering(self): """Test that both min severity and trace-based filtering work together""" config = LoggerConfig( - minimum_severity=SeverityNumber.WARN, trace_based=True + minimum_severity_level=SeverityNumber.WARN, trace_based_sampling=True ) logger, log_record_processor_mock = self._get_logger(config) @@ -408,24 +408,24 @@ def test_logger_config_property(self): """Test that logger config property works correctly""" config = LoggerConfig( disabled=True, - minimum_severity=SeverityNumber.WARN, - trace_based=True, + minimum_severity_level=SeverityNumber.WARN, + trace_based_sampling=True, ) logger, _ = self._get_logger(config) self.assertEqual(logger.config.disabled, True) - self.assertEqual(logger.config.minimum_severity, SeverityNumber.WARN) - self.assertEqual(logger.config.trace_based, True) + self.assertEqual(logger.config.minimum_severity_level, SeverityNumber.WARN) + self.assertEqual(logger.config.trace_based_sampling, True) def test_logger_configurator_behavior(self): """Test LoggerConfigurator functionality including custom configurators and dynamic updates""" logger_configs = { "test.database": LoggerConfig( - minimum_severity=SeverityNumber.ERROR + minimum_severity_level=SeverityNumber.ERROR ), "test.auth": LoggerConfig(disabled=True), - "test.performance": LoggerConfig(trace_based=True), + "test.performance": LoggerConfig(trace_based_sampling=True), } configurator = create_logger_configurator_by_name(logger_configs) @@ -434,34 +434,34 @@ def test_logger_configurator_behavior(self): db_logger = provider.get_logger("test.database") self.assertEqual( - db_logger.config.minimum_severity, SeverityNumber.ERROR + db_logger.config.minimum_severity_level, SeverityNumber.ERROR ) self.assertFalse(db_logger.config.disabled) - self.assertFalse(db_logger.config.trace_based) + self.assertFalse(db_logger.config.trace_based_sampling) auth_logger = provider.get_logger("test.auth") self.assertTrue(auth_logger.config.disabled) perf_logger = provider.get_logger("test.performance") - self.assertTrue(perf_logger.config.trace_based) + self.assertTrue(perf_logger.config.trace_based_sampling) other_logger = provider.get_logger("test.other") self.assertEqual( - other_logger.config.minimum_severity, SeverityNumber.UNSPECIFIED + other_logger.config.minimum_severity_level, SeverityNumber.UNSPECIFIED ) self.assertFalse(other_logger.config.disabled) - self.assertFalse(other_logger.config.trace_based) + self.assertFalse(other_logger.config.trace_based_sampling) def test_logger_configurator_pattern_matching(self): """Test LoggerConfigurator with pattern matching""" patterns = [ ( "test.database.*", - LoggerConfig(minimum_severity=SeverityNumber.ERROR), + LoggerConfig(minimum_severity_level=SeverityNumber.ERROR), ), ("test.*.debug", LoggerConfig(disabled=True)), - ("test.*", LoggerConfig(trace_based=True)), - ("*", LoggerConfig(minimum_severity=SeverityNumber.WARN)), + ("test.*", LoggerConfig(trace_based_sampling=True)), + ("*", LoggerConfig(minimum_severity_level=SeverityNumber.WARN)), ] configurator = create_logger_configurator_with_pattern(patterns) @@ -469,24 +469,24 @@ def test_logger_configurator_pattern_matching(self): db_logger = provider.get_logger("test.database.connection") self.assertEqual( - db_logger.config.minimum_severity, SeverityNumber.ERROR + db_logger.config.minimum_severity_level, SeverityNumber.ERROR ) debug_logger = provider.get_logger("test.module.debug") self.assertTrue(debug_logger.config.disabled) general_logger = provider.get_logger("test.module") - self.assertTrue(general_logger.config.trace_based) + self.assertTrue(general_logger.config.trace_based_sampling) other_logger = provider.get_logger("other.module") self.assertEqual( - other_logger.config.minimum_severity, SeverityNumber.WARN + other_logger.config.minimum_severity_level, SeverityNumber.WARN ) def test_logger_configurator_dynamic_updates(self): """Test that LoggerConfigurator updates apply to existing loggers""" initial_configs = { - "test.module": LoggerConfig(minimum_severity=SeverityNumber.INFO) + "test.module": LoggerConfig(minimum_severity_level=SeverityNumber.INFO) } initial_configurator = create_logger_configurator_by_name( @@ -496,12 +496,12 @@ def test_logger_configurator_dynamic_updates(self): provider = LoggerProvider(logger_configurator=initial_configurator) logger = provider.get_logger("test.module") - self.assertEqual(logger.config.minimum_severity, SeverityNumber.INFO) + self.assertEqual(logger.config.minimum_severity_level, SeverityNumber.INFO) self.assertFalse(logger.config.disabled) updated_configs = { "test.module": LoggerConfig( - minimum_severity=SeverityNumber.ERROR, disabled=True + minimum_severity_level=SeverityNumber.ERROR, disabled=True ) } updated_configurator = create_logger_configurator_by_name( @@ -510,12 +510,12 @@ def test_logger_configurator_dynamic_updates(self): provider.set_logger_configurator(updated_configurator) - self.assertEqual(logger.config.minimum_severity, SeverityNumber.ERROR) + self.assertEqual(logger.config.minimum_severity_level, SeverityNumber.ERROR) self.assertTrue(logger.config.disabled) new_logger = provider.get_logger("test.module") self.assertEqual( - new_logger.config.minimum_severity, SeverityNumber.ERROR + new_logger.config.minimum_severity_level, SeverityNumber.ERROR ) self.assertTrue(new_logger.config.disabled) @@ -528,13 +528,13 @@ def none_configurator(scope): provider = LoggerProvider( logger_configurator=none_configurator, min_severity_level=SeverityNumber.WARN, - trace_based=True, + trace_based_sampling=True, ) logger = provider.get_logger("test.module") - self.assertEqual(logger.config.minimum_severity, SeverityNumber.WARN) - self.assertTrue(logger.config.trace_based) + self.assertEqual(logger.config.minimum_severity_level, SeverityNumber.WARN) + self.assertTrue(logger.config.trace_based_sampling) self.assertFalse(logger.config.disabled) @staticmethod @@ -542,9 +542,9 @@ def _selective_configurator(scope): if scope.name == "disabled.logger": return LoggerConfig(disabled=True) if scope.name == "error.logger": - return LoggerConfig(minimum_severity=SeverityNumber.ERROR) + return LoggerConfig(minimum_severity_level=SeverityNumber.ERROR) if scope.name == "trace.logger": - return LoggerConfig(trace_based=True) + return LoggerConfig(trace_based_sampling=True) return LoggerConfig() def test_logger_configurator_with_filtering(self): From 80f8a788ecafd51b247337222aba1484d92ca554 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 5 Nov 2025 12:03:27 -0800 Subject: [PATCH 29/34] Fix changelog description --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee09d6cf3c..8c8f0c05bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#4737](https://github.com/open-telemetry/opentelemetry-python/pull/4737)) - logs: add warnings for classes that would be deprecated and renamed in 1.39.0 ([#4771](https://github.com/open-telemetry/opentelemetry-python/pull/4771)) -- Add `minimum_severity level` and `trace_based sampling` logger parameters to filter logs +- Add `minimum_severity_level` and `trace_based_sampling` logger parameters to filter logs ([#4765](https://github.com/open-telemetry/opentelemetry-python/pull/4765)) ## Version 1.37.0/0.58b0 (2025-09-11) From 03ad66a508790dbe514fcb5ad5e9f581a68220c6 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 5 Nov 2025 12:06:26 -0800 Subject: [PATCH 30/34] Fix ruff --- .../sdk/_logs/_internal/__init__.py | 4 ++- opentelemetry-sdk/tests/logs/test_logs.py | 26 ++++++++++++++----- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index eac6b7076a..00c95d24cd 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -869,7 +869,9 @@ def emit( if self._config.disabled: return - if is_less_than_min_severity(record, self._config.minimum_severity_level): + if is_less_than_min_severity( + record, self._config.minimum_severity_level + ): return if should_drop_logs_for_unsampled_traces( diff --git a/opentelemetry-sdk/tests/logs/test_logs.py b/opentelemetry-sdk/tests/logs/test_logs.py index d458f6fc2d..361842a3e3 100644 --- a/opentelemetry-sdk/tests/logs/test_logs.py +++ b/opentelemetry-sdk/tests/logs/test_logs.py @@ -348,7 +348,8 @@ def test_emit_logrecord_trace_filtering_edge_cases(self): def test_emit_both_min_severity_and_trace_based_sampling_filtering(self): """Test that both min severity and trace-based filtering work together""" config = LoggerConfig( - minimum_severity_level=SeverityNumber.WARN, trace_based_sampling=True + minimum_severity_level=SeverityNumber.WARN, + trace_based_sampling=True, ) logger, log_record_processor_mock = self._get_logger(config) @@ -414,7 +415,9 @@ def test_logger_config_property(self): logger, _ = self._get_logger(config) self.assertEqual(logger.config.disabled, True) - self.assertEqual(logger.config.minimum_severity_level, SeverityNumber.WARN) + self.assertEqual( + logger.config.minimum_severity_level, SeverityNumber.WARN + ) self.assertEqual(logger.config.trace_based_sampling, True) def test_logger_configurator_behavior(self): @@ -447,7 +450,8 @@ def test_logger_configurator_behavior(self): other_logger = provider.get_logger("test.other") self.assertEqual( - other_logger.config.minimum_severity_level, SeverityNumber.UNSPECIFIED + other_logger.config.minimum_severity_level, + SeverityNumber.UNSPECIFIED, ) self.assertFalse(other_logger.config.disabled) self.assertFalse(other_logger.config.trace_based_sampling) @@ -486,7 +490,9 @@ def test_logger_configurator_pattern_matching(self): def test_logger_configurator_dynamic_updates(self): """Test that LoggerConfigurator updates apply to existing loggers""" initial_configs = { - "test.module": LoggerConfig(minimum_severity_level=SeverityNumber.INFO) + "test.module": LoggerConfig( + minimum_severity_level=SeverityNumber.INFO + ) } initial_configurator = create_logger_configurator_by_name( @@ -496,7 +502,9 @@ def test_logger_configurator_dynamic_updates(self): provider = LoggerProvider(logger_configurator=initial_configurator) logger = provider.get_logger("test.module") - self.assertEqual(logger.config.minimum_severity_level, SeverityNumber.INFO) + self.assertEqual( + logger.config.minimum_severity_level, SeverityNumber.INFO + ) self.assertFalse(logger.config.disabled) updated_configs = { @@ -510,7 +518,9 @@ def test_logger_configurator_dynamic_updates(self): provider.set_logger_configurator(updated_configurator) - self.assertEqual(logger.config.minimum_severity_level, SeverityNumber.ERROR) + self.assertEqual( + logger.config.minimum_severity_level, SeverityNumber.ERROR + ) self.assertTrue(logger.config.disabled) new_logger = provider.get_logger("test.module") @@ -533,7 +543,9 @@ def none_configurator(scope): logger = provider.get_logger("test.module") - self.assertEqual(logger.config.minimum_severity_level, SeverityNumber.WARN) + self.assertEqual( + logger.config.minimum_severity_level, SeverityNumber.WARN + ) self.assertTrue(logger.config.trace_based_sampling) self.assertFalse(logger.config.disabled) From e54ba6f911148b790dba0c1c17fddc6286443fa0 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 5 Nov 2025 12:12:35 -0800 Subject: [PATCH 31/34] Retrigger CI/CD pipeline From 45304030bcfdde0cf6c16b39f1eafb98509a3461 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 5 Nov 2025 12:20:18 -0800 Subject: [PATCH 32/34] Retrigger CI/CD pipeline From 9965bd32441918496e4745205bdb41e2cecabb7d Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 5 Nov 2025 12:28:42 -0800 Subject: [PATCH 33/34] Retrigger CI/CD pipeline From 8cedda6c344102d9ab710249db4b82dd82cee83b Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 5 Nov 2025 12:43:51 -0800 Subject: [PATCH 34/34] Retrigger CI/CD pipeline