From 097898feaf5b09da03597d220307e28e58757dc9 Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Balusu Date: Tue, 24 Mar 2026 22:10:32 -0400 Subject: [PATCH 1/2] fix: map Python CRITICAL log level to OTel FATAL severity text The OTel specification defines the severity text for the highest severity range as "FATAL", but the Python SDK was passing through Python's native "CRITICAL" level name unchanged. This is the same class of bug that was previously fixed for WARNING -> WARN in #3566. Fixes #4984 --- CHANGELOG.md | 2 ++ .../opentelemetry/sdk/_logs/_internal/__init__.py | 14 ++++++++++---- opentelemetry-sdk/tests/logs/test_export.py | 8 ++++---- opentelemetry-sdk/tests/logs/test_handler.py | 4 ++-- .../tests/logs/test_multi_log_processor.py | 4 ++-- 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02456fe4772..5a35f9d7639 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-sdk`: Add `create_resource` and `create_propagator`/`configure_propagator` to declarative file configuration, enabling Resource and propagator instantiation from config files without reading env vars ([#4979](https://github.com/open-telemetry/opentelemetry-python/pull/4979)) +- `opentelemetry-sdk`: Map Python `CRITICAL` log level to OTel `FATAL` severity text per the specification + ([#4984](https://github.com/open-telemetry/opentelemetry-python/issues/4984)) - `opentelemetry-sdk`: Add file configuration support with YAML/JSON loading, environment variable substitution, and schema validation against the vendored OTel config JSON schema ([#4898](https://github.com/open-telemetry/opentelemetry-python/pull/4898)) - Fix intermittent CI failures in `getting-started` and `tracecontext` jobs caused by GitHub git CDN SHA propagation lag by installing contrib packages from the already-checked-out local copy instead of a second git clone diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 9029f867a7e..10e264cf0a2 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -592,10 +592,16 @@ def _translate(self, record: logging.LogRecord) -> LogRecord: else: body = record.getMessage() - # related to https://github.com/open-telemetry/opentelemetry-python/issues/3548 - # Severity Text = WARN as defined in https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#displaying-severity. - level_name = ( - "WARN" if record.levelname == "WARNING" else record.levelname + # Map Python log level names to OTel severity text as defined in + # https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#displaying-severity + # Python "WARNING" -> OTel "WARN" (see #3548) + # Python "CRITICAL" -> OTel "FATAL" (see #4984) + _PYTHON_TO_OTEL_SEVERITY_TEXT = { + "WARNING": "WARN", + "CRITICAL": "FATAL", + } + level_name = _PYTHON_TO_OTEL_SEVERITY_TEXT.get( + record.levelname, record.levelname ) return LogRecord( diff --git a/opentelemetry-sdk/tests/logs/test_export.py b/opentelemetry-sdk/tests/logs/test_export.py index 12909b2e225..09851365c91 100644 --- a/opentelemetry-sdk/tests/logs/test_export.py +++ b/opentelemetry-sdk/tests/logs/test_export.py @@ -161,7 +161,7 @@ def test_simple_log_record_processor_custom_level(self): SeverityNumber.ERROR, ) self.assertEqual(fatal_log_record.log_record.body, "Critical message") - self.assertEqual(fatal_log_record.log_record.severity_text, "CRITICAL") + self.assertEqual(fatal_log_record.log_record.severity_text, "FATAL") self.assertEqual( fatal_log_record.log_record.severity_number, SeverityNumber.FATAL ) @@ -216,7 +216,7 @@ def test_simple_log_record_processor_trace_correlation(self): self.assertEqual( sdk_record.log_record.body, "Critical message within span" ) - self.assertEqual(sdk_record.log_record.severity_text, "CRITICAL") + self.assertEqual(sdk_record.log_record.severity_text, "FATAL") self.assertEqual( sdk_record.log_record.severity_number, SeverityNumber.FATAL ) @@ -290,7 +290,7 @@ def test_simple_log_record_processor_different_msg_types(self): ("Very high rise in temperatures across the globe", "ERROR"), ( "Temperature hits high 420 C in Hyderabad", - "CRITICAL", + "FATAL", ), (["list", "of", "strings"], "WARN"), ({"key": "value"}, "ERROR"), @@ -386,7 +386,7 @@ def test_simple_log_record_processor_different_msg_types_with_formatter( ), ( "different_msg_types - CRITICAL - Temperature hits high 420 C in Hyderabad", - "CRITICAL", + "FATAL", ), ( "different_msg_types - WARNING - ['list', 'of', 'strings']", diff --git a/opentelemetry-sdk/tests/logs/test_handler.py b/opentelemetry-sdk/tests/logs/test_handler.py index 37de01508eb..b3b8b47d16a 100644 --- a/opentelemetry-sdk/tests/logs/test_handler.py +++ b/opentelemetry-sdk/tests/logs/test_handler.py @@ -341,7 +341,7 @@ def test_log_record_trace_correlation(self): record.log_record.body, "Critical message within span", ) - self.assertEqual(record.log_record.severity_text, "CRITICAL") + self.assertEqual(record.log_record.severity_text, "FATAL") self.assertEqual( record.log_record.severity_number, SeverityNumber.FATAL, @@ -374,7 +374,7 @@ def test_log_record_trace_correlation_deprecated(self): self.assertEqual( record.log_record.body, "Critical message within span" ) - self.assertEqual(record.log_record.severity_text, "CRITICAL") + self.assertEqual(record.log_record.severity_text, "FATAL") self.assertEqual( record.log_record.severity_number, SeverityNumber.FATAL ) diff --git a/opentelemetry-sdk/tests/logs/test_multi_log_processor.py b/opentelemetry-sdk/tests/logs/test_multi_log_processor.py index c35ba120e37..05fc0fe03d8 100644 --- a/opentelemetry-sdk/tests/logs/test_multi_log_processor.py +++ b/opentelemetry-sdk/tests/logs/test_multi_log_processor.py @@ -94,9 +94,9 @@ def test_log_record_processor(self): provider.add_log_record_processor(processor2) with self.assertLogs(level=logging.CRITICAL): logger.critical("Something disastrous") - expected_list_1.append(("Something disastrous", "CRITICAL")) + expected_list_1.append(("Something disastrous", "FATAL")) - expected_list_2 = [("Something disastrous", "CRITICAL")] + expected_list_2 = [("Something disastrous", "FATAL")] self.assertEqual(logs_list_1, expected_list_1) self.assertEqual(logs_list_2, expected_list_2) From 0e9e48dd0c2ee76ce588390021edff7f5adb2c49 Mon Sep 17 00:00:00 2001 From: Kc Balusu Date: Thu, 26 Mar 2026 10:34:30 -0400 Subject: [PATCH 2/2] fix: rename local variable to lowercase to satisfy pylint C0103 --- .../src/opentelemetry/sdk/_logs/_internal/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 10e264cf0a2..888fad65073 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -596,11 +596,11 @@ def _translate(self, record: logging.LogRecord) -> LogRecord: # https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#displaying-severity # Python "WARNING" -> OTel "WARN" (see #3548) # Python "CRITICAL" -> OTel "FATAL" (see #4984) - _PYTHON_TO_OTEL_SEVERITY_TEXT = { + _python_to_otel_severity_text = { "WARNING": "WARN", "CRITICAL": "FATAL", } - level_name = _PYTHON_TO_OTEL_SEVERITY_TEXT.get( + level_name = _python_to_otel_severity_text.get( record.levelname, record.levelname )