@@ -138,6 +138,7 @@ def __init__(
138
138
* ,
139
139
prefix : str = "" ,
140
140
rename_fields : Optional [Dict [str , str ]] = None ,
141
+ rename_fields_keep_missing : bool = False ,
141
142
static_fields : Optional [Dict [str , Any ]] = None ,
142
143
reserved_attrs : Optional [Sequence [str ]] = None ,
143
144
timestamp : Union [bool , str ] = False ,
@@ -154,7 +155,8 @@ def __init__(
154
155
prefix: an optional string prefix added at the beginning of
155
156
the formatted string
156
157
rename_fields: an optional dict, used to rename field names in the output.
157
- Rename message to @message: {'message': '@message'}
158
+ Rename `message` to `@message`: `{'message': '@message'}`
159
+ rename_fields_keep_missing: When renaming fields, include missing fields in the output.
158
160
static_fields: an optional dict, used to add fields with static values to all logs
159
161
reserved_attrs: an optional list of fields that will be skipped when
160
162
outputting json log record. Defaults to all log record attributes:
@@ -164,14 +166,18 @@ def __init__(
164
166
to log record using string as key. If True boolean is passed, timestamp key
165
167
will be "timestamp". Defaults to False/off.
166
168
167
- *Changed in 3.1*: you can now use custom values for style by setting validate to `False`.
168
- The value is stored in `self._style` as a string. The `parse` method will need to be
169
- overridden in order to support the new style.
169
+ *Changed in 3.1*:
170
+
171
+ - you can now use custom values for style by setting validate to `False`.
172
+ The value is stored in `self._style` as a string. The `parse` method will need to be
173
+ overridden in order to support the new style.
174
+ - Renaming fields now preserves the order that fields were added in and avoids adding
175
+ missing fields. The original behaviour, missing fields have a value of `None`, is still
176
+ available by setting `rename_fields_keep_missing` to `True`.
170
177
"""
171
178
## logging.Formatter compatibility
172
179
## ---------------------------------------------------------------------
173
- # Note: validate added in 3.8
174
- # Note: defaults added in 3.10
180
+ # Note: validate added in 3.8, defaults added in 3.10
175
181
if style in logging ._STYLES :
176
182
_style = logging ._STYLES [style ][0 ](fmt ) # type: ignore[operator]
177
183
if validate :
@@ -192,6 +198,7 @@ def __init__(
192
198
## ---------------------------------------------------------------------
193
199
self .prefix = prefix
194
200
self .rename_fields = rename_fields if rename_fields is not None else {}
201
+ self .rename_fields_keep_missing = rename_fields_keep_missing
195
202
self .static_fields = static_fields if static_fields is not None else {}
196
203
self .reserved_attrs = set (reserved_attrs if reserved_attrs is not None else RESERVED_ATTRS )
197
204
self .timestamp = timestamp
@@ -215,6 +222,7 @@ def format(self, record: logging.LogRecord) -> str:
215
222
record .message = ""
216
223
else :
217
224
record .message = record .getMessage ()
225
+
218
226
# only format time if needed
219
227
if "asctime" in self ._required_fields :
220
228
record .asctime = self .formatTime (record , self .datefmt )
@@ -225,6 +233,7 @@ def format(self, record: logging.LogRecord) -> str:
225
233
message_dict ["exc_info" ] = self .formatException (record .exc_info )
226
234
if not message_dict .get ("exc_info" ) and record .exc_text :
227
235
message_dict ["exc_info" ] = record .exc_text
236
+
228
237
# Display formatted record of stack frames
229
238
# default format is a string returned from :func:`traceback.print_stack`
230
239
if record .stack_info and not message_dict .get ("stack_info" ):
@@ -289,13 +298,16 @@ def add_fields(
289
298
Args:
290
299
log_record: data that will be logged
291
300
record: the record to extract data from
292
- message_dict: ???
301
+ message_dict: dictionary that was logged instead of a message. e.g
302
+ `logger.info({"is_this_message_dict": True})`
293
303
"""
294
304
for field in self ._required_fields :
295
- log_record [field ] = record .__dict__ .get (field )
305
+ log_record [self ._get_rename (field )] = record .__dict__ .get (field )
306
+
307
+ for data_dict in [self .static_fields , message_dict ]:
308
+ for key , value in data_dict .items ():
309
+ log_record [self ._get_rename (key )] = value
296
310
297
- log_record .update (self .static_fields )
298
- log_record .update (message_dict )
299
311
merge_record_extra (
300
312
record ,
301
313
log_record ,
@@ -304,19 +316,19 @@ def add_fields(
304
316
)
305
317
306
318
if self .timestamp :
307
- # TODO: Can this use isinstance instead?
308
- # pylint: disable=unidiomatic-typecheck
309
- key = self .timestamp if type (self .timestamp ) == str else "timestamp"
310
- log_record [key ] = datetime .fromtimestamp (record .created , tz = timezone .utc )
311
-
312
- self ._perform_rename_log_fields (log_record )
319
+ key = self .timestamp if isinstance (self .timestamp , str ) else "timestamp"
320
+ log_record [self ._get_rename (key )] = datetime .fromtimestamp (
321
+ record .created , tz = timezone .utc
322
+ )
323
+
324
+ if self .rename_fields_keep_missing :
325
+ for field in self .rename_fields .values ():
326
+ if field not in log_record :
327
+ log_record [field ] = None
313
328
return
314
329
315
- def _perform_rename_log_fields (self , log_record : Dict [str , Any ]) -> None :
316
- for old_field_name , new_field_name in self .rename_fields .items ():
317
- log_record [new_field_name ] = log_record [old_field_name ]
318
- del log_record [old_field_name ]
319
- return
330
+ def _get_rename (self , key : str ) -> str :
331
+ return self .rename_fields .get (key , key )
320
332
321
333
# Child Methods
322
334
# ..........................................................................
0 commit comments