1
- '''
1
+ """
2
2
This library is provided to allow standard python logging
3
3
to output log data as JSON formatted strings
4
- '''
4
+ """
5
5
import logging
6
6
import json
7
7
import re
8
- from datetime import date , datetime , time , timezone
9
8
import traceback
10
9
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
13
12
14
13
from inspect import istraceback
15
14
18
17
# skip natural LogRecord attributes
19
18
# http://docs.python.org/library/logging.html#logrecord-attributes
20
19
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 ]]
26
45
27
46
28
47
def merge_record_extra (
29
48
record : logging .LogRecord ,
30
49
target : Dict ,
31
50
reserved : Union [Dict , List ],
32
- rename_fields : Optional [Dict [str ,str ]] = None ,
51
+ rename_fields : Optional [Dict [str , str ]] = None ,
33
52
) -> Dict :
34
53
"""
35
54
Merges extra attributes from LogRecord object into target dictionary
@@ -44,10 +63,10 @@ def merge_record_extra(
44
63
rename_fields = {}
45
64
for key , value in record .__dict__ .items ():
46
65
# 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
51
70
return target
52
71
53
72
@@ -61,11 +80,9 @@ def default(self, obj):
61
80
return self .format_datetime_obj (obj )
62
81
63
82
elif istraceback (obj ):
64
- return '' .join (traceback .format_tb (obj )).strip ()
83
+ return "" .join (traceback .format_tb (obj )).strip ()
65
84
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 :
69
86
return str (obj )
70
87
71
88
try :
@@ -89,22 +106,34 @@ class JsonFormatter(logging.Formatter):
89
106
json default encoder
90
107
"""
91
108
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
+ ):
93
124
"""
94
125
:param json_default: a function for encoding non-standard objects
95
126
as outlined in https://docs.python.org/3/library/json.html
96
127
:param json_encoder: optional custom encoder
97
128
:param json_serializer: a :meth:`json.dumps`-compatible callable
98
129
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
101
132
:param prefix: an optional string prefix added at the beginning of
102
133
the formatted string
103
134
:param rename_fields: an optional dict, used to rename field names in the output.
104
135
Rename message to @message: {'message': '@message'}
105
136
: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
108
137
:param reserved_attrs: an optional list of fields that will be skipped when
109
138
outputting json log record. Defaults to all log record attributes:
110
139
http://docs.python.org/library/logging.html#logrecord-attributes
@@ -113,26 +142,24 @@ def __init__(self, *args, **kwargs):
113
142
to log record using string as key. If True boolean is passed, timestamp key
114
143
will be "timestamp". Defaults to False/off.
115
144
"""
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 {}
125
153
self .reserved_attrs = dict (zip (reserved_attrs , reserved_attrs ))
126
- self .timestamp = kwargs . pop ( " timestamp" , False )
154
+ self .timestamp = timestamp
127
155
128
156
# super(JsonFormatter, self).__init__(*args, **kwargs)
129
157
logging .Formatter .__init__ (self , * args , ** kwargs )
130
158
if not self .json_encoder and not self .json_default :
131
159
self .json_encoder = JsonEncoder
132
160
133
161
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 ))
136
163
self ._skip_fields .update (self .reserved_attrs )
137
164
138
165
def _str_to_fn (self , fn_as_str ):
@@ -146,7 +173,7 @@ def _str_to_fn(self, fn_as_str):
146
173
if not isinstance (fn_as_str , str ):
147
174
return fn_as_str
148
175
149
- path , _ , function = fn_as_str .rpartition ('.' )
176
+ path , _ , function = fn_as_str .rpartition ("." )
150
177
module = importlib .import_module (path )
151
178
return getattr (module , function )
152
179
@@ -158,22 +185,27 @@ def parse(self) -> List[str]:
158
185
to include in all log messages.
159
186
"""
160
187
if isinstance (self ._style , logging .StringTemplateStyle ):
161
- formatter_style_pattern = re .compile (r' \$\{(.+?)\}' , re .IGNORECASE )
188
+ formatter_style_pattern = re .compile (r" \$\{(.+?)\}" , re .IGNORECASE )
162
189
elif isinstance (self ._style , logging .StrFormatStyle ):
163
- formatter_style_pattern = re .compile (r' \{(.+?)\}' , re .IGNORECASE )
190
+ formatter_style_pattern = re .compile (r" \{(.+?)\}" , re .IGNORECASE )
164
191
# PercentStyle is parent class of StringTemplateStyle and StrFormatStyle so
165
192
# it needs to be checked last.
166
193
elif isinstance (self ._style , logging .PercentStyle ):
167
- formatter_style_pattern = re .compile (r' %\((.+?)\)' , re .IGNORECASE )
194
+ formatter_style_pattern = re .compile (r" %\((.+?)\)" , re .IGNORECASE )
168
195
else :
169
- raise ValueError (' Invalid format: %s' % self ._fmt )
196
+ raise ValueError (" Invalid format: %s" % self ._fmt )
170
197
171
198
if self ._fmt :
172
199
return formatter_style_pattern .findall (self ._fmt )
173
200
else :
174
201
return []
175
202
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 :
177
209
"""
178
210
Override this method to implement custom logic for adding fields.
179
211
"""
@@ -182,10 +214,15 @@ def add_fields(self, log_record: Dict[str, Any], record: logging.LogRecord, mess
182
214
183
215
log_record .update (self .static_fields )
184
216
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
+ )
186
223
187
224
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"
189
226
log_record [key ] = datetime .fromtimestamp (record .created , tz = timezone .utc )
190
227
191
228
self ._perform_rename_log_fields (log_record )
@@ -204,11 +241,13 @@ def process_log_record(self, log_record):
204
241
205
242
def jsonify_log_record (self , log_record ):
206
243
"""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
+ )
212
251
213
252
def serialize_log_record (self , log_record : Dict [str , Any ]) -> str :
214
253
"""Returns the final representation of the log record."""
@@ -230,14 +269,14 @@ def format(self, record: logging.LogRecord) -> str:
230
269
231
270
# Display formatted exception, but allow overriding it in the
232
271
# 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
237
276
# Display formatted record of stack frames
238
277
# 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 )
241
280
242
281
log_record : Dict [str , Any ] = OrderedDict ()
243
282
self .add_fields (log_record , record , message_dict )
0 commit comments