1- '''
1+ """
22This library is provided to allow standard python logging
33to output log data as JSON formatted strings
4- '''
4+ """
55import logging
66import json
77import re
8- from datetime import date , datetime , time , timezone
98import traceback
109import 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
1413from inspect import istraceback
1514
1817# skip natural LogRecord attributes
1918# http://docs.python.org/library/logging.html#logrecord-attributes
2019RESERVED_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
2847def 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 )
0 commit comments