Skip to content

Commit 8d6c1dd

Browse files
authored
Merge pull request #78 from zencity/FEATURE/77/handle-extra-properties
Implement extra properties from json-logging-py
2 parents 2f4bfb6 + ef7546f commit 8d6c1dd

File tree

4 files changed

+97
-8
lines changed

4 files changed

+97
-8
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,14 @@ correlation_id_without_request_obj = json_logging.get_correlation_id()
245245

246246
## 2.4 Log extra properties
247247

248-
Extra property can be added to logging statement as follow:
248+
Extra property can be added to logging statement as follows:
249249

250250
```python
251-
logger.info("test log statement", extra={'props': {'extra_property': 'extra_value'}})
251+
logger.info("test log statement", extra={'props': {'extra_property': 'extra_value'}, 'tags': ['app:name']})
252252
```
253253

254+
_For backwards compatibility, properties added in `props` are extracted to the root and take precedence over those set directly in the root of `extra`. This is relevant only for logs formatted by `JSONLogFormatter`, other formatters never printed the `props` details._
255+
254256
### Forced overriding correlation-id
255257
A custom correlation id can be passed to logging statement as follow:
256258
```

json_logging/__init__.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,28 @@
2525
COMPONENT_NAME = EMPTY_VALUE
2626
COMPONENT_INSTANCE_INDEX = 0
2727

28+
# The list contains all the attributes listed in
29+
# http://docs.python.org/library/logging.html#logrecord-attributes
30+
RECORD_ATTR_SKIP_LIST = [
31+
'asctime', 'created', 'exc_info', 'exc_text', 'filename', 'args',
32+
'funcName', 'id', 'levelname', 'levelno', 'lineno', 'module', 'msg',
33+
'msecs', 'msecs', 'message', 'name', 'pathname', 'process',
34+
'processName', 'relativeCreated', 'thread', 'threadName', 'extra',
35+
# Also exclude legacy 'props'
36+
'props',
37+
]
38+
39+
try:
40+
basestring
41+
except NameError:
42+
basestring = str
43+
44+
if sys.version_info < (3, 0):
45+
EASY_TYPES = (basestring, bool, dict, float, int, list, type(None))
46+
else:
47+
RECORD_ATTR_SKIP_LIST.append('stack_info')
48+
EASY_TYPES = (str, bool, dict, float, int, list, type(None))
49+
2850
_framework_support_map = {}
2951
_current_framework = None
3052
_logger = get_library_logger(__name__)
@@ -268,8 +290,29 @@ def _format_log_object(self, record, request_util):
268290
"written_ts": util.epoch_nano_second(utcnow),
269291
}
270292
base_obj.update(self.base_object_common)
293+
# Add extra fields
294+
base_obj.update(self._get_extra_fields(record))
271295
return base_obj
272296

297+
def _get_extra_fields(self, record):
298+
fields = {}
299+
300+
if record.args:
301+
fields['msg'] = record.msg
302+
303+
for key, value in record.__dict__.items():
304+
if key not in RECORD_ATTR_SKIP_LIST:
305+
if isinstance(value, EASY_TYPES):
306+
fields[key] = value
307+
else:
308+
fields[key] = repr(value)
309+
310+
# Always add 'props' to the root of the log, assumes props is a dict
311+
if hasattr(record, 'props') and isinstance(record.props, dict):
312+
fields.update(record.props)
313+
314+
return fields
315+
273316

274317
class JSONRequestLogFormatter(BaseJSONFormatter):
275318
"""
@@ -342,8 +385,6 @@ def _format_log_object(self, record, request_util):
342385
"module": record.module,
343386
"line_no": record.lineno,
344387
})
345-
if hasattr(record, 'props'):
346-
json_log_object.update(record.props)
347388

348389
if record.exc_info or record.exc_text:
349390
json_log_object.update(self.get_exc_fields(record))

tests/test_fastapi.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,15 @@ async def log_error():
5151
async def extra_property():
5252
logger.info(
5353
"test log statement with extra props",
54-
extra={"props": {"extra_property": "extra_value"}},
54+
extra={"props": {"extra_property": "extra_value"}, "tags": ["app:name"], "extra_property": "extra_value2"},
55+
)
56+
return {}
57+
58+
@app.get("/log/extra_property_no_props")
59+
async def extra_property_no_props():
60+
logger.info(
61+
"test log statement with extra and no 'props' property",
62+
extra={"tags": ["app:name"], "extra_property": "extra_value2"},
5563
)
5664
return {}
5765

@@ -148,8 +156,23 @@ def test_extra_property(client_and_log_handler):
148156
assert response.status_code == 200
149157
assert len(handler.messages) == 1
150158
msg = json.loads(handler.messages[0])
151-
assert set(msg.keys()) == constants.STANDARD_MSG_ATTRIBUTES.union({"extra_property"})
159+
assert set(msg.keys()) == constants.STANDARD_MSG_ATTRIBUTES.union({"extra_property", "tags"})
152160
assert msg["extra_property"] == "extra_value"
161+
assert msg["tags"] == ["app:name"]
162+
163+
164+
def test_extra_property_no_props(client_and_log_handler):
165+
"""Test adding an extra property to a log message"""
166+
api_client, handler = client_and_log_handler
167+
168+
response = api_client.get("/log/extra_property_no_props")
169+
170+
assert response.status_code == 200
171+
assert len(handler.messages) == 1
172+
msg = json.loads(handler.messages[0])
173+
assert set(msg.keys()) == constants.STANDARD_MSG_ATTRIBUTES.union({"extra_property", "tags"})
174+
assert msg["extra_property"] == "extra_value2"
175+
assert msg["tags"] == ["app:name"]
153176

154177

155178
def test_exception_logged_with_stack_trace(client_and_log_handler):

tests/test_flask.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,15 @@ def log_error():
5151
def extra_property():
5252
logger.info(
5353
"test log statement with extra props",
54-
extra={"props": {"extra_property": "extra_value"}},
54+
extra={"props": {"extra_property": "extra_value"}, "tags": ["app:name"], "extra_property": "extra_value2"},
55+
)
56+
return {}
57+
58+
@app.route("/log/extra_property_no_props")
59+
def extra_property_no_props():
60+
logger.info(
61+
"test log statement with extra and no 'props' property",
62+
extra={"tags": ["app:name"], "extra_property": "extra_value2"},
5563
)
5664
return {}
5765

@@ -148,8 +156,23 @@ def test_extra_property(client_and_log_handler):
148156
assert response.status_code == 200
149157
assert len(handler.messages) == 1
150158
msg = json.loads(handler.messages[0])
151-
assert set(msg.keys()) == constants.STANDARD_MSG_ATTRIBUTES.union({"extra_property"})
159+
assert set(msg.keys()) == constants.STANDARD_MSG_ATTRIBUTES.union({"extra_property", "tags"})
152160
assert msg["extra_property"] == "extra_value"
161+
assert msg["tags"] == ["app:name"]
162+
163+
164+
def test_extra_property_no_props(client_and_log_handler):
165+
"""Test adding an extra property to a log message"""
166+
api_client, handler = client_and_log_handler
167+
168+
response = api_client.get("/log/extra_property_no_props")
169+
170+
assert response.status_code == 200
171+
assert len(handler.messages) == 1
172+
msg = json.loads(handler.messages[0])
173+
assert set(msg.keys()) == constants.STANDARD_MSG_ATTRIBUTES.union({"extra_property", "tags"})
174+
assert msg["extra_property"] == "extra_value2"
175+
assert msg["tags"] == ["app:name"]
153176

154177

155178
def test_exception_logged_with_stack_trace(client_and_log_handler):

0 commit comments

Comments
 (0)