Commit a39c5710 authored by Vinay Sajip's avatar Vinay Sajip

logging: Added style option to Formatter to allow %, {} or himBHformatting.

parent 7e9065cf
...@@ -301,17 +301,29 @@ Formatters ...@@ -301,17 +301,29 @@ Formatters
Formatter objects configure the final order, structure, and contents of the log Formatter objects configure the final order, structure, and contents of the log
message. Unlike the base :class:`logging.Handler` class, application code may message. Unlike the base :class:`logging.Handler` class, application code may
instantiate formatter classes, although you could likely subclass the formatter instantiate formatter classes, although you could likely subclass the formatter
if your application needs special behavior. The constructor takes two optional if your application needs special behavior. The constructor takes three
arguments: a message format string and a date format string. If there is no optional arguments -- a message format string, a date format string and a style
message format string, the default is to use the raw message. If there is no indicator.
date format string, the default date format is::
.. 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 %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 If the ``style`` is '%', the message format string uses
substitution; the possible keys are documented in :ref:`formatter-objects`. ``%(<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 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 format, the severity of the message, and the contents of the message, in that
......
...@@ -395,18 +395,33 @@ class Formatter(object): ...@@ -395,18 +395,33 @@ class Formatter(object):
converter = time.localtime 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 with specified format strings.
Initialize the formatter either with the specified format string, or a Initialize the formatter either with the specified format string, or a
default as described above. Allow for specialized date formatting with default as described above. Allow for specialized date formatting with
the optional datefmt argument (if omitted, you get the ISO8601 format). 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: if fmt:
self._fmt = fmt self._fmt = fmt
else: else:
if style == '%':
self._fmt = "%(message)s" self._fmt = "%(message)s"
elif style == '{':
self._fmt = '{message}'
else:
self._fmt = '${message}'
self.datefmt = datefmt self.datefmt = datefmt
def formatTime(self, record, datefmt=None): def formatTime(self, record, datefmt=None):
...@@ -432,7 +447,7 @@ class Formatter(object): ...@@ -432,7 +447,7 @@ class Formatter(object):
s = time.strftime(datefmt, ct) s = time.strftime(datefmt, ct)
else: else:
t = time.strftime("%Y-%m-%d %H:%M:%S", ct) 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 return s
def formatException(self, ei): def formatException(self, ei):
...@@ -458,7 +473,14 @@ class Formatter(object): ...@@ -458,7 +473,14 @@ class Formatter(object):
""" """
Check if the format uses the creation time of the record. 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): def format(self, record):
""" """
...@@ -476,7 +498,14 @@ class Formatter(object): ...@@ -476,7 +498,14 @@ class Formatter(object):
record.message = record.getMessage() record.message = record.getMessage()
if self.usesTime(): if self.usesTime():
record.asctime = self.formatTime(record, self.datefmt) record.asctime = self.formatTime(record, self.datefmt)
style = self._style
if style == '%':
s = self._fmt % record.__dict__ 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: if record.exc_info:
# Cache the traceback text to avoid converting it multiple times # Cache the traceback text to avoid converting it multiple times
# (it's constant anyway) # (it's constant anyway)
......
...@@ -1863,6 +1863,53 @@ class QueueHandlerTest(BaseTest): ...@@ -1863,6 +1863,53 @@ class QueueHandlerTest(BaseTest):
self.assertEqual(data.name, self.que_logger.name) self.assertEqual(data.name, self.que_logger.name)
self.assertEqual((data.msg, data.args), (msg, None)) 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): class BaseFileTest(BaseTest):
"Base class for handler tests that write log files" "Base class for handler tests that write log files"
...@@ -1945,6 +1992,7 @@ def test_main(): ...@@ -1945,6 +1992,7 @@ def test_main():
CustomLevelsAndFiltersTest, MemoryHandlerTest, CustomLevelsAndFiltersTest, MemoryHandlerTest,
ConfigFileTest, SocketHandlerTest, MemoryTest, ConfigFileTest, SocketHandlerTest, MemoryTest,
EncodingTest, WarningsTest, ConfigDictTest, ManagerTest, EncodingTest, WarningsTest, ConfigDictTest, ManagerTest,
FormatterTest,
LogRecordClassTest, ChildLoggerTest, QueueHandlerTest, LogRecordClassTest, ChildLoggerTest, QueueHandlerTest,
RotatingFileHandlerTest, RotatingFileHandlerTest,
#TimedRotatingFileHandlerTest #TimedRotatingFileHandlerTest
......
...@@ -51,6 +51,8 @@ Core and Builtins ...@@ -51,6 +51,8 @@ Core and Builtins
Library Library
------- -------
- logging: Added style option to Formatter to allow %, {} or $-formatting.
- Issue #5178: Added tempfile.TemporaryDirectory class that can be used - Issue #5178: Added tempfile.TemporaryDirectory class that can be used
as a context manager. as a context manager.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment