From 8f69d37f818d37a72f2980c1efc3789c3d44bdc5 Mon Sep 17 00:00:00 2001 From: Jeremy Voss Date: Mon, 12 May 2025 14:56:09 -0700 Subject: [PATCH 01/10] Squash: Add Log Level and Format configuration for autoinstrumentation # Conflicts: # CHANGELOG.md # opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py # opentelemetry-sdk/tests/test_configurator.py --- CHANGELOG.md | 5 + .../sdk/_configuration/__init__.py | 21 ++ .../sdk/environment_variables/__init__.py | 16 ++ opentelemetry-sdk/tests/test_configurator.py | 217 +++++++++++++++++- 4 files changed, 258 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b69cbbf7e5..4f1ebeeab4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -195,6 +195,11 @@ can cause a deadlock to occur over `logging._lock` in some cases ([#4636](https: - typecheck: add sdk/resources and drop mypy ([#4578](https://github.com/open-telemetry/opentelemetry-python/pull/4578)) +- Refactor `BatchLogRecordProcessor` to simplify code and make the control flow more + clear ([#4562](https://github.com/open-telemetry/opentelemetry-python/pull/4562/) + and [#4535](https://github.com/open-telemetry/opentelemetry-python/pull/4535)). +- Enable configuration of logging format and level in auto-instrumentation + ([#4203](https://github.com/open-telemetry/opentelemetry-python/pull/4203)) - Use PEP702 for marking deprecations ([#4522](https://github.com/open-telemetry/opentelemetry-python/pull/4522)) - Refactor `BatchLogRecordProcessor` and `BatchSpanProcessor` to simplify code diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py index d1af469bc9..efd226227d 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py @@ -55,6 +55,8 @@ OTEL_PYTHON_TRACER_CONFIGURATOR, OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG, + OTEL_PYTHON_LOG_LEVEL, + OTEL_PYTHON_LOG_FORMAT, ) from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export import ( @@ -101,6 +103,15 @@ _OTEL_SAMPLER_ENTRY_POINT_GROUP = "opentelemetry_traces_sampler" +_OTEL_PYTHON_LOG_LEVEL_BY_NAME = { + "notset": logging.NOTSET, + "debug": logging.DEBUG, + "info": logging.INFO, + "warn": logging.WARNING, + "warning": logging.WARNING, + "error": logging.ERROR, +} + _logger = logging.getLogger(__name__) ExporterArgsMap = Mapping[ @@ -166,6 +177,9 @@ def _get_sampler() -> str | None: def _get_id_generator() -> str: return environ.get(OTEL_PYTHON_ID_GENERATOR, _DEFAULT_ID_GENERATOR) +def _get_log_level() -> int: + return _OTEL_PYTHON_LOG_LEVEL_BY_NAME.get(environ.get(OTEL_PYTHON_LOG_LEVEL, "notset").lower().strip(), logging.NOTSET) + def _get_tracer_configurator() -> str | None: return environ.get(OTEL_PYTHON_TRACER_CONFIGURATOR, None) @@ -330,6 +344,13 @@ def _init_logging( handler = LoggingHandler( level=logging.NOTSET, logger_provider=provider ) + # Log level + if OTEL_PYTHON_LOG_LEVEL in environ: + handler.setLevel(_get_log_level()) + # Log format + if OTEL_PYTHON_LOG_FORMAT in environ: + log_format = environ.get(OTEL_PYTHON_LOG_FORMAT, logging.BASIC_FORMAT) + handler.setFormatter(logging.Formatter(log_format)) logging.getLogger().addHandler(handler) _overwrite_logging_config_fns(handler) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py index 420b576c86..9eea06d6f4 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py @@ -54,6 +54,22 @@ Default: "info" """ +OTEL_PYTHON_LOG_FORMAT = "OTEL_PYTHON_LOG_FORMAT" +""" +.. envvar:: OTEL_PYTHON_LOG_FORMAT + +The :envvar:`OTEL_PYTHON_LOG_FORMAT` environment variable sets the log format for the OpenTelemetry LoggingHandler's Formatter +Default: "logging.BASIC_FORMAT" +""" + +OTEL_PYTHON_LOG_LEVEL = "OTEL_PYTHON_LOG_LEVEL" +""" +.. envvar:: OTEL_PYTHON_LOG_LEVEL + +The :envvar:`OTEL_PYTHON_LOG_LEVEL` environment variable sets the log level for the OpenTelemetry LoggingHandler +Default: "logging.NOTSET" +""" + OTEL_TRACES_SAMPLER = "OTEL_TRACES_SAMPLER" """ .. envvar:: OTEL_TRACES_SAMPLER diff --git a/opentelemetry-sdk/tests/test_configurator.py b/opentelemetry-sdk/tests/test_configurator.py index 5d971ed1d6..c078e81140 100644 --- a/opentelemetry-sdk/tests/test_configurator.py +++ b/opentelemetry-sdk/tests/test_configurator.py @@ -19,6 +19,14 @@ import logging import logging.config from logging import WARNING, getLogger +from logging import ( + DEBUG, + ERROR, + INFO, + NOTSET, + WARNING, + getLogger, +) from os import environ from typing import Iterable, Optional, Sequence from unittest import TestCase, mock @@ -35,6 +43,7 @@ _EXPORTER_OTLP_PROTO_HTTP, _get_exporter_names, _get_id_generator, + _get_log_level, _get_sampler, _get_tracer_configurator, _import_config_components, @@ -88,6 +97,9 @@ from opentelemetry.util.types import Attributes +CUSTOM_LOG_FORMAT = "CUSTOM FORMAT %(levelname)s:%(name)s:%(message)s" + + class Provider: def __init__( self, @@ -818,7 +830,119 @@ def test_logging_init_custom_log_record_processors(self): @patch.dict( environ, - {"OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service"}, + { + "OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service", + "OTEL_PYTHON_LOG_LEVEL": "CUSTOM_LOG_LEVEL", + }, + clear=True, + ) + @patch("opentelemetry.sdk._configuration._get_log_level", return_value=39) + def test_logging_init_exporter_level_under(self, log_level_mock): + resource = Resource.create({}) + _init_logging( + {"otlp": DummyOTLPLogExporter}, + resource=resource, + ) + self.assertEqual(self.set_provider_mock.call_count, 1) + provider = self.set_provider_mock.call_args[0][0] + self.assertIsInstance(provider, DummyLoggerProvider) + self.assertIsInstance(provider.resource, Resource) + self.assertEqual( + provider.resource.attributes.get("service.name"), + "otlp-service", + ) + self.assertIsInstance(provider.processor, DummyLogRecordProcessor) + self.assertIsInstance( + provider.processor.exporter, DummyOTLPLogExporter + ) + getLogger(__name__).error("hello") + self.assertTrue(provider.processor.exporter.export_called) + root_logger = getLogger() + handler_present = False + for handler in root_logger.handlers: + if isinstance(handler, LoggingHandler): + handler_present = True + self.assertEqual(handler.level, 39) + self.assertTrue(handler_present) + + @patch.dict( + environ, + { + "OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service", + "OTEL_PYTHON_LOG_LEVEL": "CUSTOM_LOG_LEVEL", + }, + clear=True, + ) + @patch("opentelemetry.sdk._configuration._get_log_level", return_value=41) + def test_logging_init_exporter_level_over(self, log_level_mock): + resource = Resource.create({}) + _init_logging( + {"otlp": DummyOTLPLogExporter}, + resource=resource, + ) + self.assertEqual(self.set_provider_mock.call_count, 1) + provider = self.set_provider_mock.call_args[0][0] + self.assertIsInstance(provider, DummyLoggerProvider) + self.assertIsInstance(provider.resource, Resource) + self.assertEqual( + provider.resource.attributes.get("service.name"), + "otlp-service", + ) + self.assertIsInstance(provider.processor, DummyLogRecordProcessor) + self.assertIsInstance( + provider.processor.exporter, DummyOTLPLogExporter + ) + getLogger(__name__).error("hello") + self.assertFalse(provider.processor.exporter.export_called) + root_logger = getLogger() + handler_present = False + for handler in root_logger.handlers: + if isinstance(handler, LoggingHandler): + handler_present = True + self.assertEqual(handler.level, 41) + self.assertTrue(handler_present) + + @patch.dict( + environ, + { + "OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service", + "OTEL_PYTHON_LOG_FORMAT": CUSTOM_LOG_FORMAT, + }, + ) + def test_logging_init_exporter_format(self): + resource = Resource.create({}) + _init_logging( + {"otlp": DummyOTLPLogExporter}, + resource=resource, + ) + self.assertEqual(self.set_provider_mock.call_count, 1) + provider = self.set_provider_mock.call_args[0][0] + self.assertIsInstance(provider, DummyLoggerProvider) + self.assertIsInstance(provider.resource, Resource) + self.assertEqual( + provider.resource.attributes.get("service.name"), + "otlp-service", + ) + self.assertIsInstance(provider.processor, DummyLogRecordProcessor) + self.assertIsInstance( + provider.processor.exporter, DummyOTLPLogExporter + ) + getLogger(__name__).error("hello") + self.assertTrue(provider.processor.exporter.export_called) + root_logger = getLogger() + self.assertEqual(root_logger.level, WARNING) + handler_present = False + for handler in root_logger.handlers: + if isinstance(handler, LoggingHandler): + self.assertEqual(handler.formatter._fmt, CUSTOM_LOG_FORMAT) + handler_present = True + self.assertTrue(handler_present) + + @patch.dict( + environ, + { + "OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service", + }, ) def test_logging_init_exporter_without_handler_setup(self): resource = Resource.create({}) @@ -842,6 +966,43 @@ def test_logging_init_exporter_without_handler_setup(self): ) getLogger(__name__).error("hello") self.assertFalse(provider.processors[0].exporter.export_called) + root_logger = getLogger() + self.assertEqual(root_logger.level, WARNING) + for handler in root_logger.handlers: + if isinstance(handler, LoggingHandler): + self.fail() + + @patch.dict(environ, {}, clear=True) + def test_OTEL_PYTHON_LOG_LEVEL_by_name_default(self): + self.assertEqual(_get_log_level(), NOTSET) + + @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": "NOTSET "}, clear=True) + def test_OTEL_PYTHON_LOG_LEVEL_by_name_notset(self): + self.assertEqual(_get_log_level(), NOTSET) + + @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " DeBug "}, clear=True) + def test_OTEL_PYTHON_LOG_LEVEL_by_name_debug(self): + self.assertEqual(_get_log_level(), DEBUG) + + @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " info "}, clear=True) + def test_OTEL_PYTHON_LOG_LEVEL_by_name_info(self): + self.assertEqual(_get_log_level(), INFO) + + @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " warn"}, clear=True) + def test_OTEL_PYTHON_LOG_LEVEL_by_name_warn(self): + self.assertEqual(_get_log_level(), WARNING) + + @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " warnING "}, clear=True) + def test_OTEL_PYTHON_LOG_LEVEL_by_name_warning(self): + self.assertEqual(_get_log_level(), WARNING) + + @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " eRroR"}, clear=True) + def test_OTEL_PYTHON_LOG_LEVEL_by_name_error(self): + self.assertEqual(_get_log_level(), ERROR) + + @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": "foobar"}, clear=True) + def test_OTEL_PYTHON_LOG_LEVEL_by_name_invalid(self): + self.assertEqual(_get_log_level(), NOTSET) @patch.dict( environ, @@ -1074,6 +1235,60 @@ def test_basicConfig_preserves_otel_handler(self): "Should still have exactly one OpenTelemetry LoggingHandler", ) + @patch.dict( + environ, + { + "OTEL_TRACES_EXPORTER": _EXPORTER_OTLP, + "OTEL_METRICS_EXPORTER": _EXPORTER_OTLP_PROTO_GRPC, + "OTEL_LOGS_EXPORTER": _EXPORTER_OTLP_PROTO_HTTP, + }, + ) + @patch.dict( + environ, + { + "OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service, custom.key.1=env-value", + "OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED": "False", + }, + ) + @patch("opentelemetry.sdk._configuration.Resource") + @patch("opentelemetry.sdk._configuration._import_exporters") + @patch("opentelemetry.sdk._configuration._get_exporter_names") + @patch("opentelemetry.sdk._configuration._init_tracing") + @patch("opentelemetry.sdk._configuration._init_logging") + @patch("opentelemetry.sdk._configuration._init_metrics") + def test_initialize_components_kwargs_disable_logging_handler( + self, + metrics_mock, + logging_mock, + tracing_mock, + exporter_names_mock, + import_exporters_mock, + resource_mock, + ): + exporter_names_mock.return_value = [ + "env_var_exporter_1", + "env_var_exporter_2", + ] + import_exporters_mock.return_value = ( + "TEST_SPAN_EXPORTERS_DICT", + "TEST_METRICS_EXPORTERS_DICT", + "TEST_LOG_EXPORTERS_DICT", + ) + resource_mock.create.return_value = "TEST_RESOURCE" + kwargs = { + "auto_instrumentation_version": "auto-version", + "trace_exporter_names": ["custom_span_exporter"], + "metric_exporter_names": ["custom_metric_exporter"], + "log_exporter_names": ["custom_log_exporter"], + "sampler": "TEST_SAMPLER", + "resource_attributes": { + "custom.key.1": "pass-in-value-1", + "custom.key.2": "pass-in-value-2", + }, + "id_generator": "TEST_GENERATOR", + } + _initialize_components(**kwargs) + def test_dictConfig_preserves_otel_handler(self): with ResetGlobalLoggingState(): _init_logging( From 37c94631ee1b02e96593dc128448e870723c2b6a Mon Sep 17 00:00:00 2001 From: Jeremy Voss Date: Mon, 12 May 2025 15:03:04 -0700 Subject: [PATCH 02/10] Clean up --- opentelemetry-sdk/tests/test_configurator.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/opentelemetry-sdk/tests/test_configurator.py b/opentelemetry-sdk/tests/test_configurator.py index c078e81140..8189a40ac4 100644 --- a/opentelemetry-sdk/tests/test_configurator.py +++ b/opentelemetry-sdk/tests/test_configurator.py @@ -834,7 +834,6 @@ def test_logging_init_custom_log_record_processors(self): "OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service", "OTEL_PYTHON_LOG_LEVEL": "CUSTOM_LOG_LEVEL", }, - clear=True, ) @patch("opentelemetry.sdk._configuration._get_log_level", return_value=39) def test_logging_init_exporter_level_under(self, log_level_mock): @@ -842,6 +841,7 @@ def test_logging_init_exporter_level_under(self, log_level_mock): _init_logging( {"otlp": DummyOTLPLogExporter}, resource=resource, + setup_logging_handler=False, ) self.assertEqual(self.set_provider_mock.call_count, 1) provider = self.set_provider_mock.call_args[0][0] @@ -856,8 +856,9 @@ def test_logging_init_exporter_level_under(self, log_level_mock): provider.processor.exporter, DummyOTLPLogExporter ) getLogger(__name__).error("hello") - self.assertTrue(provider.processor.exporter.export_called) + self.assertFalse(provider.processor.exporter.export_called) root_logger = getLogger() + self.assertEqual(root_logger.level, WARNING) handler_present = False for handler in root_logger.handlers: if isinstance(handler, LoggingHandler): @@ -873,8 +874,8 @@ def test_logging_init_exporter_level_under(self, log_level_mock): }, clear=True, ) - @patch("opentelemetry.sdk._configuration._get_log_level", return_value=41) - def test_logging_init_exporter_level_over(self, log_level_mock): + @patch("opentelemetry.sdk._configuration._get_log_level", return_value=39) + def test_logging_init_exporter_level_under(self, log_level_mock): resource = Resource.create({}) _init_logging( {"otlp": DummyOTLPLogExporter}, @@ -899,7 +900,7 @@ def test_logging_init_exporter_level_over(self, log_level_mock): for handler in root_logger.handlers: if isinstance(handler, LoggingHandler): handler_present = True - self.assertEqual(handler.level, 41) + self.assertEqual(handler.level, 39) self.assertTrue(handler_present) @patch.dict( @@ -928,7 +929,7 @@ def test_logging_init_exporter_format(self): provider.processor.exporter, DummyOTLPLogExporter ) getLogger(__name__).error("hello") - self.assertTrue(provider.processor.exporter.export_called) + self.assertFalse(provider.processor.exporter.export_called) root_logger = getLogger() self.assertEqual(root_logger.level, WARNING) handler_present = False @@ -942,6 +943,7 @@ def test_logging_init_exporter_format(self): environ, { "OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service", + "OTEL_PYTHON_LOG_FORMAT": CUSTOM_LOG_FORMAT, }, ) def test_logging_init_exporter_without_handler_setup(self): @@ -968,9 +970,12 @@ def test_logging_init_exporter_without_handler_setup(self): self.assertFalse(provider.processors[0].exporter.export_called) root_logger = getLogger() self.assertEqual(root_logger.level, WARNING) + handler_present = False for handler in root_logger.handlers: if isinstance(handler, LoggingHandler): - self.fail() + self.assertEqual(handler.formatter._fmt, CUSTOM_LOG_FORMAT) + handler_present = True + self.assertTrue(handler_present) @patch.dict(environ, {}, clear=True) def test_OTEL_PYTHON_LOG_LEVEL_by_name_default(self): From e29bc415f3b849038cfcd41766d2f70768611076 Mon Sep 17 00:00:00 2001 From: Jeremy Voss Date: Mon, 12 May 2025 15:24:22 -0700 Subject: [PATCH 03/10] Reorg tests added for past PR: https://github.com/open-telemetry/opentelemetry-python/pull/4340 --- opentelemetry-sdk/tests/test_configurator.py | 54 ++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/opentelemetry-sdk/tests/test_configurator.py b/opentelemetry-sdk/tests/test_configurator.py index 8189a40ac4..abba017c54 100644 --- a/opentelemetry-sdk/tests/test_configurator.py +++ b/opentelemetry-sdk/tests/test_configurator.py @@ -1337,6 +1337,60 @@ def test_dictConfig_preserves_otel_handler(self): "Should still have exactly one OpenTelemetry LoggingHandler", ) + def test_basicConfig_works_with_otel_handler(self): + with ClearLoggingHandlers(): + _init_logging( + {"otlp": DummyOTLPLogExporter}, + Resource.create({}), + setup_logging_handler=True, + ) + + logging.basicConfig(level=logging.INFO) + + root_logger = logging.getLogger() + stream_handlers = [ + h + for h in root_logger.handlers + if isinstance(h, logging.StreamHandler) + ] + self.assertEqual( + len(stream_handlers), + 1, + "basicConfig should add a StreamHandler even when OTel handler exists", + ) + + def test_basicConfig_preserves_otel_handler(self): + with ClearLoggingHandlers(): + _init_logging( + {"otlp": DummyOTLPLogExporter}, + Resource.create({}), + setup_logging_handler=True, + ) + + root_logger = logging.getLogger() + self.assertEqual( + len(root_logger.handlers), + 1, + "Should be exactly one OpenTelemetry LoggingHandler", + ) + handler = root_logger.handlers[0] + self.assertIsInstance(handler, LoggingHandler) + + logging.basicConfig() + + self.assertGreater(len(root_logger.handlers), 1) + + logging_handlers = [ + h + for h in root_logger.handlers + if isinstance(h, LoggingHandler) + ] + self.assertEqual( + len(logging_handlers), + 1, + "Should still have exactly one OpenTelemetry LoggingHandler", + ) + class TestMetricsInit(TestCase): def setUp(self): From 2975983cb5a87697bd961ea23efc4a37997430e0 Mon Sep 17 00:00:00 2001 From: Jeremy Voss Date: Mon, 12 May 2025 15:48:51 -0700 Subject: [PATCH 04/10] lint --- .../src/opentelemetry/sdk/_configuration/__init__.py | 2 ++ opentelemetry-sdk/tests/test_configurator.py | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py index efd226227d..d4774546d9 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py @@ -53,6 +53,8 @@ OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, OTEL_PYTHON_TRACER_CONFIGURATOR, + OTEL_PYTHON_LOG_FORMAT, + OTEL_PYTHON_LOG_LEVEL, OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG, OTEL_PYTHON_LOG_LEVEL, diff --git a/opentelemetry-sdk/tests/test_configurator.py b/opentelemetry-sdk/tests/test_configurator.py index abba017c54..2b05f2d9d2 100644 --- a/opentelemetry-sdk/tests/test_configurator.py +++ b/opentelemetry-sdk/tests/test_configurator.py @@ -96,7 +96,6 @@ from opentelemetry.trace.span import TraceState from opentelemetry.util.types import Attributes - CUSTOM_LOG_FORMAT = "CUSTOM FORMAT %(levelname)s:%(name)s:%(message)s" From 3c75aa69f83696fffbffed55a5e246f2b725a825 Mon Sep 17 00:00:00 2001 From: Jeremy Voss Date: Tue, 13 May 2025 12:19:13 -0700 Subject: [PATCH 05/10] Fix tests on python 3.8 --- opentelemetry-sdk/tests/test_configurator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/opentelemetry-sdk/tests/test_configurator.py b/opentelemetry-sdk/tests/test_configurator.py index 2b05f2d9d2..da3a76fe6b 100644 --- a/opentelemetry-sdk/tests/test_configurator.py +++ b/opentelemetry-sdk/tests/test_configurator.py @@ -729,6 +729,8 @@ def setUp(self): self.set_event_logger_provider_patch.start() ) + getLogger().handlers.clear() + def tearDown(self): self.processor_patch.stop() self.set_provider_patch.stop() From aa70486f9789fc757c75fc864d2173cb4e3523ae Mon Sep 17 00:00:00 2001 From: Jeremy Voss Date: Wed, 14 May 2025 10:16:50 -0700 Subject: [PATCH 06/10] OTEL_PYTHON_LOG_HANDLER_LOG_LEVEL --- .../sdk/_configuration/__init__.py | 13 +++++--- .../sdk/environment_variables/__init__.py | 6 ++-- opentelemetry-sdk/tests/test_configurator.py | 33 ++++++++++--------- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py index d4774546d9..59de95d4af 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py @@ -54,7 +54,7 @@ OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, OTEL_PYTHON_TRACER_CONFIGURATOR, OTEL_PYTHON_LOG_FORMAT, - OTEL_PYTHON_LOG_LEVEL, + OTEL_PYTHON_LOG_HANDLER_LEVEL, OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG, OTEL_PYTHON_LOG_LEVEL, @@ -105,7 +105,7 @@ _OTEL_SAMPLER_ENTRY_POINT_GROUP = "opentelemetry_traces_sampler" -_OTEL_PYTHON_LOG_LEVEL_BY_NAME = { +_OTEL_PYTHON_LOG_HANDLER_LEVEL_BY_NAME = { "notset": logging.NOTSET, "debug": logging.DEBUG, "info": logging.INFO, @@ -180,13 +180,16 @@ def _get_id_generator() -> str: return environ.get(OTEL_PYTHON_ID_GENERATOR, _DEFAULT_ID_GENERATOR) def _get_log_level() -> int: - return _OTEL_PYTHON_LOG_LEVEL_BY_NAME.get(environ.get(OTEL_PYTHON_LOG_LEVEL, "notset").lower().strip(), logging.NOTSET) - + return _OTEL_PYTHON_LOG_HANDLER_LEVEL_BY_NAME.get( + environ.get(OTEL_PYTHON_LOG_HANDLER_LEVEL, "notset").lower().strip(), + logging.NOTSET, + ) def _get_tracer_configurator() -> str | None: return environ.get(OTEL_PYTHON_TRACER_CONFIGURATOR, None) + def _get_exporter_entry_point( exporter_name: str, signal_type: Literal["traces", "metrics", "logs"] ): @@ -347,7 +350,7 @@ def _init_logging( level=logging.NOTSET, logger_provider=provider ) # Log level - if OTEL_PYTHON_LOG_LEVEL in environ: + if OTEL_PYTHON_LOG_HANDLER_LEVEL in environ: handler.setLevel(_get_log_level()) # Log format if OTEL_PYTHON_LOG_FORMAT in environ: diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py index 9eea06d6f4..ebc0d212a2 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py @@ -62,11 +62,11 @@ Default: "logging.BASIC_FORMAT" """ -OTEL_PYTHON_LOG_LEVEL = "OTEL_PYTHON_LOG_LEVEL" +OTEL_PYTHON_LOG_HANDLER_LEVEL = "OTEL_PYTHON_LOG_HANDLER_LEVEL" """ -.. envvar:: OTEL_PYTHON_LOG_LEVEL +.. envvar:: OTEL_PYTHON_LOG_HANDLER_LEVEL -The :envvar:`OTEL_PYTHON_LOG_LEVEL` environment variable sets the log level for the OpenTelemetry LoggingHandler +The :envvar:`OTEL_PYTHON_LOG_HANDLER_LEVEL` environment variable sets the log level for the OpenTelemetry LoggingHandler Default: "logging.NOTSET" """ diff --git a/opentelemetry-sdk/tests/test_configurator.py b/opentelemetry-sdk/tests/test_configurator.py index da3a76fe6b..fa83433731 100644 --- a/opentelemetry-sdk/tests/test_configurator.py +++ b/opentelemetry-sdk/tests/test_configurator.py @@ -871,7 +871,7 @@ def test_logging_init_exporter_level_under(self, log_level_mock): environ, { "OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service", - "OTEL_PYTHON_LOG_LEVEL": "CUSTOM_LOG_LEVEL", + "OTEL_PYTHON_LOG_HANDLER_LEVEL": "CUSTOM_LOG_LEVEL", }, clear=True, ) @@ -908,6 +908,7 @@ def test_logging_init_exporter_level_under(self, log_level_mock): environ, { "OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service", + "OTEL_PYTHON_LOG_HANDLER_LEVEL": "CUSTOM_LOG_LEVEL", "OTEL_PYTHON_LOG_FORMAT": CUSTOM_LOG_FORMAT, }, ) @@ -979,35 +980,35 @@ def test_logging_init_exporter_without_handler_setup(self): self.assertTrue(handler_present) @patch.dict(environ, {}, clear=True) - def test_OTEL_PYTHON_LOG_LEVEL_by_name_default(self): + def test_OTEL_PYTHON_LOG_HANDLER_LEVEL_by_name_default(self): self.assertEqual(_get_log_level(), NOTSET) - @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": "NOTSET "}, clear=True) - def test_OTEL_PYTHON_LOG_LEVEL_by_name_notset(self): + @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": "NOTSET "}, clear=True) + def test_OTEL_PYTHON_LOG_HANDLER_LEVEL_by_name_notset(self): self.assertEqual(_get_log_level(), NOTSET) - @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " DeBug "}, clear=True) - def test_OTEL_PYTHON_LOG_LEVEL_by_name_debug(self): + @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " DeBug "}, clear=True) + def test_OTEL_PYTHON_LOG_HANDLER_LEVEL_by_name_debug(self): self.assertEqual(_get_log_level(), DEBUG) - @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " info "}, clear=True) - def test_OTEL_PYTHON_LOG_LEVEL_by_name_info(self): + @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " info "}, clear=True) + def test_OTEL_PYTHON_LOG_HANDLER_LEVEL_by_name_info(self): self.assertEqual(_get_log_level(), INFO) - @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " warn"}, clear=True) - def test_OTEL_PYTHON_LOG_LEVEL_by_name_warn(self): + @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " warn"}, clear=True) + def test_OTEL_PYTHON_LOG_HANDLER_LEVEL_by_name_warn(self): self.assertEqual(_get_log_level(), WARNING) - @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " warnING "}, clear=True) - def test_OTEL_PYTHON_LOG_LEVEL_by_name_warning(self): + @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " warnING "}, clear=True) + def test_OTEL_PYTHON_LOG_HANDLER_LEVEL_by_name_warning(self): self.assertEqual(_get_log_level(), WARNING) - @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " eRroR"}, clear=True) - def test_OTEL_PYTHON_LOG_LEVEL_by_name_error(self): + @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " eRroR"}, clear=True) + def test_OTEL_PYTHON_LOG_HANDLER_LEVEL_by_name_error(self): self.assertEqual(_get_log_level(), ERROR) - @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": "foobar"}, clear=True) - def test_OTEL_PYTHON_LOG_LEVEL_by_name_invalid(self): + @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": "foobar"}, clear=True) + def test_OTEL_PYTHON_LOG_HANDLER_LEVEL_by_name_invalid(self): self.assertEqual(_get_log_level(), NOTSET) @patch.dict( From a9de81442d9a55fe6710ff3ce35ba5b0f5e521d5 Mon Sep 17 00:00:00 2001 From: Jeremy Voss Date: Wed, 14 May 2025 10:27:14 -0700 Subject: [PATCH 07/10] clean up test names --- opentelemetry-sdk/tests/test_configurator.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/opentelemetry-sdk/tests/test_configurator.py b/opentelemetry-sdk/tests/test_configurator.py index fa83433731..46c1448ffc 100644 --- a/opentelemetry-sdk/tests/test_configurator.py +++ b/opentelemetry-sdk/tests/test_configurator.py @@ -980,35 +980,35 @@ def test_logging_init_exporter_without_handler_setup(self): self.assertTrue(handler_present) @patch.dict(environ, {}, clear=True) - def test_OTEL_PYTHON_LOG_HANDLER_LEVEL_by_name_default(self): + def test_otel_log_level_by_name_default(self): self.assertEqual(_get_log_level(), NOTSET) @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": "NOTSET "}, clear=True) - def test_OTEL_PYTHON_LOG_HANDLER_LEVEL_by_name_notset(self): + def test_otel_log_level_by_name_notset(self): self.assertEqual(_get_log_level(), NOTSET) @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " DeBug "}, clear=True) - def test_OTEL_PYTHON_LOG_HANDLER_LEVEL_by_name_debug(self): + def test_otel_log_level_by_name_debug(self): self.assertEqual(_get_log_level(), DEBUG) @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " info "}, clear=True) - def test_OTEL_PYTHON_LOG_HANDLER_LEVEL_by_name_info(self): + def test_otel_log_level_by_name_info(self): self.assertEqual(_get_log_level(), INFO) @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " warn"}, clear=True) - def test_OTEL_PYTHON_LOG_HANDLER_LEVEL_by_name_warn(self): + def test_otel_log_level_by_name_warn(self): self.assertEqual(_get_log_level(), WARNING) @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " warnING "}, clear=True) - def test_OTEL_PYTHON_LOG_HANDLER_LEVEL_by_name_warning(self): + def test_otel_log_level_by_name_warning(self): self.assertEqual(_get_log_level(), WARNING) @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " eRroR"}, clear=True) - def test_OTEL_PYTHON_LOG_HANDLER_LEVEL_by_name_error(self): + def test_otel_log_level_by_name_error(self): self.assertEqual(_get_log_level(), ERROR) @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": "foobar"}, clear=True) - def test_OTEL_PYTHON_LOG_HANDLER_LEVEL_by_name_invalid(self): + def test_otel_log_level_by_name_invalid(self): self.assertEqual(_get_log_level(), NOTSET) @patch.dict( From a28dd959f8017a38b9219f7c61212919a0d782e1 Mon Sep 17 00:00:00 2001 From: Jeremy Voss Date: Wed, 14 May 2025 10:30:00 -0700 Subject: [PATCH 08/10] lint --- opentelemetry-sdk/tests/test_configurator.py | 28 +++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/opentelemetry-sdk/tests/test_configurator.py b/opentelemetry-sdk/tests/test_configurator.py index 46c1448ffc..43a8deac6c 100644 --- a/opentelemetry-sdk/tests/test_configurator.py +++ b/opentelemetry-sdk/tests/test_configurator.py @@ -983,31 +983,45 @@ def test_logging_init_exporter_without_handler_setup(self): def test_otel_log_level_by_name_default(self): self.assertEqual(_get_log_level(), NOTSET) - @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": "NOTSET "}, clear=True) + @patch.dict( + environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": "NOTSET "}, clear=True + ) def test_otel_log_level_by_name_notset(self): self.assertEqual(_get_log_level(), NOTSET) - @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " DeBug "}, clear=True) + @patch.dict( + environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " DeBug "}, clear=True + ) def test_otel_log_level_by_name_debug(self): self.assertEqual(_get_log_level(), DEBUG) - @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " info "}, clear=True) + @patch.dict( + environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " info "}, clear=True + ) def test_otel_log_level_by_name_info(self): self.assertEqual(_get_log_level(), INFO) - @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " warn"}, clear=True) + @patch.dict( + environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " warn"}, clear=True + ) def test_otel_log_level_by_name_warn(self): self.assertEqual(_get_log_level(), WARNING) - @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " warnING "}, clear=True) + @patch.dict( + environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " warnING "}, clear=True + ) def test_otel_log_level_by_name_warning(self): self.assertEqual(_get_log_level(), WARNING) - @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " eRroR"}, clear=True) + @patch.dict( + environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " eRroR"}, clear=True + ) def test_otel_log_level_by_name_error(self): self.assertEqual(_get_log_level(), ERROR) - @patch.dict(environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": "foobar"}, clear=True) + @patch.dict( + environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": "foobar"}, clear=True + ) def test_otel_log_level_by_name_invalid(self): self.assertEqual(_get_log_level(), NOTSET) From 6e4880d21ff1b65e3467db5c0ac301e8d8b99798 Mon Sep 17 00:00:00 2001 From: Jeremy Voss Date: Wed, 14 May 2025 10:52:35 -0700 Subject: [PATCH 09/10] OTEL_PYTHON_LOG_LEVEL --- .../sdk/_configuration/__init__.py | 10 +++--- .../sdk/environment_variables/__init__.py | 6 ++-- opentelemetry-sdk/tests/test_configurator.py | 31 ++++++------------- 3 files changed, 17 insertions(+), 30 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py index 59de95d4af..07eb576c68 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py @@ -54,7 +54,7 @@ OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, OTEL_PYTHON_TRACER_CONFIGURATOR, OTEL_PYTHON_LOG_FORMAT, - OTEL_PYTHON_LOG_HANDLER_LEVEL, + OTEL_PYTHON_LOG_LEVEL, OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG, OTEL_PYTHON_LOG_LEVEL, @@ -105,7 +105,7 @@ _OTEL_SAMPLER_ENTRY_POINT_GROUP = "opentelemetry_traces_sampler" -_OTEL_PYTHON_LOG_HANDLER_LEVEL_BY_NAME = { +_OTEL_PYTHON_LOG_LEVEL_BY_NAME = { "notset": logging.NOTSET, "debug": logging.DEBUG, "info": logging.INFO, @@ -180,8 +180,8 @@ def _get_id_generator() -> str: return environ.get(OTEL_PYTHON_ID_GENERATOR, _DEFAULT_ID_GENERATOR) def _get_log_level() -> int: - return _OTEL_PYTHON_LOG_HANDLER_LEVEL_BY_NAME.get( - environ.get(OTEL_PYTHON_LOG_HANDLER_LEVEL, "notset").lower().strip(), + return _OTEL_PYTHON_LOG_LEVEL_BY_NAME.get( + environ.get(OTEL_PYTHON_LOG_LEVEL, "notset").lower().strip(), logging.NOTSET, ) @@ -350,7 +350,7 @@ def _init_logging( level=logging.NOTSET, logger_provider=provider ) # Log level - if OTEL_PYTHON_LOG_HANDLER_LEVEL in environ: + if OTEL_PYTHON_LOG_LEVEL in environ: handler.setLevel(_get_log_level()) # Log format if OTEL_PYTHON_LOG_FORMAT in environ: diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py index ebc0d212a2..9eea06d6f4 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py @@ -62,11 +62,11 @@ Default: "logging.BASIC_FORMAT" """ -OTEL_PYTHON_LOG_HANDLER_LEVEL = "OTEL_PYTHON_LOG_HANDLER_LEVEL" +OTEL_PYTHON_LOG_LEVEL = "OTEL_PYTHON_LOG_LEVEL" """ -.. envvar:: OTEL_PYTHON_LOG_HANDLER_LEVEL +.. envvar:: OTEL_PYTHON_LOG_LEVEL -The :envvar:`OTEL_PYTHON_LOG_HANDLER_LEVEL` environment variable sets the log level for the OpenTelemetry LoggingHandler +The :envvar:`OTEL_PYTHON_LOG_LEVEL` environment variable sets the log level for the OpenTelemetry LoggingHandler Default: "logging.NOTSET" """ diff --git a/opentelemetry-sdk/tests/test_configurator.py b/opentelemetry-sdk/tests/test_configurator.py index 43a8deac6c..4f655a8e8d 100644 --- a/opentelemetry-sdk/tests/test_configurator.py +++ b/opentelemetry-sdk/tests/test_configurator.py @@ -871,7 +871,7 @@ def test_logging_init_exporter_level_under(self, log_level_mock): environ, { "OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service", - "OTEL_PYTHON_LOG_HANDLER_LEVEL": "CUSTOM_LOG_LEVEL", + "OTEL_PYTHON_LOG_LEVEL": "CUSTOM_LOG_LEVEL", }, clear=True, ) @@ -908,6 +908,7 @@ def test_logging_init_exporter_level_under(self, log_level_mock): environ, { "OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service", + "OTEL_PYTHON_LOG_LEVEL": "CUSTOM_LOG_LEVEL", "OTEL_PYTHON_LOG_HANDLER_LEVEL": "CUSTOM_LOG_LEVEL", "OTEL_PYTHON_LOG_FORMAT": CUSTOM_LOG_FORMAT, }, @@ -983,45 +984,31 @@ def test_logging_init_exporter_without_handler_setup(self): def test_otel_log_level_by_name_default(self): self.assertEqual(_get_log_level(), NOTSET) - @patch.dict( - environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": "NOTSET "}, clear=True - ) + @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": "NOTSET "}, clear=True) def test_otel_log_level_by_name_notset(self): self.assertEqual(_get_log_level(), NOTSET) - @patch.dict( - environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " DeBug "}, clear=True - ) + @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " DeBug "}, clear=True) def test_otel_log_level_by_name_debug(self): self.assertEqual(_get_log_level(), DEBUG) - @patch.dict( - environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " info "}, clear=True - ) + @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " info "}, clear=True) def test_otel_log_level_by_name_info(self): self.assertEqual(_get_log_level(), INFO) - @patch.dict( - environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " warn"}, clear=True - ) + @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " warn"}, clear=True) def test_otel_log_level_by_name_warn(self): self.assertEqual(_get_log_level(), WARNING) - @patch.dict( - environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " warnING "}, clear=True - ) + @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " warnING "}, clear=True) def test_otel_log_level_by_name_warning(self): self.assertEqual(_get_log_level(), WARNING) - @patch.dict( - environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " eRroR"}, clear=True - ) + @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " eRroR"}, clear=True) def test_otel_log_level_by_name_error(self): self.assertEqual(_get_log_level(), ERROR) - @patch.dict( - environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": "foobar"}, clear=True - ) + @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": "foobar"}, clear=True) def test_otel_log_level_by_name_invalid(self): self.assertEqual(_get_log_level(), NOTSET) From 980dbe7f9bd88e591933f9759696fa4de4716cd9 Mon Sep 17 00:00:00 2001 From: JWinermaSplunk Date: Wed, 18 Feb 2026 10:40:41 -0800 Subject: [PATCH 10/10] initial commit --- .../sdk/_configuration/__init__.py | 21 ++-- .../sdk/environment_variables/__init__.py | 6 +- opentelemetry-sdk/tests/test_configurator.py | 119 ++++++------------ 3 files changed, 53 insertions(+), 93 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py index 07eb576c68..cb98935530 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py @@ -52,13 +52,11 @@ OTEL_EXPORTER_OTLP_METRICS_PROTOCOL, OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, - OTEL_PYTHON_TRACER_CONFIGURATOR, OTEL_PYTHON_LOG_FORMAT, - OTEL_PYTHON_LOG_LEVEL, + OTEL_PYTHON_LOG_HANDLER_LEVEL, + OTEL_PYTHON_TRACER_CONFIGURATOR, OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG, - OTEL_PYTHON_LOG_LEVEL, - OTEL_PYTHON_LOG_FORMAT, ) from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export import ( @@ -105,7 +103,7 @@ _OTEL_SAMPLER_ENTRY_POINT_GROUP = "opentelemetry_traces_sampler" -_OTEL_PYTHON_LOG_LEVEL_BY_NAME = { +_OTEL_PYTHON_LOG_HANDLER_LEVEL_BY_NAME = { "notset": logging.NOTSET, "debug": logging.DEBUG, "info": logging.INFO, @@ -179,17 +177,18 @@ def _get_sampler() -> str | None: def _get_id_generator() -> str: return environ.get(OTEL_PYTHON_ID_GENERATOR, _DEFAULT_ID_GENERATOR) + def _get_log_level() -> int: - return _OTEL_PYTHON_LOG_LEVEL_BY_NAME.get( - environ.get(OTEL_PYTHON_LOG_LEVEL, "notset").lower().strip(), + return _OTEL_PYTHON_LOG_HANDLER_LEVEL_BY_NAME.get( + environ.get(OTEL_PYTHON_LOG_HANDLER_LEVEL, "notset").lower().strip(), logging.NOTSET, ) + def _get_tracer_configurator() -> str | None: return environ.get(OTEL_PYTHON_TRACER_CONFIGURATOR, None) - def _get_exporter_entry_point( exporter_name: str, signal_type: Literal["traces", "metrics", "logs"] ): @@ -350,11 +349,13 @@ def _init_logging( level=logging.NOTSET, logger_provider=provider ) # Log level - if OTEL_PYTHON_LOG_LEVEL in environ: + if OTEL_PYTHON_LOG_HANDLER_LEVEL in environ: handler.setLevel(_get_log_level()) # Log format if OTEL_PYTHON_LOG_FORMAT in environ: - log_format = environ.get(OTEL_PYTHON_LOG_FORMAT, logging.BASIC_FORMAT) + log_format = environ.get( + OTEL_PYTHON_LOG_FORMAT, logging.BASIC_FORMAT + ) handler.setFormatter(logging.Formatter(log_format)) logging.getLogger().addHandler(handler) _overwrite_logging_config_fns(handler) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py index 9eea06d6f4..ebc0d212a2 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py @@ -62,11 +62,11 @@ Default: "logging.BASIC_FORMAT" """ -OTEL_PYTHON_LOG_LEVEL = "OTEL_PYTHON_LOG_LEVEL" +OTEL_PYTHON_LOG_HANDLER_LEVEL = "OTEL_PYTHON_LOG_HANDLER_LEVEL" """ -.. envvar:: OTEL_PYTHON_LOG_LEVEL +.. envvar:: OTEL_PYTHON_LOG_HANDLER_LEVEL -The :envvar:`OTEL_PYTHON_LOG_LEVEL` environment variable sets the log level for the OpenTelemetry LoggingHandler +The :envvar:`OTEL_PYTHON_LOG_HANDLER_LEVEL` environment variable sets the log level for the OpenTelemetry LoggingHandler Default: "logging.NOTSET" """ diff --git a/opentelemetry-sdk/tests/test_configurator.py b/opentelemetry-sdk/tests/test_configurator.py index 4f655a8e8d..715ddbaf27 100644 --- a/opentelemetry-sdk/tests/test_configurator.py +++ b/opentelemetry-sdk/tests/test_configurator.py @@ -18,7 +18,6 @@ import logging import logging.config -from logging import WARNING, getLogger from logging import ( DEBUG, ERROR, @@ -833,7 +832,7 @@ def test_logging_init_custom_log_record_processors(self): environ, { "OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service", - "OTEL_PYTHON_LOG_LEVEL": "CUSTOM_LOG_LEVEL", + "OTEL_PYTHON_LOG_HANDLER_LEVEL": "CUSTOM_LOG_LEVEL", }, ) @patch("opentelemetry.sdk._configuration._get_log_level", return_value=39) @@ -842,7 +841,6 @@ def test_logging_init_exporter_level_under(self, log_level_mock): _init_logging( {"otlp": DummyOTLPLogExporter}, resource=resource, - setup_logging_handler=False, ) self.assertEqual(self.set_provider_mock.call_count, 1) provider = self.set_provider_mock.call_args[0][0] @@ -852,12 +850,13 @@ def test_logging_init_exporter_level_under(self, log_level_mock): provider.resource.attributes.get("service.name"), "otlp-service", ) - self.assertIsInstance(provider.processor, DummyLogRecordProcessor) + self.assertEqual(len(provider.processors), 1) + self.assertIsInstance(provider.processors[0], DummyLogRecordProcessor) self.assertIsInstance( - provider.processor.exporter, DummyOTLPLogExporter + provider.processors[0].exporter, DummyOTLPLogExporter ) getLogger(__name__).error("hello") - self.assertFalse(provider.processor.exporter.export_called) + self.assertTrue(provider.processors[0].exporter.export_called) root_logger = getLogger() self.assertEqual(root_logger.level, WARNING) handler_present = False @@ -871,12 +870,12 @@ def test_logging_init_exporter_level_under(self, log_level_mock): environ, { "OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service", - "OTEL_PYTHON_LOG_LEVEL": "CUSTOM_LOG_LEVEL", + "OTEL_PYTHON_LOG_HANDLER_LEVEL": "CUSTOM_LOG_LEVEL", }, clear=True, ) - @patch("opentelemetry.sdk._configuration._get_log_level", return_value=39) - def test_logging_init_exporter_level_under(self, log_level_mock): + @patch("opentelemetry.sdk._configuration._get_log_level", return_value=41) + def test_logging_init_exporter_level_over(self, log_level_mock): resource = Resource.create({}) _init_logging( {"otlp": DummyOTLPLogExporter}, @@ -890,26 +889,25 @@ def test_logging_init_exporter_level_under(self, log_level_mock): provider.resource.attributes.get("service.name"), "otlp-service", ) - self.assertIsInstance(provider.processor, DummyLogRecordProcessor) + self.assertEqual(len(provider.processors), 1) + self.assertIsInstance(provider.processors[0], DummyLogRecordProcessor) self.assertIsInstance( - provider.processor.exporter, DummyOTLPLogExporter + provider.processors[0].exporter, DummyOTLPLogExporter ) getLogger(__name__).error("hello") - self.assertFalse(provider.processor.exporter.export_called) + self.assertFalse(provider.processors[0].exporter.export_called) root_logger = getLogger() handler_present = False for handler in root_logger.handlers: if isinstance(handler, LoggingHandler): handler_present = True - self.assertEqual(handler.level, 39) + self.assertEqual(handler.level, 41) self.assertTrue(handler_present) @patch.dict( environ, { "OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service", - "OTEL_PYTHON_LOG_LEVEL": "CUSTOM_LOG_LEVEL", - "OTEL_PYTHON_LOG_HANDLER_LEVEL": "CUSTOM_LOG_LEVEL", "OTEL_PYTHON_LOG_FORMAT": CUSTOM_LOG_FORMAT, }, ) @@ -927,12 +925,13 @@ def test_logging_init_exporter_format(self): provider.resource.attributes.get("service.name"), "otlp-service", ) - self.assertIsInstance(provider.processor, DummyLogRecordProcessor) + self.assertEqual(len(provider.processors), 1) + self.assertIsInstance(provider.processors[0], DummyLogRecordProcessor) self.assertIsInstance( - provider.processor.exporter, DummyOTLPLogExporter + provider.processors[0].exporter, DummyOTLPLogExporter ) getLogger(__name__).error("hello") - self.assertFalse(provider.processor.exporter.export_called) + self.assertTrue(provider.processors[0].exporter.export_called) root_logger = getLogger() self.assertEqual(root_logger.level, WARNING) handler_present = False @@ -984,31 +983,45 @@ def test_logging_init_exporter_without_handler_setup(self): def test_otel_log_level_by_name_default(self): self.assertEqual(_get_log_level(), NOTSET) - @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": "NOTSET "}, clear=True) + @patch.dict( + environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": "NOTSET "}, clear=True + ) def test_otel_log_level_by_name_notset(self): self.assertEqual(_get_log_level(), NOTSET) - @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " DeBug "}, clear=True) + @patch.dict( + environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " DeBug "}, clear=True + ) def test_otel_log_level_by_name_debug(self): self.assertEqual(_get_log_level(), DEBUG) - @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " info "}, clear=True) + @patch.dict( + environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " info "}, clear=True + ) def test_otel_log_level_by_name_info(self): self.assertEqual(_get_log_level(), INFO) - @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " warn"}, clear=True) + @patch.dict( + environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " warn"}, clear=True + ) def test_otel_log_level_by_name_warn(self): self.assertEqual(_get_log_level(), WARNING) - @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " warnING "}, clear=True) + @patch.dict( + environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " warnING "}, clear=True + ) def test_otel_log_level_by_name_warning(self): self.assertEqual(_get_log_level(), WARNING) - @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": " eRroR"}, clear=True) + @patch.dict( + environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": " eRroR"}, clear=True + ) def test_otel_log_level_by_name_error(self): self.assertEqual(_get_log_level(), ERROR) - @patch.dict(environ, {"OTEL_PYTHON_LOG_LEVEL": "foobar"}, clear=True) + @patch.dict( + environ, {"OTEL_PYTHON_LOG_HANDLER_LEVEL": "foobar"}, clear=True + ) def test_otel_log_level_by_name_invalid(self): self.assertEqual(_get_log_level(), NOTSET) @@ -1340,60 +1353,6 @@ def test_dictConfig_preserves_otel_handler(self): "Should still have exactly one OpenTelemetry LoggingHandler", ) - def test_basicConfig_works_with_otel_handler(self): - with ClearLoggingHandlers(): - _init_logging( - {"otlp": DummyOTLPLogExporter}, - Resource.create({}), - setup_logging_handler=True, - ) - - logging.basicConfig(level=logging.INFO) - - root_logger = logging.getLogger() - stream_handlers = [ - h - for h in root_logger.handlers - if isinstance(h, logging.StreamHandler) - ] - self.assertEqual( - len(stream_handlers), - 1, - "basicConfig should add a StreamHandler even when OTel handler exists", - ) - - def test_basicConfig_preserves_otel_handler(self): - with ClearLoggingHandlers(): - _init_logging( - {"otlp": DummyOTLPLogExporter}, - Resource.create({}), - setup_logging_handler=True, - ) - - root_logger = logging.getLogger() - self.assertEqual( - len(root_logger.handlers), - 1, - "Should be exactly one OpenTelemetry LoggingHandler", - ) - handler = root_logger.handlers[0] - self.assertIsInstance(handler, LoggingHandler) - - logging.basicConfig() - - self.assertGreater(len(root_logger.handlers), 1) - - logging_handlers = [ - h - for h in root_logger.handlers - if isinstance(h, LoggingHandler) - ] - self.assertEqual( - len(logging_handlers), - 1, - "Should still have exactly one OpenTelemetry LoggingHandler", - ) - class TestMetricsInit(TestCase): def setUp(self): @@ -1671,7 +1630,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): logging.config.fileConfig = self.original_file_config -class TestClearLoggingHandlers(TestCase): +class TestResetGlobalLoggingState(TestCase): def test_preserves_handlers(self): root_logger = getLogger() initial_handlers = root_logger.handlers[:]