From 44626e904c14c58ccc5af8c7753f222e9ae9f6f7 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 23 Jul 2026 18:52:02 +0300 Subject: [PATCH] gh-96339: Support address=None in SysLogHandler to use the syslog module SysLogHandler now logs to the local system logger via the syslog module when the address is None. This works even where there is no syslog socket, such as on recent versions of macOS. Co-Authored-By: Claude Opus 4.8 (1M context) --- Doc/library/logging.handlers.rst | 12 ++++++++-- Doc/whatsnew/3.16.rst | 5 ++++ Lib/logging/handlers.py | 23 +++++++++++++++---- Lib/test/test_logging.py | 22 ++++++++++++++++++ ...6-07-23-15-50-43.gh-issue-96339.rs3Pr1.rst | 3 +++ 5 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-23-15-50-43.gh-issue-96339.rs3Pr1.rst diff --git a/Doc/library/logging.handlers.rst b/Doc/library/logging.handlers.rst index 5152c7561fa1f26..a1f0f05c2366fa8 100644 --- a/Doc/library/logging.handlers.rst +++ b/Doc/library/logging.handlers.rst @@ -632,7 +632,11 @@ supports sending logging messages to a remote or local Unix syslog. ``('localhost', 514)`` is used. The address is used to open a socket. An alternative to providing a ``(host, port)`` tuple is providing an address as a string, for example '/dev/log'. In this case, a Unix domain socket is used to - send the message to the syslog. If *facility* is not specified, + send the message to the syslog. + If *address* is ``None``, the :mod:`syslog` module is used to log to the + local system logger; this works even where there is no syslog socket, such + as on recent versions of macOS. + If *facility* is not specified, :const:`LOG_USER` is used. The type of socket opened depends on the *socktype* argument, which defaults to :const:`socket.SOCK_DGRAM` and thus opens a UDP socket. To open a TCP socket (for use with the newer syslog @@ -654,7 +658,8 @@ supports sending logging messages to a remote or local Unix syslog. .. note:: On macOS 12.x (Monterey), Apple has changed the behaviour of their syslog daemon - it no longer listens on a domain socket. Therefore, you cannot - expect :class:`SysLogHandler` to work on this system. + expect :class:`SysLogHandler` to work on this system with the default + *address*. Pass ``address=None`` to use the :mod:`syslog` module instead. See :gh:`91070` for more information. @@ -664,6 +669,9 @@ supports sending logging messages to a remote or local Unix syslog. .. versionchanged:: 3.14 *timeout* was added. + .. versionchanged:: next + *address* can now be ``None`` to use the :mod:`syslog` module. + .. method:: close() Closes the socket to the remote host. diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 662defa709a246c..f79127cca4d92d1 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -349,6 +349,11 @@ logging before the rotation interval expires. (Contributed by Iván Márton and Serhiy Storchaka in :gh:`84649`.) +* :class:`~logging.handlers.SysLogHandler` now accepts ``address=None`` to log + to the local system logger via the :mod:`syslog` module, which works even + where there is no syslog socket, such as on recent versions of macOS. + (Contributed by Serhiy Storchaka in :gh:`96339`.) + lzma ---- diff --git a/Lib/logging/handlers.py b/Lib/logging/handlers.py index a5394d2dbea6494..71220f54506d43a 100644 --- a/Lib/logging/handlers.py +++ b/Lib/logging/handlers.py @@ -888,6 +888,9 @@ def __init__(self, address=('localhost', SYSLOG_UDP_PORT), If address is specified as a string, a UNIX socket is used. To log to a local syslogd, "SysLogHandler(address="/dev/log")" can be used. + If address is None, the syslog module is used to log to the local + system logger; this works even where there is no syslog socket, such + as on recent versions of macOS. If facility is not specified, LOG_USER is used. If socktype is specified as socket.SOCK_DGRAM or socket.SOCK_STREAM, that specific socket type will be used. For Unix sockets, you can also specify a @@ -901,7 +904,12 @@ def __init__(self, address=('localhost', SYSLOG_UDP_PORT), self.socktype = socktype self.timeout = timeout self.socket = None - self.createSocket() + if address is None: + # Use the syslog module to log to the local system logger. + import syslog + self.syslog = syslog + else: + self.createSocket() def _connect_unixsocket(self, address): use_socktype = self.socktype @@ -1023,13 +1031,18 @@ def emit(self, record): msg = self.format(record) if self.ident: msg = self.ident + msg - if self.append_nul: - msg += '\000' # We need to convert record level to lowercase, maybe this will # change in the future. - prio = '<%d>' % self.encodePriority(self.facility, - self.mapPriority(record.levelname)) + prio = self.encodePriority(self.facility, + self.mapPriority(record.levelname)) + if self.address is None: + self.syslog.syslog(prio, msg) + return + + if self.append_nul: + msg += '\000' + prio = '<%d>' % prio prio = prio.encode('utf-8') # Message is a string. Convert to bytes as required by RFC 5424 msg = msg.encode('utf-8') diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 06b3aa66fc47a31..8e4d20065463fac 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -72,6 +72,11 @@ except ImportError: win32evtlog = win32evtlogutil = pywintypes = None +try: + import syslog +except ImportError: + syslog = None + try: import zlib except ImportError: @@ -2155,6 +2160,23 @@ def tearDown(self): self.server_class.address_family = socket.AF_INET super(IPv6SysLogHandlerTest, self).tearDown() +@unittest.skipUnless(syslog, 'syslog module required') +class LocalSysLogHandlerTest(BaseTest): + + """Test for SysLogHandler using the syslog module (address=None).""" + + def test_emit(self): + h = logging.handlers.SysLogHandler(address=None) + self.addCleanup(h.close) + h.setFormatter(logging.Formatter('%(message)s')) + record = self.next_message() + logrec = logging.makeLogRecord({'msg': record, 'levelname': 'WARNING', + 'levelno': logging.WARNING}) + with patch.object(syslog, 'syslog') as mock_syslog: + h.emit(logrec) + mock_syslog.assert_called_once_with( + syslog.LOG_USER | syslog.LOG_WARNING, record) + @support.requires_working_socket() @threading_helper.requires_working_threading() class HTTPHandlerTest(BaseTest): diff --git a/Misc/NEWS.d/next/Library/2026-07-23-15-50-43.gh-issue-96339.rs3Pr1.rst b/Misc/NEWS.d/next/Library/2026-07-23-15-50-43.gh-issue-96339.rs3Pr1.rst new file mode 100644 index 000000000000000..b91ef62ec8b76e9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-23-15-50-43.gh-issue-96339.rs3Pr1.rst @@ -0,0 +1,3 @@ +:class:`logging.handlers.SysLogHandler` now accepts ``address=None`` to log to +the local system logger via the :mod:`syslog` module. This works even where +there is no syslog socket, such as on recent versions of macOS.