mirror of https://github.com/python/cpython.git
logging: Added style option to Formatter to allow %, {} or himBHformatting.
This commit is contained in:
parent
7e9065cf8c
commit
a39c571061
|
@ -301,17 +301,29 @@ Formatters
|
|||
Formatter objects configure the final order, structure, and contents of the log
|
||||
message. Unlike the base :class:`logging.Handler` class, application code may
|
||||
instantiate formatter classes, although you could likely subclass the formatter
|
||||
if your application needs special behavior. The constructor takes two optional
|
||||
arguments: a message format string and a date format string. If there is no
|
||||
message format string, the default is to use the raw message. If there is no
|
||||
date format string, the default date format is::
|
||||
if your application needs special behavior. The constructor takes three
|
||||
optional arguments -- a message format string, a date format string and a style
|
||||
indicator.
|
||||
|
||||
.. method:: logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
|
||||
|
||||
If there is no message format string, the default is to use the
|
||||
raw message. If there is no date format string, the default date format is::
|
||||
|
||||
%Y-%m-%d %H:%M:%S
|
||||
|
||||
with the milliseconds tacked on at the end.
|
||||
with the milliseconds tacked on at the end. The ``style`` is one of `%`, '{'
|
||||
or '$'. If one of these is not specified, then '%' will be used.
|
||||
|
||||
The message format string uses ``%(<dictionary key>)s`` styled string
|
||||
substitution; the possible keys are documented in :ref:`formatter-objects`.
|
||||
If the ``style`` is '%', the message format string uses
|
||||
``%(<dictionary key>)s`` styled string substitution; the possible keys are
|
||||
documented in :ref:`formatter-objects`. If the style is '{', the message format
|
||||
string is assumed to be compatible with :meth:`str.format` (using keyword
|
||||
arguments), while if the style is '$' then the message format string should
|
||||
conform to what is expected by :meth:`string.Template.substitute`.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
Added the ``style`` parameter.
|
||||
|
||||
The following message format string will log the time in a human-readable
|
||||
format, the severity of the message, and the contents of the message, in that
|
||||
|
|
|
@ -395,18 +395,33 @@ class Formatter(object):
|
|||
|
||||
converter = time.localtime
|
||||
|
||||
def __init__(self, fmt=None, datefmt=None):
|
||||
def __init__(self, fmt=None, datefmt=None, style='%'):
|
||||
"""
|
||||
Initialize the formatter with specified format strings.
|
||||
|
||||
Initialize the formatter either with the specified format string, or a
|
||||
default as described above. Allow for specialized date formatting with
|
||||
the optional datefmt argument (if omitted, you get the ISO8601 format).
|
||||
|
||||
Use a style parameter of '%', '{' or '$' to specify that you want to
|
||||
use one of %-formatting, :meth:`str.format` (``{}``) formatting or
|
||||
:class:`string.Template` formatting in your format string.
|
||||
|
||||
.. versionchanged: 3.2
|
||||
Added the ``style`` parameter.
|
||||
"""
|
||||
if style not in ('%', '$', '{'):
|
||||
style = '%'
|
||||
self._style = style
|
||||
if fmt:
|
||||
self._fmt = fmt
|
||||
else:
|
||||
self._fmt = "%(message)s"
|
||||
if style == '%':
|
||||
self._fmt = "%(message)s"
|
||||
elif style == '{':
|
||||
self._fmt = '{message}'
|
||||
else:
|
||||
self._fmt = '${message}'
|
||||
self.datefmt = datefmt
|
||||
|
||||
def formatTime(self, record, datefmt=None):
|
||||
|
@ -432,7 +447,7 @@ def formatTime(self, record, datefmt=None):
|
|||
s = time.strftime(datefmt, ct)
|
||||
else:
|
||||
t = time.strftime("%Y-%m-%d %H:%M:%S", ct)
|
||||
s = "%s,%03d" % (t, record.msecs)
|
||||
s = "%s,%03d" % (t, record.msecs) # the use of % here is internal
|
||||
return s
|
||||
|
||||
def formatException(self, ei):
|
||||
|
@ -458,7 +473,14 @@ def usesTime(self):
|
|||
"""
|
||||
Check if the format uses the creation time of the record.
|
||||
"""
|
||||
return self._fmt.find("%(asctime)") >= 0
|
||||
if self._style == '%':
|
||||
result = self._fmt.find("%(asctime)") >= 0
|
||||
elif self._style == '$':
|
||||
result = self._fmt.find("{asctime}") >= 0
|
||||
else:
|
||||
result = self._fmt.find("$asctime") >= 0 or \
|
||||
self._fmt.find("${asctime}") >= 0
|
||||
return result
|
||||
|
||||
def format(self, record):
|
||||
"""
|
||||
|
@ -476,7 +498,14 @@ def format(self, record):
|
|||
record.message = record.getMessage()
|
||||
if self.usesTime():
|
||||
record.asctime = self.formatTime(record, self.datefmt)
|
||||
s = self._fmt % record.__dict__
|
||||
style = self._style
|
||||
if style == '%':
|
||||
s = self._fmt % record.__dict__
|
||||
elif style == '{':
|
||||
s = self._fmt.format(**record.__dict__)
|
||||
else:
|
||||
from string import Template
|
||||
s = Template(self._fmt).substitute(**record.__dict__)
|
||||
if record.exc_info:
|
||||
# Cache the traceback text to avoid converting it multiple times
|
||||
# (it's constant anyway)
|
||||
|
|
|
@ -1863,6 +1863,53 @@ def test_queue_handler(self):
|
|||
self.assertEqual(data.name, self.que_logger.name)
|
||||
self.assertEqual((data.msg, data.args), (msg, None))
|
||||
|
||||
class FormatterTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.common = {
|
||||
'name': 'formatter.test',
|
||||
'level': logging.DEBUG,
|
||||
'pathname': os.path.join('path', 'to', 'dummy.ext'),
|
||||
'lineno': 42,
|
||||
'exc_info': None,
|
||||
'func': None,
|
||||
'msg': 'Message with %d %s',
|
||||
'args': (2, 'placeholders'),
|
||||
}
|
||||
self.variants = {
|
||||
}
|
||||
|
||||
def get_record(self, name=None):
|
||||
result = dict(self.common)
|
||||
if name is not None:
|
||||
result.update(self.variants[name])
|
||||
return logging.makeLogRecord(result)
|
||||
|
||||
def test_percent(self):
|
||||
"Test %-formatting"
|
||||
r = self.get_record()
|
||||
f = logging.Formatter('${%(message)s}')
|
||||
self.assertEqual(f.format(r), '${Message with 2 placeholders}')
|
||||
f = logging.Formatter('%(random)s')
|
||||
self.assertRaises(KeyError, f.format, r)
|
||||
|
||||
def test_braces(self):
|
||||
"Test {}-formatting"
|
||||
r = self.get_record()
|
||||
f = logging.Formatter('$%{message}%$', style='{')
|
||||
self.assertEqual(f.format(r), '$%Message with 2 placeholders%$')
|
||||
f = logging.Formatter('{random}', style='{')
|
||||
self.assertRaises(KeyError, f.format, r)
|
||||
|
||||
def test_dollars(self):
|
||||
"Test $-formatting"
|
||||
r = self.get_record()
|
||||
f = logging.Formatter('$message', style='$')
|
||||
self.assertEqual(f.format(r), 'Message with 2 placeholders')
|
||||
f = logging.Formatter('$$%${message}%$$', style='$')
|
||||
self.assertEqual(f.format(r), '$%Message with 2 placeholders%$')
|
||||
f = logging.Formatter('${random}', style='$')
|
||||
self.assertRaises(KeyError, f.format, r)
|
||||
|
||||
class BaseFileTest(BaseTest):
|
||||
"Base class for handler tests that write log files"
|
||||
|
||||
|
@ -1945,6 +1992,7 @@ def test_main():
|
|||
CustomLevelsAndFiltersTest, MemoryHandlerTest,
|
||||
ConfigFileTest, SocketHandlerTest, MemoryTest,
|
||||
EncodingTest, WarningsTest, ConfigDictTest, ManagerTest,
|
||||
FormatterTest,
|
||||
LogRecordClassTest, ChildLoggerTest, QueueHandlerTest,
|
||||
RotatingFileHandlerTest,
|
||||
#TimedRotatingFileHandlerTest
|
||||
|
|
Loading…
Reference in New Issue