https://github.com/python/cpython/commit/2eb49d278e081b0cde057c1ffc2e8cd24ae39225
commit: 2eb49d278e081b0cde057c1ffc2e8cd24ae39225
branch: main
author: Sebastian Pipping <[email protected]>
committer: vsajip <[email protected]>
date: 2025-05-14T07:45:00+01:00
summary:
gh-133577: Add parameter `formatter` to `logging.basicConfig` (GH-133578)
files:
A Misc/NEWS.d/next/Library/2025-05-07-14-36-30.gh-issue-133577.BggPk9.rst
M Doc/library/logging.rst
M Lib/logging/__init__.py
M Lib/test/test_logging.py
diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst
index 72190e97240514..4509da5891685f 100644
--- a/Doc/library/logging.rst
+++ b/Doc/library/logging.rst
@@ -1342,8 +1342,9 @@ functions.
.. function:: basicConfig(**kwargs)
- Does basic configuration for the logging system by creating a
- :class:`StreamHandler` with a default :class:`Formatter` and adding it to
the
+ Does basic configuration for the logging system by either creating a
+ :class:`StreamHandler` with a default :class:`Formatter`
+ or using the given *formatter* instance, and adding it to the
root logger. The functions :func:`debug`, :func:`info`, :func:`warning`,
:func:`error` and :func:`critical` will call :func:`basicConfig`
automatically
if no handlers are defined for the root logger.
@@ -1428,6 +1429,19 @@ functions.
| | which means that it will be treated the |
| | same as passing 'errors'. |
+--------------+---------------------------------------------+
+ | *formatter* | If specified, set this formatter instance |
+ | | (see :ref:`formatter-objects`) |
+ | | for all involved handlers. |
+ | | If not specified, the default is to create |
+ | | and use an instance of |
+ | | :class:`logging.Formatter` based on |
+ | | arguments *format*, *datefmt* and *style*. |
+ | | When *formatter* is specified together with |
+ | | any of the three arguments *format*, |
+ | | *datefmt* and *style*, a ``ValueError`` is |
+ | | raised to signal that these arguments would |
+ | | lose meaning otherwise. |
+ +--------------+---------------------------------------------+
.. versionchanged:: 3.2
The *style* argument was added.
@@ -1444,6 +1458,9 @@ functions.
.. versionchanged:: 3.9
The *encoding* and *errors* arguments were added.
+ .. versionchanged:: 3.15
+ The *formatter* argument was added.
+
.. function:: shutdown()
Informs the logging system to perform an orderly shutdown by flushing and
diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py
index 283a1055182b63..f2d1a02629d92b 100644
--- a/Lib/logging/__init__.py
+++ b/Lib/logging/__init__.py
@@ -2057,6 +2057,15 @@ def basicConfig(**kwargs):
created FileHandler, causing it to be used when the file is
opened in text mode. If not specified, the default value is
`backslashreplace`.
+ formatter If specified, set this formatter instance for all involved
+ handlers.
+ If not specified, the default is to create and use an instance of
+ `logging.Formatter` based on arguments 'format', 'datefmt' and
+ 'style'.
+ When 'formatter' is specified together with any of the three
+ arguments 'format', 'datefmt' and 'style', a `ValueError`
+ is raised to signal that these arguments would lose meaning
+ otherwise.
Note that you could specify a stream created using open(filename, mode)
rather than passing the filename and mode in. However, it should be
@@ -2079,6 +2088,9 @@ def basicConfig(**kwargs):
.. versionchanged:: 3.9
Added the ``encoding`` and ``errors`` parameters.
+
+ .. versionchanged:: 3.15
+ Added the ``formatter`` parameter.
"""
# Add thread safety in case someone mistakenly calls
# basicConfig() from multiple threads
@@ -2114,13 +2126,19 @@ def basicConfig(**kwargs):
stream = kwargs.pop("stream", None)
h = StreamHandler(stream)
handlers = [h]
- dfs = kwargs.pop("datefmt", None)
- style = kwargs.pop("style", '%')
- if style not in _STYLES:
- raise ValueError('Style must be one of: %s' % ','.join(
- _STYLES.keys()))
- fs = kwargs.pop("format", _STYLES[style][1])
- fmt = Formatter(fs, dfs, style)
+ fmt = kwargs.pop("formatter", None)
+ if fmt is None:
+ dfs = kwargs.pop("datefmt", None)
+ style = kwargs.pop("style", '%')
+ if style not in _STYLES:
+ raise ValueError('Style must be one of: %s' % ','.join(
+ _STYLES.keys()))
+ fs = kwargs.pop("format", _STYLES[style][1])
+ fmt = Formatter(fs, dfs, style)
+ else:
+ for forbidden_key in ("datefmt", "format", "style"):
+ if forbidden_key in kwargs:
+ raise ValueError(f"{forbidden_key!r} should not be
specified together with 'formatter'")
for h in handlers:
if h.formatter is None:
h.setFormatter(fmt)
diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py
index 1e5adcc8db13f6..fa5b1e438168bc 100644
--- a/Lib/test/test_logging.py
+++ b/Lib/test/test_logging.py
@@ -61,7 +61,7 @@
import weakref
from http.server import HTTPServer, BaseHTTPRequestHandler
-from unittest.mock import patch
+from unittest.mock import call, Mock, patch
from urllib.parse import urlparse, parse_qs
from socketserver import (ThreadingUDPServer, DatagramRequestHandler,
ThreadingTCPServer, StreamRequestHandler)
@@ -5655,12 +5655,19 @@ def test_incompatible(self):
assertRaises = self.assertRaises
handlers = [logging.StreamHandler()]
stream = sys.stderr
+ formatter = logging.Formatter()
assertRaises(ValueError, logging.basicConfig, filename='test.log',
stream=stream)
assertRaises(ValueError, logging.basicConfig, filename='test.log',
handlers=handlers)
assertRaises(ValueError, logging.basicConfig, stream=stream,
handlers=handlers)
+ assertRaises(ValueError, logging.basicConfig, formatter=formatter,
+ format='%(message)s')
+ assertRaises(ValueError, logging.basicConfig, formatter=formatter,
+ datefmt='%H:%M:%S')
+ assertRaises(ValueError, logging.basicConfig, formatter=formatter,
+ style='%')
# Issue 23207: test for invalid kwargs
assertRaises(ValueError, logging.basicConfig, loglevel=logging.INFO)
# Should pop both filename and filemode even if filename is None
@@ -5795,6 +5802,20 @@ def dummy_handle_error(record):
# didn't write anything due to the encoding error
self.assertEqual(data, r'')
+ def test_formatter_given(self):
+ mock_formatter = Mock()
+ mock_handler = Mock(formatter=None)
+ with patch("logging.Formatter") as mock_formatter_init:
+ logging.basicConfig(formatter=mock_formatter,
handlers=[mock_handler])
+ self.assertEqual(mock_handler.setFormatter.call_args_list,
[call(mock_formatter)])
+ self.assertEqual(mock_formatter_init.call_count, 0)
+
+ def test_formatter_not_given(self):
+ mock_handler = Mock(formatter=None)
+ with patch("logging.Formatter") as mock_formatter_init:
+ logging.basicConfig(handlers=[mock_handler])
+ self.assertEqual(mock_formatter_init.call_count, 1)
+
@support.requires_working_socket()
def test_log_taskName(self):
async def log_record():
diff --git
a/Misc/NEWS.d/next/Library/2025-05-07-14-36-30.gh-issue-133577.BggPk9.rst
b/Misc/NEWS.d/next/Library/2025-05-07-14-36-30.gh-issue-133577.BggPk9.rst
new file mode 100644
index 00000000000000..9d056983439e4b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-07-14-36-30.gh-issue-133577.BggPk9.rst
@@ -0,0 +1 @@
+Add parameter ``formatter`` to :func:`logging.basicConfig`.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]