From b1781015b94614c0e58fd1100231caf9d0be564b Mon Sep 17 00:00:00 2001 From: Daigo Yamashita Date: Tue, 2 Jun 2026 19:51:50 +0900 Subject: [PATCH] fix(logging): preserve log messages with the literal string "None" `_format_and_parse_message` treated any record whose formatted output equaled the string "None" as empty, returning `None` (or omitting the `message` field when `json_fields` were present). This also dropped a legitimate user message of the literal string "None" (e.g. `logging.getLogger().info("None")`). The empty-content case is meant to cover a record whose `msg` is the Python object `None`, which `logging.Formatter` renders as "None". Detect that from `record.msg is None` directly so the literal string "None" is preserved. Fixes #17339 Co-Authored-By: Claude Opus 4.8 --- .../cloud/logging_v2/handlers/handlers.py | 9 ++++-- .../tests/unit/handlers/test_handlers.py | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/google-cloud-logging/google/cloud/logging_v2/handlers/handlers.py b/packages/google-cloud-logging/google/cloud/logging_v2/handlers/handlers.py index 8cab7d1e3f0c..280ce319fcf1 100644 --- a/packages/google-cloud-logging/google/cloud/logging_v2/handlers/handlers.py +++ b/packages/google-cloud-logging/google/cloud/logging_v2/handlers/handlers.py @@ -281,14 +281,19 @@ def _format_and_parse_message(record, formatter_handler): except (json.decoder.JSONDecodeError, IndexError): # log string is not valid json pass + # A record whose ``msg`` is the Python object ``None`` is treated as + # having no content. ``logging.Formatter`` renders such a record as the + # string ``"None"``; detect that case from ``record.msg`` directly so that + # a legitimate user message of the literal string ``"None"`` is preserved. + is_empty_message = record.msg is None and message == "None" # if json_fields was set, create a dictionary using that if passed_json_fields and isinstance(passed_json_fields, collections.abc.Mapping): passed_json_fields = passed_json_fields.copy() - if message != "None": + if not is_empty_message: passed_json_fields["message"] = message return passed_json_fields # if formatted message contains no content, return None - return message if message != "None" else None + return None if is_empty_message else message def setup_logging( diff --git a/packages/google-cloud-logging/tests/unit/handlers/test_handlers.py b/packages/google-cloud-logging/tests/unit/handlers/test_handlers.py index 4b3e09cdd629..b7e719fdff88 100644 --- a/packages/google-cloud-logging/tests/unit/handlers/test_handlers.py +++ b/packages/google-cloud-logging/tests/unit/handlers/test_handlers.py @@ -922,6 +922,34 @@ def test_none(self): result = _format_and_parse_message(record, handler) self.assertEqual(result, None) + def test_literal_none_string_preserved(self): + """ + A literal string message of "None" should be preserved, not dropped. + Only a record whose msg is the Python object None is treated as empty. + """ + from google.cloud.logging_v2.handlers.handlers import _format_and_parse_message + + message = "None" + record = logging.LogRecord("logname", None, None, None, message, None, None) + handler = logging.StreamHandler() + result = _format_and_parse_message(record, handler) + self.assertEqual(result, "None") + + def test_literal_none_string_preserved_with_json_fields(self): + """ + A literal string message of "None" should populate the message field + even when json_fields are present. + """ + from google.cloud.logging_v2.handlers.handlers import _format_and_parse_message + + message = "None" + json_fields = {"key": "val"} + record = logging.LogRecord("logname", None, None, None, message, None, None) + setattr(record, "json_fields", json_fields) + handler = logging.StreamHandler() + result = _format_and_parse_message(record, handler) + self.assertEqual(result, {"message": "None", "key": "val"}) + def test_none_formatted(self): """ None messages with formatting rules should return formatted string