diff options
-rw-r--r-- | Doc/library/logging.rst | 26 | ||||
-rw-r--r-- | Lib/logging/__init__.py | 39 | ||||
-rw-r--r-- | Lib/test/test_logging.py | 48 | ||||
-rw-r--r-- | Misc/NEWS | 2 |
4 files changed, 103 insertions, 12 deletions
diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst index cb613ce..9023738 100644 --- a/Doc/library/logging.rst +++ b/Doc/library/logging.rst @@ -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 diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 7f217d4..0e29cf3 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -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 @@ class Formatter(object): 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 @@ class Formatter(object): """ 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 @@ class Formatter(object): 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) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index a738d7a..9aa6af3 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -1863,6 +1863,53 @@ class QueueHandlerTest(BaseTest): 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 @@ -51,6 +51,8 @@ Core and Builtins Library ------- +- logging: Added style option to Formatter to allow %, {} or $-formatting. + - Issue #5178: Added tempfile.TemporaryDirectory class that can be used as a context manager. |