|  | 
| 15 | 15 | # pylint: skip-file | 
| 16 | 16 | from __future__ import annotations | 
| 17 | 17 | 
 | 
|  | 18 | +import logging | 
| 18 | 19 | from logging import WARNING, getLogger | 
| 19 | 20 | from os import environ | 
| 20 | 21 | from typing import Iterable, Optional, Sequence | 
|  | 
| 44 | 45 |     _OTelSDKConfigurator, | 
| 45 | 46 | ) | 
| 46 | 47 | from opentelemetry.sdk._logs import LoggingHandler | 
|  | 48 | +from opentelemetry.sdk._logs._internal.export import LogExporter | 
| 47 | 49 | from opentelemetry.sdk._logs.export import ConsoleLogExporter | 
| 48 | 50 | from opentelemetry.sdk.environment_variables import ( | 
| 49 | 51 |     OTEL_TRACES_SAMPLER, | 
| @@ -203,7 +205,7 @@ class OTLPSpanExporter: | 
| 203 | 205 |     pass | 
| 204 | 206 | 
 | 
| 205 | 207 | 
 | 
| 206 |  | -class DummyOTLPLogExporter: | 
|  | 208 | +class DummyOTLPLogExporter(LogExporter): | 
| 207 | 209 |     def __init__(self, *args, **kwargs): | 
| 208 | 210 |         self.export_called = False | 
| 209 | 211 | 
 | 
| @@ -841,6 +843,60 @@ def test_initialize_components_kwargs( | 
| 841 | 843 |             True, | 
| 842 | 844 |         ) | 
| 843 | 845 | 
 | 
|  | 846 | +    def test_basicConfig_works_with_otel_handler(self): | 
|  | 847 | +        with ClearLoggingHandlers(): | 
|  | 848 | +            _init_logging( | 
|  | 849 | +                {"otlp": DummyOTLPLogExporter}, | 
|  | 850 | +                Resource.create({}), | 
|  | 851 | +                setup_logging_handler=True, | 
|  | 852 | +            ) | 
|  | 853 | + | 
|  | 854 | +            logging.basicConfig(level=logging.INFO) | 
|  | 855 | + | 
|  | 856 | +            root_logger = logging.getLogger() | 
|  | 857 | +            stream_handlers = [ | 
|  | 858 | +                h | 
|  | 859 | +                for h in root_logger.handlers | 
|  | 860 | +                if isinstance(h, logging.StreamHandler) | 
|  | 861 | +            ] | 
|  | 862 | +            self.assertEqual( | 
|  | 863 | +                len(stream_handlers), | 
|  | 864 | +                1, | 
|  | 865 | +                "basicConfig should add a StreamHandler even when OTel handler exists", | 
|  | 866 | +            ) | 
|  | 867 | + | 
|  | 868 | +    def test_basicConfig_preserves_otel_handler(self): | 
|  | 869 | +        with ClearLoggingHandlers(): | 
|  | 870 | +            _init_logging( | 
|  | 871 | +                {"otlp": DummyOTLPLogExporter}, | 
|  | 872 | +                Resource.create({}), | 
|  | 873 | +                setup_logging_handler=True, | 
|  | 874 | +            ) | 
|  | 875 | + | 
|  | 876 | +            root_logger = logging.getLogger() | 
|  | 877 | +            self.assertEqual( | 
|  | 878 | +                len(root_logger.handlers), | 
|  | 879 | +                1, | 
|  | 880 | +                "Should be exactly one OpenTelemetry LoggingHandler", | 
|  | 881 | +            ) | 
|  | 882 | +            handler = root_logger.handlers[0] | 
|  | 883 | +            self.assertIsInstance(handler, LoggingHandler) | 
|  | 884 | + | 
|  | 885 | +            logging.basicConfig() | 
|  | 886 | + | 
|  | 887 | +            self.assertGreater(len(root_logger.handlers), 1) | 
|  | 888 | + | 
|  | 889 | +            logging_handlers = [ | 
|  | 890 | +                h | 
|  | 891 | +                for h in root_logger.handlers | 
|  | 892 | +                if isinstance(h, LoggingHandler) | 
|  | 893 | +            ] | 
|  | 894 | +            self.assertEqual( | 
|  | 895 | +                len(logging_handlers), | 
|  | 896 | +                1, | 
|  | 897 | +                "Should still have exactly one OpenTelemetry LoggingHandler", | 
|  | 898 | +            ) | 
|  | 899 | + | 
| 844 | 900 | 
 | 
| 845 | 901 | class TestMetricsInit(TestCase): | 
| 846 | 902 |     def setUp(self): | 
| @@ -1076,3 +1132,40 @@ def test_custom_configurator(self, mock_init_comp): | 
| 1076 | 1132 |             "sampler": "TEST_SAMPLER", | 
| 1077 | 1133 |         } | 
| 1078 | 1134 |         mock_init_comp.assert_called_once_with(**kwargs) | 
|  | 1135 | + | 
|  | 1136 | + | 
|  | 1137 | +class ClearLoggingHandlers: | 
|  | 1138 | +    def __init__(self): | 
|  | 1139 | +        self.root_logger = getLogger() | 
|  | 1140 | +        self.original_handlers = None | 
|  | 1141 | + | 
|  | 1142 | +    def __enter__(self): | 
|  | 1143 | +        self.original_handlers = self.root_logger.handlers[:] | 
|  | 1144 | +        self.root_logger.handlers = [] | 
|  | 1145 | +        return self | 
|  | 1146 | + | 
|  | 1147 | +    def __exit__(self, exc_type, exc_val, exc_tb): | 
|  | 1148 | +        self.root_logger.handlers = [] | 
|  | 1149 | +        for handler in self.original_handlers: | 
|  | 1150 | +            self.root_logger.addHandler(handler) | 
|  | 1151 | + | 
|  | 1152 | + | 
|  | 1153 | +class TestClearLoggingHandlers(TestCase): | 
|  | 1154 | +    def test_preserves_handlers(self): | 
|  | 1155 | +        root_logger = getLogger() | 
|  | 1156 | +        initial_handlers = root_logger.handlers[:] | 
|  | 1157 | + | 
|  | 1158 | +        test_handler = logging.StreamHandler() | 
|  | 1159 | +        root_logger.addHandler(test_handler) | 
|  | 1160 | +        expected_handlers = initial_handlers + [test_handler] | 
|  | 1161 | + | 
|  | 1162 | +        with ClearLoggingHandlers(): | 
|  | 1163 | +            self.assertEqual(len(root_logger.handlers), 0) | 
|  | 1164 | +            temp_handler = logging.StreamHandler() | 
|  | 1165 | +            root_logger.addHandler(temp_handler) | 
|  | 1166 | + | 
|  | 1167 | +        self.assertEqual(len(root_logger.handlers), len(expected_handlers)) | 
|  | 1168 | +        for h1, h2 in zip(root_logger.handlers, expected_handlers): | 
|  | 1169 | +            self.assertIs(h1, h2) | 
|  | 1170 | + | 
|  | 1171 | +        root_logger.removeHandler(test_handler) | 
0 commit comments