From b52c8cd247e9b185d176bfbc4c24c5a2cb35e5be Mon Sep 17 00:00:00 2001 From: Vladyslav Drok Date: Fri, 20 Jul 2018 14:17:43 +0300 Subject: [PATCH] Serialize complex objects in FluentFormatter This change introduces the serialization with basically json dumps, falling back to repr for non-serializable object. We check the types of the top level objects passed to the formatter, as for example an object enclosed into dictionary passed to the logger should be a much more rare case. Closes-Bug: 1782361 Change-Id: Ib214f95abfadd91f85b6cce853057a572ec287f6 --- oslo_log/formatters.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/oslo_log/formatters.py b/oslo_log/formatters.py index 3c508999..8aa73c68 100644 --- a/oslo_log/formatters.py +++ b/oslo_log/formatters.py @@ -184,6 +184,19 @@ class _ReplaceFalseValue(dict): _MSG_KEY_REGEX = re.compile(r'(%+)\((\w+)\)') +def _json_dumps_with_fallback(obj): + if _HAVE_JSONUTILS_FALLBACK: + # Bug #1593641: If an object cannot be serialized to JSON, convert + # it using repr() to prevent serialization errors. Using repr() is + # not ideal, but serialization errors are unexpected on logs, + # especially when the code using logs is not aware that the + # JSONFormatter will be used. + convert = functools.partial(jsonutils.to_primitive, fallback=repr) + return jsonutils.dumps(obj, default=convert) + else: + return jsonutils.dumps(obj) + + class JSONFormatter(logging.Formatter): def __init__(self, fmt=None, datefmt=None, style='%'): # NOTE(sfinucan) we ignore the fmt and style arguments, but they're @@ -271,16 +284,7 @@ class JSONFormatter(logging.Formatter): if record.exc_info: message['traceback'] = self.formatException(record.exc_info) - if _HAVE_JSONUTILS_FALLBACK: - # Bug #1593641: If an object cannot be serialized to JSON, convert - # it using repr() to prevent serialization errors. Using repr() is - # not ideal, but serialization errors are unexpected on logs, - # especially when the code using logs is not aware that the - # JSONFormatter will be used. - convert = functools.partial(jsonutils.to_primitive, fallback=repr) - return jsonutils.dumps(message, default=convert) - else: - return jsonutils.dumps(message) + return _json_dumps_with_fallback(message) class FluentFormatter(logging.Formatter): @@ -352,6 +356,12 @@ class FluentFormatter(logging.Formatter): else: message['context'] = {} extra.pop('context', None) + # NOTE(vdrok): try to dump complex objects + primitive_types = six.string_types + six.integer_types + ( + bool, type(None), float, list, dict) + for key, value in extra.items(): + if not isinstance(value, primitive_types): + extra[key] = _json_dumps_with_fallback(value) message['extra'] = extra if record.exc_info: