8000 fix: structured log handler formatting issues by daniel-sanche · Pull Request #319 · googleapis/python-logging · GitHub
[go: up one dir, main page]

Skip to content

fix: structured log handler formatting issues #319

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions google/cloud/logging_v2/handlers/structured_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@

"""Logging handler for printing formatted structured logs to standard output.
"""

import json
import logging.handlers

from google.cloud.logging_v2.handlers.handlers import CloudLoggingFilter

GCP_FORMAT = (
'{"message": "%(_msg_str)s", '
'{"message": %(_formatted_msg)s, '
'"severity": "%(levelname)s", '
'"logging.googleapis.com/labels": %(_labels_str)s, '
'"logging.googleapis.com/trace": "%(_trace_str)s", '
Expand Down Expand Up @@ -50,7 +50,7 @@ def __init__(self, *, labels=None, stream=None, project_id=None):
self.addFilter(log_filter)

# make logs appear in GCP structured logging format
self.formatter = logging.Formatter(GCP_FORMAT)
self._gcp_formatter = logging.Formatter(GCP_FORMAT)

def format(self, record):
"""Format the message into structured log JSON.
Expand All @@ -59,6 +59,12 @@ def format(self, record):
Returns:
str: A JSON string formatted for GKE fluentd.
"""

payload = self.formatter.format(record)
return payload
# let other formatters alter the message
super_payload = None
if record.msg:
super_payload = super(StructuredLogHandler, self).format(record)
# properly break any formatting in string to make it json safe
record._formatted_msg = json.dumps(super_payload or "")
# convert to GCP structred logging format
gcp_payload = self._gcp_formatter.format(record)
return gcp_payload
55 changes: 53 additions & 2 deletions tests/unit/handlers/test_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,9 @@ def test_emit(self):
)
logname = "loggername"
message = "hello world"
record = logging.LogRecord(logname, logging, None, None, message, None, None)
record = logging.LogRecord(
logname, logging.INFO, None, None, message, None, None
)
handler.handle(record)
self.assertEqual(
handler.transport.send_called_with,
Expand All @@ -315,7 +317,9 @@ def test_emit_manual_field_override(self):
)
logname = "loggername"
message = "hello world"
record = logging.LogRecord(logname, logging, None, None, message, None, None)
record = logging.LogRecord(
logname, logging.INFO, None, None, message, None, None
)
# set attributes manually
expected_trace = "123"
setattr(record, "trace", expected_trace)
Expand Down Expand Up @@ -350,6 +354,53 @@ def test_emit_manual_field_override(self):
),
)

def test_emit_with_custom_formatter(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="%(name)s :: %(levelname)s :: %(message)s")
handler.setFormatter(logFormatter)
message = "test"
expected_result = "logname :: INFO :: test"
record = logging.LogRecord(
"logname", logging.INFO, None, None, message, None, None
)
handler.handle(record)

self.assertEqual(
handler.transport.send_called_with,
(record, expected_result, _GLOBAL_RESOURCE, None, None, None, None, None,),
)

def test_format_with_arguments(self):
"""
Handler should support format string arguments
"""
from google.cloud.logging_v2.logger import _GLOBAL_RESOURCE

client = _Client(self.PROJECT)
handler = self._make_one(
client, transport=_Transport, resource=_GLOBAL_RESOURCE,
)
message = "name: %s"
name_arg = "Daniel"
expected_result = "name: Daniel"
record = logging.LogRecord(
None, logging.INFO, None, None, message, name_arg, None
)
handler.handle(record)

self.assertEqual(
handler.transport.send_called_with,
(record, expected_result, _GLOBAL_RESOURCE, None, None, None, None, None,),
)


class TestSetupLogging(unittest.TestCase):
def _call_fut(self, handler, excludes=None):
Expand Down
58 changes: 54 additions & 4 deletions tests/unit/handlers/test_structured_log.py
8000
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,67 @@ def test_format_with_quotes(self):
When logging a message containing quotes, escape chars should be added
"""
import logging
import json

handler = self._make_one()
message = '"test"'
expected_result = '\\"test\\"'
record = logging.LogRecord(None, logging.INFO, None, None, message, None, None,)
record.created = None
handler.filter(record)
result = json.loads(handler.format(record))
result["message"] = expected_result
self.assertEqual(result["message"], expected_result)
result = handler.format(record)
self.assertIn(expected_result, result)

def test_format_with_line_break(self):
"""
When logging a message containing \n, it should be properly escaped
"""
import logging

handler = self._make_one()
message = "test\ntest"
expected_result = "test\\ntest"
record = logging.LogRecord(None, logging.INFO, None, None, message, None, None,)
record.created = None
handler.filter(record)
result = handler.format(record)
self.assertIn(expected_result, result)

def test_format_with_custom_formatter(self):
"""
Handler should respect custom formatters attached
"""
import logging

handler = self._make_one()
logFormatter = logging.Formatter(fmt="%(name)s :: %(levelname)s :: %(message)s")
handler.setFormatter(logFormatter)
message = "test"
expected_result = "logname :: INFO :: 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)

def test_format_with_arguments(self):
"""
Handler should support format string arguments
"""
import logging

handler = self._make_one()
message = "name: %s"
name_arg = "Daniel"
expected_result = "name: Daniel"
record = logging.LogRecord(
None, logging.INFO, None, None, message, name_arg, None,
)
record.created = None
handler.filter(record)
result = handler.format(record)
self.assertIn(expected_result, result)

def test_format_with_request(self):
import logging
Expand Down
0