From 0f04597f311ccf35c66a30a93c61bee0ae42b8f5 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 17 Feb 2026 12:56:31 +0100 Subject: [PATCH 1/9] opentelemetry-sdk: depcreate logging handler Move config handling to opentelemetry-instrumentation and the handler to opentelemetry-instrumentation-logging --- .../sdk/_configuration/__init__.py | 33 ++----------------- .../sdk/_logs/_internal/__init__.py | 4 +++ 2 files changed, 6 insertions(+), 31 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py index d1af469bc9..28da9c8fbc 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py @@ -39,7 +39,6 @@ from opentelemetry.metrics import set_meter_provider from opentelemetry.sdk._logs import ( LoggerProvider, - LoggingHandler, LogRecordProcessor, ) from opentelemetry.sdk._logs.export import ( @@ -326,37 +325,9 @@ def _init_logging( set_event_logger_provider(event_logger_provider) if setup_logging_handler: - # Add OTel handler - handler = LoggingHandler( - level=logging.NOTSET, logger_provider=provider + _logger.warning( + "Handling of logging integrations as been moved to opentelemetry-instrumentation" ) - logging.getLogger().addHandler(handler) - _overwrite_logging_config_fns(handler) - - -def _overwrite_logging_config_fns(handler: LoggingHandler) -> None: - root = logging.getLogger() - - def wrapper(config_fn: Callable) -> Callable: - def overwritten_config_fn(*args, **kwargs): - removed_handler = False - # We don't want the OTLP handler to be modified or deleted by the logging config functions. - # So we remove it and then add it back after the function call. - if handler in root.handlers: - removed_handler = True - root.handlers.remove(handler) - try: - config_fn(*args, **kwargs) - finally: - # Ensure handler is added back if logging function throws exception. - if removed_handler: - root.addHandler(handler) - - return overwritten_config_fn - - logging.config.fileConfig = wrapper(logging.config.fileConfig) - logging.config.dictConfig = wrapper(logging.config.dictConfig) - logging.basicConfig = wrapper(logging.basicConfig) def _import_tracer_configurator( diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index d775dd4455..b2e7a58b0a 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -496,6 +496,10 @@ def __init__( super().__init__(level=level) self._logger_provider = logger_provider or get_logger_provider() + warnings.deprecated( + "This is deprecated you should use the one in opentelemetry-instrumentation-logging" + ) + @staticmethod def _get_attributes(record: logging.LogRecord) -> _ExtendedAttributes: attributes = { From ef913579c32933f6b21354bc691f44cd7f802c32 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 17 Feb 2026 14:47:01 +0100 Subject: [PATCH 2/9] Drop handling of env var --- .../src/opentelemetry/sdk/_configuration/__init__.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py index 28da9c8fbc..86185b65e7 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py @@ -46,7 +46,6 @@ LogRecordExporter, ) from opentelemetry.sdk.environment_variables import ( - _OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, OTEL_EXPORTER_OTLP_LOGS_PROTOCOL, OTEL_EXPORTER_OTLP_METRICS_PROTOCOL, OTEL_EXPORTER_OTLP_PROTOCOL, @@ -520,15 +519,6 @@ def _initialize_components( _init_metrics( metric_exporters, resource, exporter_args_map=exporter_args_map ) - if setup_logging_handler is None: - setup_logging_handler = ( - os.getenv( - _OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, "false" - ) - .strip() - .lower() - == "true" - ) _init_logging( log_exporters, resource, From 32c6b120327bf6fc2e1922694d8b47902586da1a Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 17 Feb 2026 15:19:22 +0100 Subject: [PATCH 3/9] Fix typo --- .../src/opentelemetry/sdk/_configuration/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py index 86185b65e7..8d827364dc 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py @@ -325,7 +325,7 @@ def _init_logging( if setup_logging_handler: _logger.warning( - "Handling of logging integrations as been moved to opentelemetry-instrumentation" + "Handling of logging integrations has been moved to opentelemetry-instrumentation" ) From 747602bbe9dc5f1fa16624b22238ec6931592739 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 19 Feb 2026 17:15:59 +0100 Subject: [PATCH 4/9] Reinstate the old code --- .../sdk/_configuration/__init__.py | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py index 8d827364dc..5d5a993009 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py @@ -39,6 +39,7 @@ from opentelemetry.metrics import set_meter_provider from opentelemetry.sdk._logs import ( LoggerProvider, + LoggingHandler, LogRecordProcessor, ) from opentelemetry.sdk._logs.export import ( @@ -46,6 +47,7 @@ LogRecordExporter, ) from opentelemetry.sdk.environment_variables import ( + _OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, OTEL_EXPORTER_OTLP_LOGS_PROTOCOL, OTEL_EXPORTER_OTLP_METRICS_PROTOCOL, OTEL_EXPORTER_OTLP_PROTOCOL, @@ -324,9 +326,41 @@ def _init_logging( set_event_logger_provider(event_logger_provider) if setup_logging_handler: - _logger.warning( - "Handling of logging integrations has been moved to opentelemetry-instrumentation" + warnings.deprecated( + "Setting the OTel Logging handler from the SDK and the `OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED` are deprecated. You should install the opentelemetry-instrumentation-logging" + ) + + # Add OTel handler + handler = LoggingHandler( + level=logging.NOTSET, logger_provider=provider ) + logging.getLogger().addHandler(handler) + _overwrite_logging_config_fns(handler) + + +def _overwrite_logging_config_fns(handler: LoggingHandler) -> None: + root = logging.getLogger() + + def wrapper(config_fn: Callable) -> Callable: + def overwritten_config_fn(*args, **kwargs): + removed_handler = False + # We don't want the OTLP handler to be modified or deleted by the logging config functions. + # So we remove it and then add it back after the function call. + if handler in root.handlers: + removed_handler = True + root.handlers.remove(handler) + try: + config_fn(*args, **kwargs) + finally: + # Ensure handler is added back if logging function throws exception. + if removed_handler: + root.addHandler(handler) + + return overwritten_config_fn + + logging.config.fileConfig = wrapper(logging.config.fileConfig) + logging.config.dictConfig = wrapper(logging.config.dictConfig) + logging.basicConfig = wrapper(logging.basicConfig) def _import_tracer_configurator( @@ -519,6 +553,15 @@ def _initialize_components( _init_metrics( metric_exporters, resource, exporter_args_map=exporter_args_map ) + if setup_logging_handler is None: + setup_logging_handler = ( + os.getenv( + _OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, "false" + ) + .strip() + .lower() + == "true" + ) _init_logging( log_exporters, resource, From 340b226648cb2bbc8f09ad21c914b4995fe8d27e Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 23 Feb 2026 17:06:55 +0100 Subject: [PATCH 5/9] opentelemetry-sdk: stop leaking handler in loggers While at it also fix the noopprovider test case that was working because we were asserting against another handler. --- opentelemetry-sdk/tests/logs/test_handler.py | 89 +++++++++++++++----- 1 file changed, 68 insertions(+), 21 deletions(-) diff --git a/opentelemetry-sdk/tests/logs/test_handler.py b/opentelemetry-sdk/tests/logs/test_handler.py index b9f1edf49a..ea5dc59bbc 100644 --- a/opentelemetry-sdk/tests/logs/test_handler.py +++ b/opentelemetry-sdk/tests/logs/test_handler.py @@ -39,7 +39,7 @@ # pylint: disable=too-many-public-methods class TestLoggingHandler(unittest.TestCase): def test_handler_default_log_level(self): - processor, logger = set_up_test_logging(logging.NOTSET) + processor, logger, handler = set_up_test_logging(logging.NOTSET) # Make sure debug messages are ignored by default logger.debug("Debug message") @@ -50,8 +50,10 @@ def test_handler_default_log_level(self): logger.warning("Warning message") self.assertEqual(processor.emit_count(), 1) + logger.removeHandler(handler) + def test_handler_custom_log_level(self): - processor, logger = set_up_test_logging(logging.ERROR) + processor, logger, handler = set_up_test_logging(logging.ERROR) with self.assertLogs(level=logging.WARNING): logger.warning("Warning message test custom log level") @@ -64,6 +66,8 @@ def test_handler_custom_log_level(self): logger.critical("No Time For Caution") self.assertEqual(processor.emit_count(), 2) + logger.removeHandler(handler) + # pylint: disable=protected-access def test_log_record_emit_noop(self): noop_logger_provder = NoOpLoggerProvider() @@ -78,9 +82,10 @@ def test_log_record_emit_noop(self): with self.assertLogs(level=logging.WARNING): logger.warning("Warning message") + logger.removeHandler(handler_mock) + def test_log_flush_noop(self): no_op_logger_provider = NoOpLoggerProvider() - no_op_logger_provider.force_flush = Mock() logger = logging.getLogger("foo") handler = LoggingHandler( @@ -91,11 +96,19 @@ def test_log_flush_noop(self): with self.assertLogs(level=logging.WARNING): logger.warning("Warning message") - logger.handlers[0].flush() - no_op_logger_provider.force_flush.assert_not_called() + # the LoggingHandler flush method will call the force_flush method of LoggerProvider in + # a separate thread if present. NoOpLoggerProvider is not supposed to have that + with patch( + "opentelemetry.sdk._logs._internal.threading" + ) as threading_mock: + logger.handlers[0].flush() + + threading_mock.Thread.assert_not_called() + + logger.removeHandler(handler) def test_log_record_no_span_context(self): - processor, logger = set_up_test_logging(logging.WARNING) + processor, logger, handler = set_up_test_logging(logging.WARNING) # Assert emit gets called for warning message with self.assertLogs(level=logging.WARNING): @@ -115,8 +128,10 @@ def test_log_record_no_span_context(self): INVALID_SPAN_CONTEXT.trace_flags, ) + logger.removeHandler(handler) + def test_log_record_observed_timestamp(self): - processor, logger = set_up_test_logging(logging.WARNING) + processor, logger, handler = set_up_test_logging(logging.WARNING) with self.assertLogs(level=logging.WARNING): logger.warning("Warning message") @@ -124,9 +139,11 @@ def test_log_record_observed_timestamp(self): record = processor.get_log_record(0) self.assertIsNotNone(record.log_record.observed_timestamp) + logger.removeHandler(handler) + def test_log_record_user_attributes(self): """Attributes can be injected into logs by adding them to the ReadWriteLogRecord""" - processor, logger = set_up_test_logging(logging.WARNING) + processor, logger, handler = set_up_test_logging(logging.WARNING) # Assert emit gets called for warning message with self.assertLogs(level=logging.WARNING): @@ -155,9 +172,11 @@ def test_log_record_user_attributes(self): isinstance(record.log_record.attributes, BoundedAttributes) ) + logger.removeHandler(handler) + def test_log_record_exception(self): """Exception information will be included in attributes""" - processor, logger = set_up_test_logging(logging.ERROR) + processor, logger, handler = set_up_test_logging(logging.ERROR) try: raise ZeroDivisionError("division by zero") @@ -189,9 +208,11 @@ def test_log_record_exception(self): self.assertTrue("division by zero" in stack_trace) self.assertTrue(__file__ in stack_trace) + logger.removeHandler(handler) + def test_log_record_recursive_exception(self): """Exception information will be included in attributes even though it is recursive""" - processor, logger = set_up_test_logging(logging.ERROR) + processor, logger, handler = set_up_test_logging(logging.ERROR) try: raise ZeroDivisionError( @@ -224,9 +245,11 @@ def test_log_record_recursive_exception(self): self.assertTrue("division by zero" in stack_trace) self.assertTrue(__file__ in stack_trace) + logger.removeHandler(handler) + def test_log_exc_info_false(self): """Exception information will not be included in attributes""" - processor, logger = set_up_test_logging(logging.NOTSET) + processor, logger, handler = set_up_test_logging(logging.NOTSET) try: raise ZeroDivisionError("division by zero") @@ -251,8 +274,10 @@ def test_log_exc_info_false(self): record.log_record.attributes, ) + logger.removeHandler(handler) + def test_log_record_exception_with_object_payload(self): - processor, logger = set_up_test_logging(logging.ERROR) + processor, logger, handler = set_up_test_logging(logging.ERROR) class CustomException(Exception): def __str__(self): @@ -287,8 +312,10 @@ def __str__(self): self.assertTrue("CustomException" in stack_trace) self.assertTrue(__file__ in stack_trace) + logger.removeHandler(handler) + def test_log_record_trace_correlation(self): - processor, logger = set_up_test_logging(logging.WARNING) + processor, logger, handler = set_up_test_logging(logging.WARNING) tracer = trace.TracerProvider().get_tracer(__name__) with tracer.start_as_current_span("test") as span: @@ -325,8 +352,10 @@ def test_log_record_trace_correlation(self): span_context.trace_flags, ) + logger.removeHandler(handler) + def test_log_record_trace_correlation_deprecated(self): - processor, logger = set_up_test_logging(logging.WARNING) + processor, logger, handler = set_up_test_logging(logging.WARNING) tracer = trace.TracerProvider().get_tracer(__name__) with tracer.start_as_current_span("test") as span: @@ -349,22 +378,28 @@ def test_log_record_trace_correlation_deprecated(self): record.log_record.trace_flags, span_context.trace_flags ) + logger.removeHandler(handler) + def test_warning_without_formatter(self): - processor, logger = set_up_test_logging(logging.WARNING) + processor, logger, handler = set_up_test_logging(logging.WARNING) logger.warning("Test message") record = processor.get_log_record(0) self.assertEqual(record.log_record.body, "Test message") + logger.removeHandler(handler) + def test_exception_without_formatter(self): - processor, logger = set_up_test_logging(logging.WARNING) + processor, logger, handler = set_up_test_logging(logging.WARNING) logger.exception("Test exception") record = processor.get_log_record(0) self.assertEqual(record.log_record.body, "Test exception") + logger.removeHandler(handler) + def test_warning_with_formatter(self): - processor, logger = set_up_test_logging( + processor, logger, handler = set_up_test_logging( logging.WARNING, formatter=logging.Formatter( "%(name)s - %(levelname)s - %(message)s" @@ -377,8 +412,10 @@ def test_warning_with_formatter(self): record.log_record.body, "foo - WARNING - Test message" ) + logger.removeHandler(handler) + def test_log_body_is_always_string_with_formatter(self): - processor, logger = set_up_test_logging( + processor, logger, handler = set_up_test_logging( logging.WARNING, formatter=logging.Formatter( "%(name)s - %(levelname)s - %(message)s" @@ -389,17 +426,21 @@ def test_log_body_is_always_string_with_formatter(self): record = processor.get_log_record(0) self.assertIsInstance(record.log_record.body, str) + logger.removeHandler(handler) + @patch.dict(os.environ, {"OTEL_SDK_DISABLED": "true"}) def test_handler_root_logger_with_disabled_sdk_does_not_go_into_recursion_error( self, ): - processor, logger = set_up_test_logging( + processor, logger, handler = set_up_test_logging( logging.NOTSET, root_logger=True ) logger.warning("hello") self.assertEqual(processor.emit_count(), 0) + logger.removeHandler(handler) + @patch.dict(os.environ, {OTEL_ATTRIBUTE_COUNT_LIMIT: "3"}) def test_otel_attribute_count_limit_respected_in_logging_handler(self): """Test that OTEL_ATTRIBUTE_COUNT_LIMIT is properly respected by LoggingHandler.""" @@ -439,6 +480,8 @@ def test_otel_attribute_count_limit_respected_in_logging_handler(self): f"Should have 10 dropped attributes, got {record.dropped_attributes}", ) + logger.removeHandler(handler) + @patch.dict(os.environ, {OTEL_ATTRIBUTE_COUNT_LIMIT: "5"}) def test_otel_attribute_count_limit_includes_code_attributes(self): """Test that OTEL_ATTRIBUTE_COUNT_LIMIT applies to all attributes including code attributes.""" @@ -476,9 +519,11 @@ def test_otel_attribute_count_limit_includes_code_attributes(self): f"Should have 6 dropped attributes, got {record.dropped_attributes}", ) + logger.removeHandler(handler) + def test_logging_handler_without_env_var_uses_default_limit(self): """Test that without OTEL_ATTRIBUTE_COUNT_LIMIT, default limit (128) should apply.""" - processor, logger = set_up_test_logging(logging.WARNING) + processor, logger, handler = set_up_test_logging(logging.WARNING) # Create a log record with many attributes (more than default limit of 128) extra_attrs = {f"attr_{i}": f"value_{i}" for i in range(150)} @@ -505,6 +550,8 @@ def test_logging_handler_without_env_var_uses_default_limit(self): f"Should have 25 dropped attributes, got {record.dropped_attributes}", ) + logger.removeHandler(handler) + def set_up_test_logging(level, formatter=None, root_logger=False): logger_provider = LoggerProvider() @@ -515,7 +562,7 @@ def set_up_test_logging(level, formatter=None, root_logger=False): if formatter: handler.setFormatter(formatter) logger.addHandler(handler) - return processor, logger + return processor, logger, handler class FakeProcessor(LogRecordProcessor): From 18260e404bf1b241d8df6874c959c72cab261c7b Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 23 Feb 2026 17:16:12 +0100 Subject: [PATCH 6/9] Don't use warnings.deprecated - too new --- .../src/opentelemetry/sdk/_configuration/__init__.py | 7 +++++-- .../src/opentelemetry/sdk/_logs/_internal/__init__.py | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py index 5d5a993009..b16022cf93 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py @@ -326,8 +326,11 @@ def _init_logging( set_event_logger_provider(event_logger_provider) if setup_logging_handler: - warnings.deprecated( - "Setting the OTel Logging handler from the SDK and the `OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED` are deprecated. You should install the opentelemetry-instrumentation-logging" + warnings.warn( + "Setting the OTel Logging handler from the SDK and the " + "`OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED` are deprecated. " + "You should install the opentelemetry-instrumentation-logging", + DeprecationWarning, ) # Add OTel handler diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index b2e7a58b0a..eea01db850 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -496,8 +496,9 @@ def __init__( super().__init__(level=level) self._logger_provider = logger_provider or get_logger_provider() - warnings.deprecated( - "This is deprecated you should use the one in opentelemetry-instrumentation-logging" + warnings.warn( + "This is deprecated you should use the one in opentelemetry-instrumentation-logging", + DeprecationWarning, ) @staticmethod From 0382fc262220e7c910fe252700dd2ae25de7be84 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 24 Feb 2026 10:26:54 +0100 Subject: [PATCH 7/9] Update import in examples --- docs/examples/logs/example.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/examples/logs/example.py b/docs/examples/logs/example.py index 0549b3ec5e..51d12ceee1 100644 --- a/docs/examples/logs/example.py +++ b/docs/examples/logs/example.py @@ -5,7 +5,10 @@ from opentelemetry.exporter.otlp.proto.grpc._log_exporter import ( OTLPLogExporter, ) -from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler + +# this is available in the opentelemetry-instrumentation-logging package +from opentelemetry.instrumentation.logging.handler import LoggingHandler +from opentelemetry.sdk._logs import LoggerProvider from opentelemetry.sdk._logs.export import BatchLogRecordProcessor from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider From 7c2eab6d9877d0fb4aab2562d6b7e79c20f102dc Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 24 Feb 2026 10:50:34 +0100 Subject: [PATCH 8/9] Cleanup deprecation messages and test for them --- .../src/opentelemetry/sdk/_configuration/__init__.py | 6 +++--- .../opentelemetry/sdk/_logs/_internal/__init__.py | 3 ++- opentelemetry-sdk/tests/logs/test_handler.py | 7 +++++++ opentelemetry-sdk/tests/test_configurator.py | 12 ++++++++++++ 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py index b16022cf93..602b105ca7 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py @@ -327,9 +327,9 @@ def _init_logging( if setup_logging_handler: warnings.warn( - "Setting the OTel Logging handler from the SDK and the " - "`OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED` are deprecated. " - "You should install the opentelemetry-instrumentation-logging", + "The `OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED` environment variable " + "and the `LoggingHandler` in `opentelemetry-sdk` that it controls are deprecated." + "Install `opentelemetry-instrumentation-logging` package instead.", DeprecationWarning, ) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index eea01db850..b0c6d30d11 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -497,7 +497,8 @@ def __init__( self._logger_provider = logger_provider or get_logger_provider() warnings.warn( - "This is deprecated you should use the one in opentelemetry-instrumentation-logging", + "`LoggingHandler` in `opentelemetry-sdk` is deprecated. Use the " + "handler from `opentelemetry-instrumentation-logging` instead.", DeprecationWarning, ) diff --git a/opentelemetry-sdk/tests/logs/test_handler.py b/opentelemetry-sdk/tests/logs/test_handler.py index ea5dc59bbc..37de01508e 100644 --- a/opentelemetry-sdk/tests/logs/test_handler.py +++ b/opentelemetry-sdk/tests/logs/test_handler.py @@ -38,6 +38,13 @@ # pylint: disable=too-many-public-methods class TestLoggingHandler(unittest.TestCase): + def test_warns_when_used(self): + with self.assertWarnsRegex( + DeprecationWarning, + "`LoggingHandler` in `opentelemetry-sdk` is deprecated", + ): + LoggingHandler() + def test_handler_default_log_level(self): processor, logger, handler = set_up_test_logging(logging.NOTSET) diff --git a/opentelemetry-sdk/tests/test_configurator.py b/opentelemetry-sdk/tests/test_configurator.py index 5d971ed1d6..aeaab8d6b4 100644 --- a/opentelemetry-sdk/tests/test_configurator.py +++ b/opentelemetry-sdk/tests/test_configurator.py @@ -843,6 +843,18 @@ def test_logging_init_exporter_without_handler_setup(self): getLogger(__name__).error("hello") self.assertFalse(provider.processors[0].exporter.export_called) + def test_logging_init_with_setup_logging_handler_to_true_warns(self): + resource = Resource.create({}) + with self.assertWarnsRegex( + DeprecationWarning, + "and the `LoggingHandler` in `opentelemetry-sdk` that it controls are deprecated", + ): + _init_logging( + {"otlp": DummyOTLPLogExporter}, + resource=resource, + setup_logging_handler=True, + ) + @patch.dict( environ, {"OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service"}, From 98356fd50062b0cc6fb8f7c94ab4ea4b88982288 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 24 Feb 2026 11:00:25 +0100 Subject: [PATCH 9/9] Add changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3b925076a..25349ab5dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#4913](https://github.com/open-telemetry/opentelemetry-python/pull/4913)) - bump semantic-conventions to v1.39.0 ([#4914](https://github.com/open-telemetry/opentelemetry-python/pull/4914)) +- `opentelemetry-sdk`: deprecate `LoggingHandler` in favor of `opentelemetry-instrumentation-logging` + ([#4919](https://github.com/open-telemetry/opentelemetry-python/pull/4919)) ## Version 1.39.0/0.60b0 (2025-12-03)