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.