Repository: SAP/cf-python-logging-support

Issue:
Problem with object logging
Hello,

we run into trouble using this package in combination with the pika package (http://pika.readthedocs.io/en/0.10.0/). After initialization of the logger using cf_logging.init() the pika packages produces exceptions while trying to perform the following log operation: 

LOGGER.info('Closing channel (%s): %r on %s',
                    reply_code, reply_text, self)

Here 'reply_code' is an int, 'reply_text' a string and 'self' is the channel object which has a working __repr__ method. 

Best 
Jan

Discussion:

Patch:
diff --git a/.gitignore b/.gitignore
index e6ff992..4a314cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ __pycache__/
 
 # Distribution / packaging
 .Python
+.env
 env/
 env3/
 venv/
@@ -25,6 +26,7 @@ lib/
 dist/
 lib64/
 parts/
+.pytest_cache/
 sdist/
 var/
 *.egg-info/
diff --git a/sap/cf_logging/formatters/json_formatter.py b/sap/cf_logging/formatters/json_formatter.py
index 9df5474..1e941d0 100644
--- a/sap/cf_logging/formatters/json_formatter.py
+++ b/sap/cf_logging/formatters/json_formatter.py
@@ -2,22 +2,26 @@
 import json
 import logging
 import sys
+from sap.cf_logging.record.simple_log_record import SimpleLogRecord
+
+def _default_serializer(obj):
+    return str(obj)
 
 if sys.version_info[0] == 3:
     def _encode(obj):
-        return json.dumps(obj)
+        return json.dumps(obj, default=_default_serializer)
 else:
     def _encode(obj):
-        return unicode(json.dumps(obj))  # pylint: disable=undefined-variable
+        return unicode(json.dumps(obj, default=_default_serializer))  # pylint: disable=undefined-variable
 
 
 class JsonFormatter(logging.Formatter):
     """
-    Formatter for non-web application log
+    Format application log in JSON format
     """
 
     def format(self, record):
-        """ Format the log record into a JSON object """
-        if hasattr(record, 'format'):
+        """ Format the known log records in JSON format """
+        if isinstance(record, SimpleLogRecord):
             return _encode(record.format())
-        return _encode(record.__dict__)
+        return super(JsonFormatter, self).format(record)


Test patch:
diff --git a/tests/unit/formatters/test_json_formatter.py b/tests/unit/formatters/test_json_formatter.py
new file mode 100644
index 0000000..70ffe60
--- /dev/null
+++ b/tests/unit/formatters/test_json_formatter.py
@@ -0,0 +1,24 @@
+""" Tests json log formatting """
+import json
+import logging
+from sap.cf_logging.record.simple_log_record import SimpleLogRecord
+from sap.cf_logging.formatters.json_formatter import JsonFormatter
+
+lvl, fn, lno, func, exc_info = logging.INFO, "(unknown file)", 0, "(unknown function)", None
+formatter = JsonFormatter()
+
+
+def test_unknown_records_format():
+    """ test unknown log records will be delegated to logging.Formatter """
+    log_record = logging.LogRecord('name', lvl, fn, lno, 'msg', [], exc_info)
+    assert formatter.format(log_record) == 'msg'
+
+
+def test_non_json_serializable():
+    """ test json formatter handles non JSON serializable object """
+    class MyClass(object): pass
+    extra = { 'cls': MyClass() } 
+    log_record = SimpleLogRecord(extra, None, 'name', lvl, fn, lno, 'msg', [], exc_info)
+    record_object = json.loads(formatter.format(log_record))
+    assert record_object.get('cls') is not None
+    assert 'MyClass' in record_object.get('cls')