bpo-40884: Added defaults parameter for logging.Formatter (GH-20668)
Docs and tests are underway. Automerge-Triggered-By: @vsajip
This commit is contained in:
parent
ddbeb2f3e0
commit
8f192d12af
|
@ -529,7 +529,8 @@ The useful mapping keys in a :class:`LogRecord` are given in the section on
|
||||||
:ref:`logrecord-attributes`.
|
:ref:`logrecord-attributes`.
|
||||||
|
|
||||||
|
|
||||||
.. class:: Formatter(fmt=None, datefmt=None, style='%', validate=True)
|
.. class:: Formatter(fmt=None, datefmt=None, style='%', validate=True, *,
|
||||||
|
defaults=None)
|
||||||
|
|
||||||
Returns a new instance of the :class:`Formatter` class. The instance is
|
Returns a new instance of the :class:`Formatter` class. The instance is
|
||||||
initialized with a format string for the message as a whole, as well as a
|
initialized with a format string for the message as a whole, as well as a
|
||||||
|
@ -545,6 +546,10 @@ The useful mapping keys in a :class:`LogRecord` are given in the section on
|
||||||
:ref:`formatting-styles` for more information on using {- and $-formatting
|
:ref:`formatting-styles` for more information on using {- and $-formatting
|
||||||
for log messages.
|
for log messages.
|
||||||
|
|
||||||
|
The *defaults* parameter can be a dictionary with default values to use in
|
||||||
|
custom fields. For example:
|
||||||
|
``logging.Formatter('%(ip)s %(message)s', defaults={"ip": None})``
|
||||||
|
|
||||||
.. versionchanged:: 3.2
|
.. versionchanged:: 3.2
|
||||||
The *style* parameter was added.
|
The *style* parameter was added.
|
||||||
|
|
||||||
|
@ -553,6 +558,9 @@ The useful mapping keys in a :class:`LogRecord` are given in the section on
|
||||||
will raise a ``ValueError``.
|
will raise a ``ValueError``.
|
||||||
For example: ``logging.Formatter('%(asctime)s - %(message)s', style='{')``.
|
For example: ``logging.Formatter('%(asctime)s - %(message)s', style='{')``.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.10
|
||||||
|
The *defaults* parameter was added.
|
||||||
|
|
||||||
.. method:: format(record)
|
.. method:: format(record)
|
||||||
|
|
||||||
The record's attribute dictionary is used as the operand to a string
|
The record's attribute dictionary is used as the operand to a string
|
||||||
|
|
|
@ -411,8 +411,9 @@ class PercentStyle(object):
|
||||||
asctime_search = '%(asctime)'
|
asctime_search = '%(asctime)'
|
||||||
validation_pattern = re.compile(r'%\(\w+\)[#0+ -]*(\*|\d+)?(\.(\*|\d+))?[diouxefgcrsa%]', re.I)
|
validation_pattern = re.compile(r'%\(\w+\)[#0+ -]*(\*|\d+)?(\.(\*|\d+))?[diouxefgcrsa%]', re.I)
|
||||||
|
|
||||||
def __init__(self, fmt):
|
def __init__(self, fmt, *, defaults=None):
|
||||||
self._fmt = fmt or self.default_format
|
self._fmt = fmt or self.default_format
|
||||||
|
self._defaults = defaults
|
||||||
|
|
||||||
def usesTime(self):
|
def usesTime(self):
|
||||||
return self._fmt.find(self.asctime_search) >= 0
|
return self._fmt.find(self.asctime_search) >= 0
|
||||||
|
@ -423,7 +424,11 @@ class PercentStyle(object):
|
||||||
raise ValueError("Invalid format '%s' for '%s' style" % (self._fmt, self.default_format[0]))
|
raise ValueError("Invalid format '%s' for '%s' style" % (self._fmt, self.default_format[0]))
|
||||||
|
|
||||||
def _format(self, record):
|
def _format(self, record):
|
||||||
return self._fmt % record.__dict__
|
if defaults := self._defaults:
|
||||||
|
values = defaults | record.__dict__
|
||||||
|
else:
|
||||||
|
values = record.__dict__
|
||||||
|
return self._fmt % values
|
||||||
|
|
||||||
def format(self, record):
|
def format(self, record):
|
||||||
try:
|
try:
|
||||||
|
@ -441,7 +446,11 @@ class StrFormatStyle(PercentStyle):
|
||||||
field_spec = re.compile(r'^(\d+|\w+)(\.\w+|\[[^]]+\])*$')
|
field_spec = re.compile(r'^(\d+|\w+)(\.\w+|\[[^]]+\])*$')
|
||||||
|
|
||||||
def _format(self, record):
|
def _format(self, record):
|
||||||
return self._fmt.format(**record.__dict__)
|
if defaults := self._defaults:
|
||||||
|
values = defaults | record.__dict__
|
||||||
|
else:
|
||||||
|
values = record.__dict__
|
||||||
|
return self._fmt.format(**values)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
"""Validate the input format, ensure it is the correct string formatting style"""
|
"""Validate the input format, ensure it is the correct string formatting style"""
|
||||||
|
@ -467,8 +476,8 @@ class StringTemplateStyle(PercentStyle):
|
||||||
asctime_format = '${asctime}'
|
asctime_format = '${asctime}'
|
||||||
asctime_search = '${asctime}'
|
asctime_search = '${asctime}'
|
||||||
|
|
||||||
def __init__(self, fmt):
|
def __init__(self, *args, **kwargs):
|
||||||
self._fmt = fmt or self.default_format
|
super().__init__(*args, **kwargs)
|
||||||
self._tpl = Template(self._fmt)
|
self._tpl = Template(self._fmt)
|
||||||
|
|
||||||
def usesTime(self):
|
def usesTime(self):
|
||||||
|
@ -490,7 +499,11 @@ class StringTemplateStyle(PercentStyle):
|
||||||
raise ValueError('invalid format: no fields')
|
raise ValueError('invalid format: no fields')
|
||||||
|
|
||||||
def _format(self, record):
|
def _format(self, record):
|
||||||
return self._tpl.substitute(**record.__dict__)
|
if defaults := self._defaults:
|
||||||
|
values = defaults | record.__dict__
|
||||||
|
else:
|
||||||
|
values = record.__dict__
|
||||||
|
return self._tpl.substitute(**values)
|
||||||
|
|
||||||
|
|
||||||
BASIC_FORMAT = "%(levelname)s:%(name)s:%(message)s"
|
BASIC_FORMAT = "%(levelname)s:%(name)s:%(message)s"
|
||||||
|
@ -546,7 +559,8 @@ class Formatter(object):
|
||||||
|
|
||||||
converter = time.localtime
|
converter = time.localtime
|
||||||
|
|
||||||
def __init__(self, fmt=None, datefmt=None, style='%', validate=True):
|
def __init__(self, fmt=None, datefmt=None, style='%', validate=True, *,
|
||||||
|
defaults=None):
|
||||||
"""
|
"""
|
||||||
Initialize the formatter with specified format strings.
|
Initialize the formatter with specified format strings.
|
||||||
|
|
||||||
|
@ -565,7 +579,7 @@ class Formatter(object):
|
||||||
if style not in _STYLES:
|
if style not in _STYLES:
|
||||||
raise ValueError('Style must be one of: %s' % ','.join(
|
raise ValueError('Style must be one of: %s' % ','.join(
|
||||||
_STYLES.keys()))
|
_STYLES.keys()))
|
||||||
self._style = _STYLES[style][0](fmt)
|
self._style = _STYLES[style][0](fmt, defaults=defaults)
|
||||||
if validate:
|
if validate:
|
||||||
self._style.validate()
|
self._style.validate()
|
||||||
|
|
||||||
|
|
|
@ -3710,6 +3710,9 @@ class FormatterTest(unittest.TestCase):
|
||||||
'args': (2, 'placeholders'),
|
'args': (2, 'placeholders'),
|
||||||
}
|
}
|
||||||
self.variants = {
|
self.variants = {
|
||||||
|
'custom': {
|
||||||
|
'custom': 1234
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_record(self, name=None):
|
def get_record(self, name=None):
|
||||||
|
@ -3926,6 +3929,26 @@ class FormatterTest(unittest.TestCase):
|
||||||
)
|
)
|
||||||
self.assertRaises(ValueError, logging.Formatter, '${asctime', style='$')
|
self.assertRaises(ValueError, logging.Formatter, '${asctime', style='$')
|
||||||
|
|
||||||
|
def test_defaults_parameter(self):
|
||||||
|
fmts = ['%(custom)s %(message)s', '{custom} {message}', '$custom $message']
|
||||||
|
styles = ['%', '{', '$']
|
||||||
|
for fmt, style in zip(fmts, styles):
|
||||||
|
f = logging.Formatter(fmt, style=style, defaults={'custom': 'Default'})
|
||||||
|
r = self.get_record()
|
||||||
|
self.assertEqual(f.format(r), 'Default Message with 2 placeholders')
|
||||||
|
r = self.get_record("custom")
|
||||||
|
self.assertEqual(f.format(r), '1234 Message with 2 placeholders')
|
||||||
|
|
||||||
|
# Without default
|
||||||
|
f = logging.Formatter(fmt, style=style)
|
||||||
|
r = self.get_record()
|
||||||
|
self.assertRaises(ValueError, f.format, r)
|
||||||
|
|
||||||
|
# Non-existing default is ignored
|
||||||
|
f = logging.Formatter(fmt, style=style, defaults={'Non-existing': 'Default'})
|
||||||
|
r = self.get_record("custom")
|
||||||
|
self.assertEqual(f.format(r), '1234 Message with 2 placeholders')
|
||||||
|
|
||||||
def test_invalid_style(self):
|
def test_invalid_style(self):
|
||||||
self.assertRaises(ValueError, logging.Formatter, None, None, 'x')
|
self.assertRaises(ValueError, logging.Formatter, None, None, 'x')
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Added a `defaults` parameter to :class:`logging.Formatter`, to allow
|
||||||
|
specifying default values for custom fields. Patch by Asaf Alon and Bar
|
||||||
|
Harel.
|
Loading…
Reference in New Issue