Skip to content

Commit 026faaf

Browse files
author
Michael Brewer
authored
fix(logging): Don't include json_default in logs (#132)
Changes: * formatter.py - pop `json_default` from the **kwargs before updating `self.format_dict` * formatter.py - clean up and remove uneeded `pragma: no cover` * formatter.py - remove unused `decode` case * test_aws_lambda_logging.py - update unit tests to include a check for `json_default`
1 parent 814062e commit 026faaf

File tree

2 files changed

+36
-23
lines changed

2 files changed

+36
-23
lines changed

aws_lambda_powertools/logging/formatter.py

+11-16
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
from typing import Any
44

55

6-
def json_formatter(unserialized_value: Any):
7-
"""JSON custom serializer to cast unserialisable values to strings.
6+
def json_formatter(unserializable_value: Any):
7+
"""JSON custom serializer to cast unserializable values to strings.
88
99
Example
1010
-------
1111
12-
**Serialize unserialisable value to string**
12+
**Serialize unserializable value to string**
1313
1414
class X: pass
1515
value = {"x": X()}
@@ -18,10 +18,10 @@ class X: pass
1818
1919
Parameters
2020
----------
21-
unserialized_value: Any
21+
unserializable_value: Any
2222
Python object unserializable by JSON
2323
"""
24-
return str(unserialized_value)
24+
return str(unserializable_value)
2525

2626

2727
class JsonFormatter(logging.Formatter):
@@ -39,11 +39,12 @@ def __init__(self, **kwargs):
3939
"""Return a JsonFormatter instance.
4040
4141
The `json_default` kwarg is used to specify a formatter for otherwise
42-
unserialisable values. It must not throw. Defaults to a function that
42+
unserializable values. It must not throw. Defaults to a function that
4343
coerces the value to a string.
4444
4545
Other kwargs are used to specify log field format strings.
4646
"""
47+
self.default_json_formatter = kwargs.pop("json_default", json_formatter)
4748
datefmt = kwargs.pop("datefmt", None)
4849

4950
super(JsonFormatter, self).__init__(datefmt=datefmt)
@@ -54,7 +55,6 @@ def __init__(self, **kwargs):
5455
"location": "%(funcName)s:%(lineno)d",
5556
}
5657
self.format_dict.update(kwargs)
57-
self.default_json_formatter = kwargs.pop("json_default", json_formatter)
5858

5959
def update_formatter(self, **kwargs):
6060
self.format_dict.update(kwargs)
@@ -64,6 +64,7 @@ def format(self, record): # noqa: A003
6464
record_dict["asctime"] = self.formatTime(record, self.datefmt)
6565

6666
log_dict = {}
67+
6768
for key, value in self.format_dict.items():
6869
if value and key in self.reserved_keys:
6970
# converts default logging expr to its record value
@@ -84,19 +85,13 @@ def format(self, record): # noqa: A003
8485
except (json.decoder.JSONDecodeError, TypeError, ValueError):
8586
pass
8687

87-
if record.exc_info:
88+
if record.exc_info and not record.exc_text:
8889
# Cache the traceback text to avoid converting it multiple times
8990
# (it's constant anyway)
9091
# from logging.Formatter:format
91-
if not record.exc_text: # pragma: no cover
92-
record.exc_text = self.formatException(record.exc_info)
92+
record.exc_text = self.formatException(record.exc_info)
9393

9494
if record.exc_text:
9595
log_dict["exception"] = record.exc_text
9696

97-
json_record = json.dumps(log_dict, default=self.default_json_formatter)
98-
99-
if hasattr(json_record, "decode"): # pragma: no cover
100-
json_record = json_record.decode("utf-8")
101-
102-
return json_record
97+
return json.dumps(log_dict, default=self.default_json_formatter)

tests/functional/test_aws_lambda_logging.py

+25-7
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,17 @@ def test_setup_with_valid_log_levels(stdout, level):
3838

3939

4040
def test_logging_exception_traceback(stdout):
41-
logger = Logger(level="DEBUG", stream=stdout, request_id="request id!", another="value")
41+
logger = Logger(level="DEBUG", stream=stdout)
4242

4343
try:
44-
raise Exception("Boom")
45-
except Exception:
46-
logger.exception("This is a test")
44+
raise ValueError("Boom")
45+
except ValueError:
46+
logger.exception("A value error occurred")
4747

4848
log_dict = json.loads(stdout.getvalue())
4949

5050
check_log_dict(log_dict)
51+
assert "ERROR" == log_dict["level"]
5152
assert "exception" in log_dict
5253

5354

@@ -86,15 +87,32 @@ def test_with_json_message(stdout):
8687
assert msg == log_dict["message"]
8788

8889

89-
def test_with_unserialisable_value_in_message(stdout):
90+
def test_with_unserializable_value_in_message(stdout):
9091
logger = Logger(level="DEBUG", stream=stdout)
9192

92-
class X:
93+
class Unserializable:
9394
pass
9495

95-
msg = {"x": X()}
96+
msg = {"x": Unserializable()}
9697
logger.debug(msg)
9798

9899
log_dict = json.loads(stdout.getvalue())
99100

100101
assert log_dict["message"]["x"].startswith("<")
102+
103+
104+
def test_with_unserializable_value_in_message_custom(stdout):
105+
class Unserializable:
106+
pass
107+
108+
# GIVEN a custom json_default
109+
logger = Logger(level="DEBUG", stream=stdout, json_default=lambda o: f"<non-serializable: {type(o).__name__}>")
110+
111+
# WHEN we log a message
112+
logger.debug({"x": Unserializable()})
113+
114+
log_dict = json.loads(stdout.getvalue())
115+
116+
# THEN json_default should not be in the log message and the custom unserializable handler should be used
117+
assert log_dict["message"]["x"] == "<non-serializable: Unserializable>"
118+
assert "json_default" not in log_dict

0 commit comments

Comments
 (0)