Skip to content

exclude_fields should filter nested extras=... #16

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 1 commit into from
Jul 23, 2020
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
11 changes: 8 additions & 3 deletions ecs_logging/_stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
TYPE_CHECKING,
collections_abc,
lru_cache,
flatten_dict,
)

if TYPE_CHECKING:
Expand Down Expand Up @@ -152,12 +153,16 @@ def format_to_ecs(self, record):
# adding 'message' to ``available``, it simplifies the code
available["message"] = record.getMessage()

extras = set(available).difference(self._LOGRECORD_DICT)
# Pull all extras and flatten them to be sent into '_is_field_excluded'
# since they can be defined as 'extras={"http": {"method": "GET"}}'
extra_keys = set(available).difference(self._LOGRECORD_DICT)
extras = flatten_dict({key: available[key] for key in extra_keys})

# Merge in any keys that were set within 'extra={...}'
for field in extras:
for field, value in extras.items():
if self._is_field_excluded(field):
continue
merge_dicts(de_dot(field, available[field]), result)
merge_dicts(de_dot(field, value), result)

# The following is mostly for the ecs format. You can't have 2x
# 'message' keys in _WANTED_ATTRS, so we set the value to
Expand Down
27 changes: 27 additions & 0 deletions ecs_logging/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,33 @@
]


def flatten_dict(value):
# type: (typing.Mapping[str, Any]) -> Dict[str, Any]
"""Adds dots to all nested fields in dictionaries.
Raises an error if there are entries which are represented
with different forms of nesting. (ie {"a": {"b": 1}, "a.b": 2})
"""
top_level = {}
for key, val in value.items():
if not isinstance(val, collections_abc.Mapping):
if key in top_level:
raise ValueError(
"Duplicate entry for '%s' with different nesting" % key
)
top_level[key] = val
else:
val = flatten_dict(val)
for vkey, vval in val.items():
vkey = "%s.%s" % (key, vkey)
if vkey in top_level:
raise ValueError(
"Duplicate entry for '%s' with different nesting" % vkey
)
top_level[vkey] = vval

return top_level


def normalize_dict(value):
# type: (Dict[str, Any]) -> Dict[str, Any]
"""Expands all dotted names to nested dictionaries"""
Expand Down
5 changes: 4 additions & 1 deletion tests/test_stdlib_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ def test_extra_is_merged(time, logger):
logger.info(
"hey world",
extra={
"tls": {"cipher": "AES"},
"tls": {
"cipher": "AES",
"client": {"hash": {"md5": "0F76C7F2C55BFD7D8E8B8F4BFBF0C9EC"}},
},
"tls.established": True,
"tls.client.certificate": "cert",
},
Expand Down
28 changes: 28 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import pytest
from ecs_logging._utils import flatten_dict, de_dot, normalize_dict


def test_flatten_dict():
assert flatten_dict(
{"a": {"b": 1}, "a.c": {"d.e": {"f": 1}, "d.e.g": [{"f.c": 2}]}}
) == {"a.b": 1, "a.c.d.e.f": 1, "a.c.d.e.g": [{"f.c": 2}]}

with pytest.raises(ValueError) as e:
flatten_dict({"a": {"b": 1}, "a.b": 2})

assert str(e.value) == "Duplicate entry for 'a.b' with different nesting"

with pytest.raises(ValueError) as e:
flatten_dict({"a": {"b": {"c": 1}}, "a.b": {"c": 2}, "a.b.c": 1})

assert str(e.value) == "Duplicate entry for 'a.b.c' with different nesting"


def test_de_dot():
assert de_dot("x.y.z", {"a": {"b": 1}}) == {"x": {"y": {"z": {"a": {"b": 1}}}}}


def test_normalize_dict():
assert normalize_dict(
{"a": {"b": 1}, "a.c": {"d.e": {"f": 1}, "d.e.g": [{"f.c": 2}]}}
) == {"a": {"b": 1, "c": {"d": {"e": {"f": 1, "g": [{"f": {"c": 2}}]}}}}}