Skip to content

Commit 5f85723

Browse files
authored
fix(JsonFormatter): type the constructor (madzak#170)
1 parent 213ce50 commit 5f85723

File tree

2 files changed

+95
-56
lines changed

2 files changed

+95
-56
lines changed

src/pythonjsonlogger/jsonlogger.py

Lines changed: 94 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
'''
1+
"""
22
This library is provided to allow standard python logging
33
to output log data as JSON formatted strings
4-
'''
4+
"""
55
import logging
66
import json
77
import re
8-
from datetime import date, datetime, time, timezone
98
import traceback
109
import importlib
11-
12-
from typing import Any, Dict, Optional, Union, List, Tuple
10+
from datetime import date, datetime, time, timezone
11+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
1312

1413
from inspect import istraceback
1514

@@ -18,18 +17,38 @@
1817
# skip natural LogRecord attributes
1918
# http://docs.python.org/library/logging.html#logrecord-attributes
2019
RESERVED_ATTRS: Tuple[str, ...] = (
21-
'args', 'asctime', 'created', 'exc_info', 'exc_text', 'filename',
22-
'funcName', 'levelname', 'levelno', 'lineno', 'module',
23-
'msecs', 'message', 'msg', 'name', 'pathname', 'process',
24-
'processName', 'relativeCreated', 'stack_info', 'thread', 'threadName')
25-
20+
"args",
21+
"asctime",
22+
"created",
23+
"exc_info",
24+
"exc_text",
25+
"filename",
26+
"funcName",
27+
"levelname",
28+
"levelno",
29+
"lineno",
30+
"module",
31+
"msecs",
32+
"message",
33+
"msg",
34+
"name",
35+
"pathname",
36+
"process",
37+
"processName",
38+
"relativeCreated",
39+
"stack_info",
40+
"thread",
41+
"threadName",
42+
)
43+
44+
OptionalCallableOrStr = Optional[Union[Callable, str]]
2645

2746

2847
def merge_record_extra(
2948
record: logging.LogRecord,
3049
target: Dict,
3150
reserved: Union[Dict, List],
32-
rename_fields: Optional[Dict[str,str]] = None,
51+
rename_fields: Optional[Dict[str, str]] = None,
3352
) -> Dict:
3453
"""
3554
Merges extra attributes from LogRecord object into target dictionary
@@ -44,10 +63,10 @@ def merge_record_extra(
4463
rename_fields = {}
4564
for key, value in record.__dict__.items():
4665
# this allows to have numeric keys
47-
if (key not in reserved
48-
and not (hasattr(key, "startswith")
49-
and key.startswith('_'))):
50-
target[rename_fields.get(key,key)] = value
66+
if key not in reserved and not (
67+
hasattr(key, "startswith") and key.startswith("_")
68+
):
69+
target[rename_fields.get(key, key)] = value
5170
return target
5271

5372

@@ -61,11 +80,9 @@ def default(self, obj):
6180
return self.format_datetime_obj(obj)
6281

6382
elif istraceback(obj):
64-
return ''.join(traceback.format_tb(obj)).strip()
83+
return "".join(traceback.format_tb(obj)).strip()
6584

66-
elif type(obj) == Exception \
67-
or isinstance(obj, Exception) \
68-
or type(obj) == type:
85+
elif type(obj) == Exception or isinstance(obj, Exception) or type(obj) == type:
6986
return str(obj)
7087

7188
try:
@@ -89,22 +106,34 @@ class JsonFormatter(logging.Formatter):
89106
json default encoder
90107
"""
91108

92-
def __init__(self, *args, **kwargs):
109+
def __init__(
110+
self,
111+
*args: Any,
112+
json_default: OptionalCallableOrStr = None,
113+
json_encoder: OptionalCallableOrStr = None,
114+
json_serialiser: Union[Callable, str] = json.dumps,
115+
json_indent: Optional[Union[int, str]] = None,
116+
json_ensure_ascii: bool = True,
117+
prefix: str = "",
118+
rename_fields: Optional[dict] = None,
119+
static_fields: Optional[dict] = None,
120+
reserved_attrs: Tuple[str, ...] = RESERVED_ATTRS,
121+
timestamp: Union[bool, str] = False,
122+
**kwargs: Any
123+
):
93124
"""
94125
:param json_default: a function for encoding non-standard objects
95126
as outlined in https://docs.python.org/3/library/json.html
96127
:param json_encoder: optional custom encoder
97128
:param json_serializer: a :meth:`json.dumps`-compatible callable
98129
that will be used to serialize the log record.
99-
:param json_indent: an optional :meth:`json.dumps`-compatible numeric value
100-
that will be used to customize the indent of the output json.
130+
:param json_indent: indent parameter for json.dumps
131+
:param json_ensure_ascii: ensure_ascii parameter for json.dumps
101132
:param prefix: an optional string prefix added at the beginning of
102133
the formatted string
103134
:param rename_fields: an optional dict, used to rename field names in the output.
104135
Rename message to @message: {'message': '@message'}
105136
:param static_fields: an optional dict, used to add fields with static values to all logs
106-
:param json_indent: indent parameter for json.dumps
107-
:param json_ensure_ascii: ensure_ascii parameter for json.dumps
108137
:param reserved_attrs: an optional list of fields that will be skipped when
109138
outputting json log record. Defaults to all log record attributes:
110139
http://docs.python.org/library/logging.html#logrecord-attributes
@@ -113,26 +142,24 @@ def __init__(self, *args, **kwargs):
113142
to log record using string as key. If True boolean is passed, timestamp key
114143
will be "timestamp". Defaults to False/off.
115144
"""
116-
self.json_default = self._str_to_fn(kwargs.pop("json_default", None))
117-
self.json_encoder = self._str_to_fn(kwargs.pop("json_encoder", None))
118-
self.json_serializer = self._str_to_fn(kwargs.pop("json_serializer", json.dumps))
119-
self.json_indent = kwargs.pop("json_indent", None)
120-
self.json_ensure_ascii = kwargs.pop("json_ensure_ascii", True)
121-
self.prefix = kwargs.pop("prefix", "")
122-
self.rename_fields = kwargs.pop("rename_fields", {})
123-
self.static_fields = kwargs.pop("static_fields", {})
124-
reserved_attrs = kwargs.pop("reserved_attrs", RESERVED_ATTRS)
145+
self.json_default = self._str_to_fn(json_default)
146+
self.json_encoder = self._str_to_fn(json_encoder)
147+
self.json_serializer = self._str_to_fn(json_serialiser)
148+
self.json_indent = json_indent
149+
self.json_ensure_ascii = json_ensure_ascii
150+
self.prefix = prefix
151+
self.rename_fields = rename_fields or {}
152+
self.static_fields = static_fields or {}
125153
self.reserved_attrs = dict(zip(reserved_attrs, reserved_attrs))
126-
self.timestamp = kwargs.pop("timestamp", False)
154+
self.timestamp = timestamp
127155

128156
# super(JsonFormatter, self).__init__(*args, **kwargs)
129157
logging.Formatter.__init__(self, *args, **kwargs)
130158
if not self.json_encoder and not self.json_default:
131159
self.json_encoder = JsonEncoder
132160

133161
self._required_fields = self.parse()
134-
self._skip_fields = dict(zip(self._required_fields,
135-
self._required_fields))
162+
self._skip_fields = dict(zip(self._required_fields, self._required_fields))
136163
self._skip_fields.update(self.reserved_attrs)
137164

138165
def _str_to_fn(self, fn_as_str):
@@ -146,7 +173,7 @@ def _str_to_fn(self, fn_as_str):
146173
if not isinstance(fn_as_str, str):
147174
return fn_as_str
148175

149-
path, _, function = fn_as_str.rpartition('.')
176+
path, _, function = fn_as_str.rpartition(".")
150177
module = importlib.import_module(path)
151178
return getattr(module, function)
152179

@@ -158,22 +185,27 @@ def parse(self) -> List[str]:
158185
to include in all log messages.
159186
"""
160187
if isinstance(self._style, logging.StringTemplateStyle):
161-
formatter_style_pattern = re.compile(r'\$\{(.+?)\}', re.IGNORECASE)
188+
formatter_style_pattern = re.compile(r"\$\{(.+?)\}", re.IGNORECASE)
162189
elif isinstance(self._style, logging.StrFormatStyle):
163-
formatter_style_pattern = re.compile(r'\{(.+?)\}', re.IGNORECASE)
190+
formatter_style_pattern = re.compile(r"\{(.+?)\}", re.IGNORECASE)
164191
# PercentStyle is parent class of StringTemplateStyle and StrFormatStyle so
165192
# it needs to be checked last.
166193
elif isinstance(self._style, logging.PercentStyle):
167-
formatter_style_pattern = re.compile(r'%\((.+?)\)', re.IGNORECASE)
194+
formatter_style_pattern = re.compile(r"%\((.+?)\)", re.IGNORECASE)
168195
else:
169-
raise ValueError('Invalid format: %s' % self._fmt)
196+
raise ValueError("Invalid format: %s" % self._fmt)
170197

171198
if self._fmt:
172199
return formatter_style_pattern.findall(self._fmt)
173200
else:
174201
return []
175202

176-
def add_fields(self, log_record: Dict[str, Any], record: logging.LogRecord, message_dict: Dict[str, Any]) -> None:
203+
def add_fields(
204+
self,
205+
log_record: Dict[str, Any],
206+
record: logging.LogRecord,
207+
message_dict: Dict[str, Any],
208+
) -> None:
177209
"""
178210
Override this method to implement custom logic for adding fields.
179211
"""
@@ -182,10 +214,15 @@ def add_fields(self, log_record: Dict[str, Any], record: logging.LogRecord, mess
182214

183215
log_record.update(self.static_fields)
184216
log_record.update(message_dict)
185-
merge_record_extra(record, log_record, reserved=self._skip_fields, rename_fields=self.rename_fields)
217+
merge_record_extra(
218+
record,
219+
log_record,
220+
reserved=self._skip_fields,
221+
rename_fields=self.rename_fields,
222+
)
186223

187224
if self.timestamp:
188-
key = self.timestamp if type(self.timestamp) == str else 'timestamp'
225+
key = self.timestamp if type(self.timestamp) == str else "timestamp"
189226
log_record[key] = datetime.fromtimestamp(record.created, tz=timezone.utc)
190227

191228
self._perform_rename_log_fields(log_record)
@@ -204,11 +241,13 @@ def process_log_record(self, log_record):
204241

205242
def jsonify_log_record(self, log_record):
206243
"""Returns a json string of the log record."""
207-
return self.json_serializer(log_record,
208-
default=self.json_default,
209-
cls=self.json_encoder,
210-
indent=self.json_indent,
211-
ensure_ascii=self.json_ensure_ascii)
244+
return self.json_serializer(
245+
log_record,
246+
default=self.json_default,
247+
cls=self.json_encoder,
248+
indent=self.json_indent,
249+
ensure_ascii=self.json_ensure_ascii,
250+
)
212251

213252
def serialize_log_record(self, log_record: Dict[str, Any]) -> str:
214253
"""Returns the final representation of the log record."""
@@ -230,14 +269,14 @@ def format(self, record: logging.LogRecord) -> str:
230269

231270
# Display formatted exception, but allow overriding it in the
232271
# user-supplied dict.
233-
if record.exc_info and not message_dict.get('exc_info'):
234-
message_dict['exc_info'] = self.formatException(record.exc_info)
235-
if not message_dict.get('exc_info') and record.exc_text:
236-
message_dict['exc_info'] = record.exc_text
272+
if record.exc_info and not message_dict.get("exc_info"):
273+
message_dict["exc_info"] = self.formatException(record.exc_info)
274+
if not message_dict.get("exc_info") and record.exc_text:
275+
message_dict["exc_info"] = record.exc_text
237276
# Display formatted record of stack frames
238277
# default format is a string returned from :func:`traceback.print_stack`
239-
if record.stack_info and not message_dict.get('stack_info'):
240-
message_dict['stack_info'] = self.formatStack(record.stack_info)
278+
if record.stack_info and not message_dict.get("stack_info"):
279+
message_dict["stack_info"] = self.formatStack(record.stack_info)
241280

242281
log_record: Dict[str, Any] = OrderedDict()
243282
self.add_fields(log_record, record, message_dict)

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ description = run type checks
3131
deps =
3232
mypy>=1.0
3333
commands =
34-
mypy src
34+
mypy src

0 commit comments

Comments
 (0)