|
14 | 14 |
|
15 | 15 | # pylint: disable=protected-access |
16 | 16 |
|
| 17 | +import importlib |
| 18 | +import logging |
17 | 19 | import unittest |
18 | 20 | from unittest.mock import Mock, patch |
19 | 21 |
|
| 22 | +import opentelemetry.sdk._logs._internal as _logs_internal |
20 | 23 | from opentelemetry._logs import LogRecord, SeverityNumber |
21 | 24 | from opentelemetry.attributes import BoundedAttributes |
22 | 25 | from opentelemetry.context import get_current |
|
28 | 31 | ReadWriteLogRecord, |
29 | 32 | ) |
30 | 33 | from opentelemetry.sdk._logs._internal import ( |
| 34 | + _OTEL_LOG_LEVEL_TO_PYTHON, |
31 | 35 | LoggerMetrics, |
32 | 36 | NoOpLogger, |
33 | 37 | SynchronousMultiLogRecordProcessor, |
34 | 38 | _disable_logger_configurator, |
35 | 39 | _LoggerConfig, |
36 | 40 | _RuleBasedLoggerConfigurator, |
37 | 41 | ) |
38 | | -from opentelemetry.sdk.environment_variables import OTEL_SDK_DISABLED |
| 42 | +from opentelemetry.sdk.environment_variables import ( |
| 43 | + OTEL_LOG_LEVEL, |
| 44 | + OTEL_SDK_DISABLED, |
| 45 | +) |
39 | 46 | from opentelemetry.sdk.resources import Resource |
40 | 47 | from opentelemetry.sdk.util.instrumentation import ( |
41 | 48 | InstrumentationScope, |
@@ -463,3 +470,84 @@ def test_emit_readwrite_logrecord_uses_exception(self): |
463 | 470 | self.assertEqual( |
464 | 471 | attributes[exception_attributes.EXCEPTION_TYPE], "RuntimeError" |
465 | 472 | ) |
| 473 | + |
| 474 | + |
| 475 | +class TestOtelLogLevelEnvVar(unittest.TestCase): |
| 476 | + """Tests for OTEL_LOG_LEVEL → SDK internal logger level.""" |
| 477 | + |
| 478 | + def setUp(self): |
| 479 | + self._sdk_logger = logging.getLogger("opentelemetry.sdk") |
| 480 | + |
| 481 | + def tearDown(self): |
| 482 | + importlib.reload(_logs_internal) |
| 483 | + |
| 484 | + def test_otel_log_level_to_python_mapping_accepted_values(self): |
| 485 | + expected_keys = { |
| 486 | + "debug", |
| 487 | + "info", |
| 488 | + "warn", |
| 489 | + "warning", |
| 490 | + "error", |
| 491 | + "critical", |
| 492 | + } |
| 493 | + self.assertEqual(set(_OTEL_LOG_LEVEL_TO_PYTHON.keys()), expected_keys) |
| 494 | + |
| 495 | + @patch.dict("os.environ", {OTEL_LOG_LEVEL: ""}) |
| 496 | + def test_default_level_is_info(self): |
| 497 | + importlib.reload(_logs_internal) |
| 498 | + self.assertEqual(self._sdk_logger.level, logging.INFO) |
| 499 | + |
| 500 | + def test_invalid_value_warns_and_defaults_to_info(self): |
| 501 | + # "trace", "verbose", "none" are valid in other SDKs but not accepted here |
| 502 | + for invalid in ("INVALID", "trace", "verbose", "none", "0"): |
| 503 | + with self.subTest(invalid=invalid): |
| 504 | + with patch.dict("os.environ", {OTEL_LOG_LEVEL: invalid}): |
| 505 | + with self.assertLogs( |
| 506 | + "opentelemetry.sdk._logs._internal", |
| 507 | + level=logging.WARNING, |
| 508 | + ): |
| 509 | + importlib.reload(_logs_internal) |
| 510 | + self.assertEqual(self._sdk_logger.level, logging.INFO) |
| 511 | + |
| 512 | + def test_case_insensitive(self): |
| 513 | + for env_value, expected_level in ( |
| 514 | + ("DEBUG", logging.DEBUG), |
| 515 | + ("WARN", logging.WARNING), |
| 516 | + ("Warning", logging.WARNING), |
| 517 | + ("cRiTiCaL", logging.CRITICAL), |
| 518 | + ): |
| 519 | + with self.subTest(env_value=env_value): |
| 520 | + with patch.dict("os.environ", {OTEL_LOG_LEVEL: env_value}): |
| 521 | + importlib.reload(_logs_internal) |
| 522 | + self.assertEqual(self._sdk_logger.level, expected_level) |
| 523 | + |
| 524 | + @patch.dict("os.environ", {OTEL_LOG_LEVEL: "critical"}) |
| 525 | + def test_level_propagates_to_child_loggers(self): |
| 526 | + importlib.reload(_logs_internal) |
| 527 | + self.assertEqual( |
| 528 | + self._sdk_logger.getChild("trace").getEffectiveLevel(), |
| 529 | + logging.CRITICAL, |
| 530 | + ) |
| 531 | + self.assertEqual( |
| 532 | + self._sdk_logger.getChild("metrics").getEffectiveLevel(), |
| 533 | + logging.CRITICAL, |
| 534 | + ) |
| 535 | + self.assertEqual( |
| 536 | + self._sdk_logger.getChild("logs").getEffectiveLevel(), |
| 537 | + logging.CRITICAL, |
| 538 | + ) |
| 539 | + |
| 540 | + def test_all_valid_values_map_to_correct_level(self): |
| 541 | + cases = [ |
| 542 | + ("debug", logging.DEBUG), |
| 543 | + ("info", logging.INFO), |
| 544 | + ("warn", logging.WARNING), |
| 545 | + ("warning", logging.WARNING), |
| 546 | + ("error", logging.ERROR), |
| 547 | + ("critical", logging.CRITICAL), |
| 548 | + ] |
| 549 | + for env_value, expected_level in cases: |
| 550 | + with self.subTest(env_value=env_value): |
| 551 | + with patch.dict("os.environ", {OTEL_LOG_LEVEL: env_value}): |
| 552 | + importlib.reload(_logs_internal) |
| 553 | + self.assertEqual(self._sdk_logger.level, expected_level) |
0 commit comments