Skip to content

Commit acfd083

Browse files
committed
chore: move logger formatter to its own file
1 parent 6af9f91 commit acfd083

File tree

2 files changed

+100
-97
lines changed

2 files changed

+100
-97
lines changed
+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import json
2+
import logging
3+
from typing import Any
4+
5+
6+
def json_formatter(unserialized_value: Any):
7+
"""JSON custom serializer to cast unserialisable values to strings.
8+
9+
Example
10+
-------
11+
12+
**Serialize unserialisable value to string**
13+
14+
class X: pass
15+
value = {"x": X()}
16+
17+
json.dumps(value, default=json_formatter)
18+
19+
Parameters
20+
----------
21+
unserialized_value: Any
22+
Python object unserializable by JSON
23+
"""
24+
return str(unserialized_value)
25+
26+
27+
class JsonFormatter(logging.Formatter):
28+
"""AWS Lambda Logging formatter.
29+
30+
Formats the log message as a JSON encoded string. If the message is a
31+
dict it will be used directly. If the message can be parsed as JSON, then
32+
the parse d value is used in the output record.
33+
34+
Originally taken from https://gitlab.com/hadrien/aws_lambda_logging/
35+
36+
"""
37+
38+
def __init__(self, **kwargs):
39+
"""Return a JsonFormatter instance.
40+
41+
The `json_default` kwarg is used to specify a formatter for otherwise
42+
unserialisable values. It must not throw. Defaults to a function that
43+
coerces the value to a string.
44+
45+
Other kwargs are used to specify log field format strings.
46+
"""
47+
datefmt = kwargs.pop("datefmt", None)
48+
49+
super(JsonFormatter, self).__init__(datefmt=datefmt)
50+
self.reserved_keys = ["timestamp", "level", "location"]
51+
self.format_dict = {
52+
"timestamp": "%(asctime)s",
53+
"level": "%(levelname)s",
54+
"location": "%(funcName)s:%(lineno)d",
55+
}
56+
self.format_dict.update(kwargs)
57+
self.default_json_formatter = kwargs.pop("json_default", json_formatter)
58+
59+
def format(self, record): # noqa: A003
60+
record_dict = record.__dict__.copy()
61+
record_dict["asctime"] = self.formatTime(record, self.datefmt)
62+
63+
log_dict = {}
64+
for key, value in self.format_dict.items():
65+
if value and key in self.reserved_keys:
66+
# converts default logging expr to its record value
67+
# e.g. '%(asctime)s' to '2020-04-24 09:35:40,698'
68+
log_dict[key] = value % record_dict
69+
else:
70+
log_dict[key] = value
71+
72+
if isinstance(record_dict["msg"], dict):
73+
log_dict["message"] = record_dict["msg"]
74+
else:
75+
log_dict["message"] = record.getMessage()
76+
77+
# Attempt to decode the message as JSON, if so, merge it with the
78+
# overall message for clarity.
79+
try:
80+
log_dict["message"] = json.loads(log_dict["message"])
81+
except (json.decoder.JSONDecodeError, TypeError, ValueError):
82+
pass
83+
84+
if record.exc_info:
85+
# Cache the traceback text to avoid converting it multiple times
86+
# (it's constant anyway)
87+
# from logging.Formatter:format
88+
if not record.exc_text:
89+
record.exc_text = self.formatException(record.exc_info)
90+
91+
if record.exc_text:
92+
log_dict["exception"] = record.exc_text
93+
94+
json_record = json.dumps(log_dict, default=self.default_json_formatter)
95+
96+
if hasattr(json_record, "decode"): # pragma: no cover
97+
json_record = json_record.decode("utf-8")
98+
99+
return json_record

aws_lambda_powertools/logging/logger.py

+1-97
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import copy
22
import functools
3-
import json
43
import logging
54
import os
65
import random
@@ -9,109 +8,14 @@
98
from typing import Any, Callable, Dict, Union
109

1110
from .exceptions import InvalidLoggerSamplingRateError
11+
from .formatter import JsonFormatter
1212
from .lambda_context import build_lambda_context_model
1313

1414
logger = logging.getLogger(__name__)
1515

1616
is_cold_start = True
1717

1818

19-
def json_formatter(unserialized_value: Any):
20-
"""JSON custom serializer to cast unserialisable values to strings.
21-
22-
Example
23-
-------
24-
25-
**Serialize unserialisable value to string**
26-
27-
class X: pass
28-
value = {"x": X()}
29-
30-
json.dumps(value, default=json_formatter)
31-
32-
Parameters
33-
----------
34-
unserialized_value: Any
35-
Python object unserializable by JSON
36-
"""
37-
return str(unserialized_value)
38-
39-
40-
class JsonFormatter(logging.Formatter):
41-
"""AWS Lambda Logging formatter.
42-
43-
Formats the log message as a JSON encoded string. If the message is a
44-
dict it will be used directly. If the message can be parsed as JSON, then
45-
the parse d value is used in the output record.
46-
47-
Originally taken from https://gitlab.com/hadrien/aws_lambda_logging/
48-
49-
"""
50-
51-
def __init__(self, **kwargs):
52-
"""Return a JsonFormatter instance.
53-
54-
The `json_default` kwarg is used to specify a formatter for otherwise
55-
unserialisable values. It must not throw. Defaults to a function that
56-
coerces the value to a string.
57-
58-
Other kwargs are used to specify log field format strings.
59-
"""
60-
datefmt = kwargs.pop("datefmt", None)
61-
62-
super(JsonFormatter, self).__init__(datefmt=datefmt)
63-
self.reserved_keys = ["timestamp", "level", "location"]
64-
self.format_dict = {
65-
"timestamp": "%(asctime)s",
66-
"level": "%(levelname)s",
67-
"location": "%(funcName)s:%(lineno)d",
68-
}
69-
self.format_dict.update(kwargs)
70-
self.default_json_formatter = kwargs.pop("json_default", json_formatter)
71-
72-
def format(self, record): # noqa: A003
73-
record_dict = record.__dict__.copy()
74-
record_dict["asctime"] = self.formatTime(record, self.datefmt)
75-
76-
log_dict = {}
77-
for key, value in self.format_dict.items():
78-
if value and key in self.reserved_keys:
79-
# converts default logging expr to its record value
80-
# e.g. '%(asctime)s' to '2020-04-24 09:35:40,698'
81-
log_dict[key] = value % record_dict
82-
else:
83-
log_dict[key] = value
84-
85-
if isinstance(record_dict["msg"], dict):
86-
log_dict["message"] = record_dict["msg"]
87-
else:
88-
log_dict["message"] = record.getMessage()
89-
90-
# Attempt to decode the message as JSON, if so, merge it with the
91-
# overall message for clarity.
92-
try:
93-
log_dict["message"] = json.loads(log_dict["message"])
94-
except (json.decoder.JSONDecodeError, TypeError, ValueError):
95-
pass
96-
97-
if record.exc_info:
98-
# Cache the traceback text to avoid converting it multiple times
99-
# (it's constant anyway)
100-
# from logging.Formatter:format
101-
if not record.exc_text:
102-
record.exc_text = self.formatException(record.exc_info)
103-
104-
if record.exc_text:
105-
log_dict["exception"] = record.exc_text
106-
107-
json_record = json.dumps(log_dict, default=self.default_json_formatter)
108-
109-
if hasattr(json_record, "decode"): # pragma: no cover
110-
json_record = json_record.decode("utf-8")
111-
112-
return json_record
113-
114-
11519
def _is_cold_start() -> bool:
11620
"""Verifies whether is cold start
11721

0 commit comments

Comments
 (0)