From 4b6716b05638349630462695b9e2afdf2828ea97 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 24 Jun 2021 10:41:14 -0700 Subject: [PATCH 1/9] attempt to parse string message as json --- google/cloud/logging_v2/handlers/handlers.py | 25 +++++++++++------ .../logging_v2/handlers/structured_log.py | 27 ++++++++++++++----- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/google/cloud/logging_v2/handlers/handlers.py b/google/cloud/logging_v2/handlers/handlers.py index b554a6fdb..c83c9bdd4 100644 --- a/google/cloud/logging_v2/handlers/handlers.py +++ b/google/cloud/logging_v2/handlers/handlers.py @@ -192,20 +192,29 @@ def emit(self, record): """ resource = record._resource or self.resource labels = record._labels - message = None - if isinstance(record.msg, collections.abc.Mapping): - # if input is a dictionary, pass as-is for structured logging - message = record.msg - elif record.msg: - # otherwise, format message string based on superclass - message = super(CloudLoggingHandler, self).format(record) + + if isinstance(record.msg, str): + # format message string based on superclass + parsed_message = super(StructuredLogHandler, self).format(record) + try: + # attempt to parse encoded json into dictionary + if message[0] = '{': + json_message = json.loads(message) + if isinstance(json_message, collections.abc.Mapping): + parsed_message = json_message + except (json.decoder.JSONDecodeError, IndexError): + pass + else: + # if non-string, pass along as-is + parsed_message = record.msg + if resource.type == _GAE_RESOURCE_TYPE and record._trace is not None: # add GAE-specific label labels = {_GAE_TRACE_ID_LABEL: record._trace, **(labels or {})} # send off request self.transport.send( record, - message, + parsed_message, resource=resource, labels=labels, trace=record._trace, diff --git a/google/cloud/logging_v2/handlers/structured_log.py b/google/cloud/logging_v2/handlers/structured_log.py index c981a1f27..571f952aa 100644 --- a/google/cloud/logging_v2/handlers/structured_log.py +++ b/google/cloud/logging_v2/handlers/structured_log.py @@ -62,16 +62,31 @@ def format(self, record): str: A JSON string formatted for GCP structured logging. """ payload = None - if isinstance(record.msg, collections.abc.Mapping): + + + if isinstance(record.msg, str): + # format message string based on superclass + parsed_message = super(StructuredLogHandler, self).format(record) + try: + if message[0] = '{': + # attempt to parse encoded json into dictionary + json_message = json.loads(message) + if isinstance(json_message, collections.abc.Mapping): + parsed_message = json_message + except (json.decoder.JSONDecodeError, IndexError): + pass + else: + # if non-string, pass along as-is + parsed_message = record.msg + + if isinstance(message, collections.abc.Mapping): # if input is a dictionary, encode it as a json string - encoded_msg = json.dumps(record.msg, ensure_ascii=False) + encoded_msg = json.dumps(message, ensure_ascii=False) # strip out open and close parentheses payload = encoded_msg.lstrip("{").rstrip("}") + "," - elif record.msg: - # otherwise, format based on superclass - super_message = super(StructuredLogHandler, self).format(record) + elif message: # properly break any formatting in string to make it json safe - encoded_message = json.dumps(super_message, ensure_ascii=False) + encoded_message = json.dumps(message, ensure_ascii=False) payload = '"message": {},'.format(encoded_message) record._payload_str = payload or "" From fb809c977aba7a11bc545cb7db54453f5b81cca6 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 24 Jun 2021 11:35:34 -0700 Subject: [PATCH 2/9] added helper function --- google/cloud/logging_v2/handlers/handlers.py | 41 +++++++++++-------- .../logging_v2/handlers/structured_log.py | 18 +------- 2 files changed, 27 insertions(+), 32 deletions(-) diff --git a/google/cloud/logging_v2/handlers/handlers.py b/google/cloud/logging_v2/handlers/handlers.py index c83c9bdd4..4589c2ac2 100644 --- a/google/cloud/logging_v2/handlers/handlers.py +++ b/google/cloud/logging_v2/handlers/handlers.py @@ -192,21 +192,7 @@ def emit(self, record): """ resource = record._resource or self.resource labels = record._labels - - if isinstance(record.msg, str): - # format message string based on superclass - parsed_message = super(StructuredLogHandler, self).format(record) - try: - # attempt to parse encoded json into dictionary - if message[0] = '{': - json_message = json.loads(message) - if isinstance(json_message, collections.abc.Mapping): - parsed_message = json_message - except (json.decoder.JSONDecodeError, IndexError): - pass - else: - # if non-string, pass along as-is - parsed_message = record.msg + message = _format_and_parse_message(record, self) if resource.type == _GAE_RESOURCE_TYPE and record._trace is not None: # add GAE-specific label @@ -214,7 +200,7 @@ def emit(self, record): # send off request self.transport.send( record, - parsed_message, + message, resource=resource, labels=labels, trace=record._trace, @@ -223,6 +209,29 @@ def emit(self, record): source_location=record._source_location, ) +def _format_and_parse_message(record, formatter_handler): + """ + Helper function to apply formatting to a LogRecord message, + and attempt to parse encoded JSON into a dictionary object. + + Args: + record (logging.LogRecord): The record object representing the log + formatter_handler (logging.Handler): The handler used to format the log + """ + message = record.msg + if isinstance(message, str): + # format message string based on superclass + message = formatter_handler.format(record) + try: + # attempt to parse encoded json into dictionary + if message[0] == '{': + json_message = json.loads(message) + if isinstance(json_message, collections.abc.Mapping): + message = json_message + except (json.decoder.JSONDecodeError, IndexError): + pass + return message + def setup_logging( handler, *, excluded_loggers=EXCLUDED_LOGGER_DEFAULTS, log_level=logging.INFO diff --git a/google/cloud/logging_v2/handlers/structured_log.py b/google/cloud/logging_v2/handlers/structured_log.py index 571f952aa..6ec83fade 100644 --- a/google/cloud/logging_v2/handlers/structured_log.py +++ b/google/cloud/logging_v2/handlers/structured_log.py @@ -19,6 +19,7 @@ import logging.handlers from google.cloud.logging_v2.handlers.handlers import CloudLoggingFilter +from google.cloud.logging_v2.handlers.handlers import _format_and_parse_message GCP_FORMAT = ( "{%(_payload_str)s" @@ -62,22 +63,7 @@ def format(self, record): str: A JSON string formatted for GCP structured logging. """ payload = None - - - if isinstance(record.msg, str): - # format message string based on superclass - parsed_message = super(StructuredLogHandler, self).format(record) - try: - if message[0] = '{': - # attempt to parse encoded json into dictionary - json_message = json.loads(message) - if isinstance(json_message, collections.abc.Mapping): - parsed_message = json_message - except (json.decoder.JSONDecodeError, IndexError): - pass - else: - # if non-string, pass along as-is - parsed_message = record.msg + message = _format_and_parse_message(record, super(StructuredLogHandler, self)) if isinstance(message, collections.abc.Mapping): # if input is a dictionary, encode it as a json string From 2ffd7fa4064fc342a2f6992f304a061092b19157 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 24 Jun 2021 11:35:44 -0700 Subject: [PATCH 3/9] added unit tests --- tests/unit/handlers/test_handlers.py | 34 ++++++++++++++++++++++ tests/unit/handlers/test_structured_log.py | 21 +++++++++++++ 2 files changed, 55 insertions(+) diff --git a/tests/unit/handlers/test_handlers.py b/tests/unit/handlers/test_handlers.py index 74f5c6dd8..d3264771d 100644 --- a/tests/unit/handlers/test_handlers.py +++ b/tests/unit/handlers/test_handlers.py @@ -401,6 +401,40 @@ def test_emit_with_custom_formatter(self): ), ) + def test_emit_with_encoded_json(self): + """ + Handler should respect custom formatters attached + """ + from google.cloud.logging_v2.logger import _GLOBAL_RESOURCE + + client = _Client(self.PROJECT) + handler = self._make_one( + client, transport=_Transport, resource=_GLOBAL_RESOURCE, + ) + logFormatter = logging.Formatter(fmt='{ "x" : "%(name)s" }') + handler.setFormatter(logFormatter) + logname = "logname" + expected_result = {"x": logname} + expected_label = {"python_logger": logname} + record = logging.LogRecord( + logname, logging.INFO, None, None, "", None, None + ) + handler.handle(record) + + self.assertEqual( + handler.transport.send_called_with, + ( + record, + expected_result, + _GLOBAL_RESOURCE, + expected_label, + None, + None, + None, + None, + ), + ) + def test_format_with_arguments(self): """ Handler should support format string arguments diff --git a/tests/unit/handlers/test_structured_log.py b/tests/unit/handlers/test_structured_log.py index dd09edbbf..b37559c68 100644 --- a/tests/unit/handlers/test_structured_log.py +++ b/tests/unit/handlers/test_structured_log.py @@ -152,6 +152,27 @@ def test_format_with_custom_formatter(self): handler.filter(record) result = handler.format(record) self.assertIn(expected_result, result) + self.assertIn("message", result) + + def test_encoded_json(self): + """ + Handler should parse json encoded as a string + """ + import logging + + handler = self._make_one() + logFormatter = logging.Formatter(fmt='{ "name" : "%(name)s" }') + handler.setFormatter(logFormatter) + message = "" + expected_result = '"name": "logname"' + record = logging.LogRecord( + "logname", logging.INFO, None, None, message, None, None, + ) + record.created = None + handler.filter(record) + result = handler.format(record) + self.assertIn(expected_result, result) + self.assertNotIn("message", result) def test_format_with_arguments(self): """ From b19f9bc35e076be6ac11b7182609054f0e3e1277 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 24 Jun 2021 11:39:52 -0700 Subject: [PATCH 4/9] added more unit tests --- tests/unit/handlers/test_handlers.py | 34 +++++++++++++++++++++- tests/unit/handlers/test_structured_log.py | 18 ++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/tests/unit/handlers/test_handlers.py b/tests/unit/handlers/test_handlers.py index d3264771d..6c86a044a 100644 --- a/tests/unit/handlers/test_handlers.py +++ b/tests/unit/handlers/test_handlers.py @@ -401,9 +401,41 @@ def test_emit_with_custom_formatter(self): ), ) + def test_emit_dict(self): + """ + Handler should support logging dictionaries + """ + from google.cloud.logging_v2.logger import _GLOBAL_RESOURCE + + client = _Client(self.PROJECT) + handler = self._make_one( + client, transport=_Transport, resource=_GLOBAL_RESOURCE, + ) + message = {"x": "test"} + logname = "logname" + expected_label = {"python_logger": logname} + record = logging.LogRecord( + logname, logging.INFO, None, None, message, None, None + ) + handler.handle(record) + + self.assertEqual( + handler.transport.send_called_with, + ( + record, + message, + _GLOBAL_RESOURCE, + expected_label, + None, + None, + None, + None, + ), + ) + def test_emit_with_encoded_json(self): """ - Handler should respect custom formatters attached + Handler should parse json encoded as a string """ from google.cloud.logging_v2.logger import _GLOBAL_RESOURCE diff --git a/tests/unit/handlers/test_structured_log.py b/tests/unit/handlers/test_structured_log.py index b37559c68..f6be37b0a 100644 --- a/tests/unit/handlers/test_structured_log.py +++ b/tests/unit/handlers/test_structured_log.py @@ -154,6 +154,24 @@ def test_format_with_custom_formatter(self): self.assertIn(expected_result, result) self.assertIn("message", result) + def test_dict(self): + """ + Handler should parse json encoded as a string + """ + import logging + + handler = self._make_one() + message = {"x": "test"} + expected_result = '"x": "test"' + record = logging.LogRecord( + "logname", logging.INFO, None, None, message, None, None, + ) + record.created = None + handler.filter(record) + result = handler.format(record) + self.assertIn(expected_result, result) + self.assertNotIn("message", result) + def test_encoded_json(self): """ Handler should parse json encoded as a string From fc693f49bc5fb694437d90170d023feebc37335d Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 24 Jun 2021 12:00:57 -0700 Subject: [PATCH 5/9] restructured _format_and_parse_message --- google/cloud/logging_v2/handlers/handlers.py | 25 ++++++++++---------- tests/unit/handlers/test_handlers.py | 2 +- tests/unit/handlers/test_structured_log.py | 3 +-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/google/cloud/logging_v2/handlers/handlers.py b/google/cloud/logging_v2/handlers/handlers.py index 4589c2ac2..661bead19 100644 --- a/google/cloud/logging_v2/handlers/handlers.py +++ b/google/cloud/logging_v2/handlers/handlers.py @@ -218,18 +218,19 @@ def _format_and_parse_message(record, formatter_handler): record (logging.LogRecord): The record object representing the log formatter_handler (logging.Handler): The handler used to format the log """ - message = record.msg - if isinstance(message, str): - # format message string based on superclass - message = formatter_handler.format(record) - try: - # attempt to parse encoded json into dictionary - if message[0] == '{': - json_message = json.loads(message) - if isinstance(json_message, collections.abc.Mapping): - message = json_message - except (json.decoder.JSONDecodeError, IndexError): - pass + # if message is a dictionary, return as-is + if isinstance(record.msg, collections.abc.Mapping): + return record.msg + # format message string based on superclass + message = formatter_handler.format(record) + try: + # attempt to parse encoded json into dictionary + if message[0] == '{': + json_message = json.loads(message) + if isinstance(json_message, collections.abc.Mapping): + message = json_message + except (json.decoder.JSONDecodeError, IndexError): + pass return message diff --git a/tests/unit/handlers/test_handlers.py b/tests/unit/handlers/test_handlers.py index 6c86a044a..9d18bb68c 100644 --- a/tests/unit/handlers/test_handlers.py +++ b/tests/unit/handlers/test_handlers.py @@ -449,7 +449,7 @@ def test_emit_with_encoded_json(self): expected_result = {"x": logname} expected_label = {"python_logger": logname} record = logging.LogRecord( - logname, logging.INFO, None, None, "", None, None + logname, logging.INFO, None, None, None, None, None ) handler.handle(record) diff --git a/tests/unit/handlers/test_structured_log.py b/tests/unit/handlers/test_structured_log.py index f6be37b0a..8e25ac8ef 100644 --- a/tests/unit/handlers/test_structured_log.py +++ b/tests/unit/handlers/test_structured_log.py @@ -181,10 +181,9 @@ def test_encoded_json(self): handler = self._make_one() logFormatter = logging.Formatter(fmt='{ "name" : "%(name)s" }') handler.setFormatter(logFormatter) - message = "" expected_result = '"name": "logname"' record = logging.LogRecord( - "logname", logging.INFO, None, None, message, None, None, + "logname", logging.INFO, None, None, None, None, None, ) record.created = None handler.filter(record) From 18bfa7d499972be978e89e4622c4cd257415dce4 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 24 Jun 2021 12:16:40 -0700 Subject: [PATCH 6/9] support None logs --- google/cloud/logging_v2/handlers/handlers.py | 4 +++- tests/unit/handlers/test_handlers.py | 25 ++++++++++++++++++++ tests/unit/handlers/test_structured_log.py | 3 +++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/google/cloud/logging_v2/handlers/handlers.py b/google/cloud/logging_v2/handlers/handlers.py index 661bead19..aed78b576 100644 --- a/google/cloud/logging_v2/handlers/handlers.py +++ b/google/cloud/logging_v2/handlers/handlers.py @@ -230,8 +230,10 @@ def _format_and_parse_message(record, formatter_handler): if isinstance(json_message, collections.abc.Mapping): message = json_message except (json.decoder.JSONDecodeError, IndexError): + # log string is not valid json pass - return message + # if formatted message contains no content, return None + return message if message != "None" else None def setup_logging( diff --git a/tests/unit/handlers/test_handlers.py b/tests/unit/handlers/test_handlers.py index 9d18bb68c..1941b3d83 100644 --- a/tests/unit/handlers/test_handlers.py +++ b/tests/unit/handlers/test_handlers.py @@ -311,6 +311,31 @@ def test_emit(self): ), ) + def test_emit_minimal(self): + from google.cloud.logging_v2.logger import _GLOBAL_RESOURCE + + client = _Client(self.PROJECT) + handler = self._make_one( + client, transport=_Transport, resource=_GLOBAL_RESOURCE + ) + record = logging.LogRecord( + None, logging.INFO, None, None, None, None, None + ) + handler.handle(record) + self.assertEqual( + handler.transport.send_called_with, + ( + record, + None, + _GLOBAL_RESOURCE, + None, + None, + None, + None, + None, + ), + ) + def test_emit_manual_field_override(self): from google.cloud.logging_v2.logger import _GLOBAL_RESOURCE from google.cloud.logging_v2.resource import Resource diff --git a/tests/unit/handlers/test_structured_log.py b/tests/unit/handlers/test_structured_log.py index 8e25ac8ef..1fc96b23b 100644 --- a/tests/unit/handlers/test_structured_log.py +++ b/tests/unit/handlers/test_structured_log.py @@ -92,13 +92,16 @@ def test_format_minimal(self): record = logging.LogRecord(None, logging.INFO, None, None, None, None, None,) record.created = None expected_payload = { + "severity": "INFO", "logging.googleapis.com/trace": "", + "logging.googleapis.com/spanId": "", "logging.googleapis.com/sourceLocation": {}, "httpRequest": {}, "logging.googleapis.com/labels": {}, } handler.filter(record) result = json.loads(handler.format(record)) + self.assertEqual(set(expected_payload.keys()), set(result.keys())) for (key, value) in expected_payload.items(): self.assertEqual( value, result[key], f"expected_payload[{key}] != result[{key}]" From 8cf6c68827cb3733fd4e5290b15eb0f8b4a20c30 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 24 Jun 2021 12:37:53 -0700 Subject: [PATCH 7/9] added unit tests for _format_and_parse_message --- tests/unit/handlers/test_handlers.py | 116 +++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/tests/unit/handlers/test_handlers.py b/tests/unit/handlers/test_handlers.py index 1941b3d83..02e7de98a 100644 --- a/tests/unit/handlers/test_handlers.py +++ b/tests/unit/handlers/test_handlers.py @@ -516,6 +516,122 @@ def test_format_with_arguments(self): ) + +class TestFormatAndParseMessage(unittest.TestCase): + + def test_none(self): + """ + None messages with no special formatting should return + None after formatting + """ + from google.cloud.logging_v2.handlers.handlers import _format_and_parse_message + message = None + record = logging.LogRecord( + None, None, None, None, message, None, None + ) + handler = logging.StreamHandler() + result = _format_and_parse_message(record, handler) + self.assertEqual(result, None) + + def test_none_formatted(self): + """ + None messages with formatting rules should return formatted string + """ + 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() + formatter = logging.Formatter('name: %(name)s') + handler.setFormatter(formatter) + result = _format_and_parse_message(record, handler) + self.assertEqual(result, "name: logname") + + def test_unformatted_string(self): + """ + Unformated strings should be returned unchanged + """ + from google.cloud.logging_v2.handlers.handlers import _format_and_parse_message + message = '"test"' + record = logging.LogRecord( + "logname", None, None, None, message, None, None + ) + handler = logging.StreamHandler() + result = _format_and_parse_message(record, handler) + self.assertEqual(result, message) + + def test_empty_string(self): + """ + Empty strings should be returned unchanged + """ + from google.cloud.logging_v2.handlers.handlers import _format_and_parse_message + message = "" + record = logging.LogRecord( + "logname", None, None, None, message, None, None + ) + handler = logging.StreamHandler() + result = _format_and_parse_message(record, handler) + self.assertEqual(result, message) + + def test_string_formatted_with_args(self): + """ + string messages should properly apply formatting and arguments + """ + from google.cloud.logging_v2.handlers.handlers import _format_and_parse_message + message = "argument: %s" + arg = "test" + record = logging.LogRecord( + "logname", None, None, None, message, arg, None + ) + handler = logging.StreamHandler() + formatter = logging.Formatter('name: %(name)s :: message: %(message)s') + handler.setFormatter(formatter) + result = _format_and_parse_message(record, handler) + self.assertEqual(result, "name: logname :: message: argument: test") + + def test_dict(self): + """ + dict messages should be unchanged + """ + from google.cloud.logging_v2.handlers.handlers import _format_and_parse_message + message = {"a": "b"} + record = logging.LogRecord( + "logname", None, None, None, message, None, None + ) + handler = logging.StreamHandler() + formatter = logging.Formatter('name: %(name)s') + handler.setFormatter(formatter) + result = _format_and_parse_message(record, handler) + self.assertEqual(result, message) + + def test_string_encoded_dict(self): + """ + dicts should be extracted from string messages + """ + from google.cloud.logging_v2.handlers.handlers import _format_and_parse_message + message = '{ "x": { "y" : "z" } }' + record = logging.LogRecord( + "logname", None, None, None, message, None, None + ) + handler = logging.StreamHandler() + result = _format_and_parse_message(record, handler) + self.assertEqual(result, {"x":{"y":"z"}}) + + def test_broken_encoded_dict(self): + """ + unparseable encoded dicts should be kept as strings + """ + from google.cloud.logging_v2.handlers.handlers import _format_and_parse_message + message = '{ "x": { "y" : ' + record = logging.LogRecord( + "logname", None, None, None, message, None, None + ) + handler = logging.StreamHandler() + result = _format_and_parse_message(record, handler) + self.assertEqual(result, message) + + class TestSetupLogging(unittest.TestCase): def _call_fut(self, handler, excludes=None): from google.cloud.logging.handlers import setup_logging From 72257869f89fff82914a1ee1d7a8d081b908dbf8 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 24 Jun 2021 14:22:19 -0700 Subject: [PATCH 8/9] lint --- google/cloud/logging_v2/handlers/handlers.py | 3 +- tests/environment | 2 +- tests/unit/handlers/test_handlers.py | 71 +++++++------------- 3 files changed, 27 insertions(+), 49 deletions(-) diff --git a/google/cloud/logging_v2/handlers/handlers.py b/google/cloud/logging_v2/handlers/handlers.py index aed78b576..bb04256ff 100644 --- a/google/cloud/logging_v2/handlers/handlers.py +++ b/google/cloud/logging_v2/handlers/handlers.py @@ -209,6 +209,7 @@ def emit(self, record): source_location=record._source_location, ) + def _format_and_parse_message(record, formatter_handler): """ Helper function to apply formatting to a LogRecord message, @@ -225,7 +226,7 @@ def _format_and_parse_message(record, formatter_handler): message = formatter_handler.format(record) try: # attempt to parse encoded json into dictionary - if message[0] == '{': + if message[0] == "{": json_message = json.loads(message) if isinstance(json_message, collections.abc.Mapping): message = json_message diff --git a/tests/environment b/tests/environment index 3b1d17e2e..c83d9e8a5 160000 --- a/tests/environment +++ b/tests/environment @@ -1 +1 @@ -Subproject commit 3b1d17e2ee5da599d72c56daa2e152c2679ed73f +Subproject commit c83d9e8a5cc87f08eeee0e99a7cb12e0cd18c0d0 diff --git a/tests/unit/handlers/test_handlers.py b/tests/unit/handlers/test_handlers.py index 02e7de98a..d36dc8959 100644 --- a/tests/unit/handlers/test_handlers.py +++ b/tests/unit/handlers/test_handlers.py @@ -318,22 +318,11 @@ def test_emit_minimal(self): handler = self._make_one( client, transport=_Transport, resource=_GLOBAL_RESOURCE ) - record = logging.LogRecord( - None, logging.INFO, None, None, None, None, None - ) + record = logging.LogRecord(None, logging.INFO, None, None, None, None, None) handler.handle(record) self.assertEqual( handler.transport.send_called_with, - ( - record, - None, - _GLOBAL_RESOURCE, - None, - None, - None, - None, - None, - ), + (record, None, _GLOBAL_RESOURCE, None, None, None, None, None,), ) def test_emit_manual_field_override(self): @@ -473,9 +462,7 @@ def test_emit_with_encoded_json(self): logname = "logname" expected_result = {"x": logname} expected_label = {"python_logger": logname} - record = logging.LogRecord( - logname, logging.INFO, None, None, None, None, None - ) + record = logging.LogRecord(logname, logging.INFO, None, None, None, None, None) handler.handle(record) self.assertEqual( @@ -516,19 +503,16 @@ def test_format_with_arguments(self): ) - class TestFormatAndParseMessage(unittest.TestCase): - def test_none(self): """ - None messages with no special formatting should return + None messages with no special formatting should return None after formatting """ from google.cloud.logging_v2.handlers.handlers import _format_and_parse_message + message = None - record = logging.LogRecord( - None, None, None, None, message, None, None - ) + record = logging.LogRecord(None, None, None, None, message, None, None) handler = logging.StreamHandler() result = _format_and_parse_message(record, handler) self.assertEqual(result, None) @@ -538,12 +522,11 @@ def test_none_formatted(self): None messages with formatting rules should return formatted string """ from google.cloud.logging_v2.handlers.handlers import _format_and_parse_message + message = None - record = logging.LogRecord( - "logname", None, None, None, message, None, None - ) + record = logging.LogRecord("logname", None, None, None, message, None, None) handler = logging.StreamHandler() - formatter = logging.Formatter('name: %(name)s') + formatter = logging.Formatter("name: %(name)s") handler.setFormatter(formatter) result = _format_and_parse_message(record, handler) self.assertEqual(result, "name: logname") @@ -553,10 +536,9 @@ def test_unformatted_string(self): Unformated strings should be returned unchanged """ from google.cloud.logging_v2.handlers.handlers import _format_and_parse_message + message = '"test"' - record = logging.LogRecord( - "logname", None, None, None, message, None, None - ) + record = logging.LogRecord("logname", None, None, None, message, None, None) handler = logging.StreamHandler() result = _format_and_parse_message(record, handler) self.assertEqual(result, message) @@ -566,10 +548,9 @@ def test_empty_string(self): Empty strings should be returned unchanged """ from google.cloud.logging_v2.handlers.handlers import _format_and_parse_message + message = "" - record = logging.LogRecord( - "logname", None, None, None, message, None, None - ) + record = logging.LogRecord("logname", None, None, None, message, None, None) handler = logging.StreamHandler() result = _format_and_parse_message(record, handler) self.assertEqual(result, message) @@ -579,13 +560,12 @@ def test_string_formatted_with_args(self): string messages should properly apply formatting and arguments """ from google.cloud.logging_v2.handlers.handlers import _format_and_parse_message + message = "argument: %s" arg = "test" - record = logging.LogRecord( - "logname", None, None, None, message, arg, None - ) + record = logging.LogRecord("logname", None, None, None, message, arg, None) handler = logging.StreamHandler() - formatter = logging.Formatter('name: %(name)s :: message: %(message)s') + formatter = logging.Formatter("name: %(name)s :: message: %(message)s") handler.setFormatter(formatter) result = _format_and_parse_message(record, handler) self.assertEqual(result, "name: logname :: message: argument: test") @@ -595,12 +575,11 @@ def test_dict(self): dict messages should be unchanged """ from google.cloud.logging_v2.handlers.handlers import _format_and_parse_message + message = {"a": "b"} - record = logging.LogRecord( - "logname", None, None, None, message, None, None - ) + record = logging.LogRecord("logname", None, None, None, message, None, None) handler = logging.StreamHandler() - formatter = logging.Formatter('name: %(name)s') + formatter = logging.Formatter("name: %(name)s") handler.setFormatter(formatter) result = _format_and_parse_message(record, handler) self.assertEqual(result, message) @@ -610,23 +589,21 @@ def test_string_encoded_dict(self): dicts should be extracted from string messages """ from google.cloud.logging_v2.handlers.handlers import _format_and_parse_message + message = '{ "x": { "y" : "z" } }' - record = logging.LogRecord( - "logname", None, None, None, message, None, None - ) + record = logging.LogRecord("logname", None, None, None, message, None, None) handler = logging.StreamHandler() result = _format_and_parse_message(record, handler) - self.assertEqual(result, {"x":{"y":"z"}}) + self.assertEqual(result, {"x": {"y": "z"}}) def test_broken_encoded_dict(self): """ unparseable encoded dicts should be kept as strings """ from google.cloud.logging_v2.handlers.handlers import _format_and_parse_message + message = '{ "x": { "y" : ' - record = logging.LogRecord( - "logname", None, None, None, message, None, None - ) + record = logging.LogRecord("logname", None, None, None, message, None, None) handler = logging.StreamHandler() result = _format_and_parse_message(record, handler) self.assertEqual(result, message) From 683de39a4bf9c6f3e6055a1fa005f7c4e5538342 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Thu, 24 Jun 2021 15:14:04 -0700 Subject: [PATCH 9/9] added comment --- google/cloud/logging_v2/handlers/handlers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/google/cloud/logging_v2/handlers/handlers.py b/google/cloud/logging_v2/handlers/handlers.py index bb04256ff..8d14852e1 100644 --- a/google/cloud/logging_v2/handlers/handlers.py +++ b/google/cloud/logging_v2/handlers/handlers.py @@ -215,6 +215,8 @@ def _format_and_parse_message(record, formatter_handler): Helper function to apply formatting to a LogRecord message, and attempt to parse encoded JSON into a dictionary object. + Resulting output will be of type (str | dict | None) + Args: record (logging.LogRecord): The record object representing the log formatter_handler (logging.Handler): The handler used to format the log