diff --git a/Doc/library/logging.handlers.rst b/Doc/library/logging.handlers.rst index 7230224b4fc5323..b3af53920901a7b 100644 --- a/Doc/library/logging.handlers.rst +++ b/Doc/library/logging.handlers.rst @@ -633,7 +633,14 @@ supports sending logging messages to a remote or local Unix syslog. alternative to providing a ``(host, port)`` tuple is providing an address as a string or a :class:`bytes` object, 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. + In this case *socktype* and *timeout* are not applicable + and passing them raises :exc:`ValueError`. + 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 @@ -655,7 +662,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. @@ -667,6 +675,7 @@ supports sending logging messages to a remote or local Unix syslog. .. versionchanged:: next *address* can now be a :class:`bytes` object. + *address* can now be ``None`` to use the :mod:`syslog` module. .. method:: close() diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 06e831ea1e34631..aff7c0da5fcac06 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -353,6 +353,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 c78a30763d9fa08..e9c3ef42b5f70b1 100644 --- a/Lib/logging/handlers.py +++ b/Lib/logging/handlers.py @@ -889,6 +889,9 @@ def __init__(self, address=('localhost', SYSLOG_UDP_PORT), If address is specified as a string or bytes, 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 @@ -902,7 +905,17 @@ 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. + if socktype is not None: + raise ValueError("socktype is not supported if address is None") + if timeout is not None: + raise ValueError("timeout is not supported if address is None") + import syslog + self.unixsocket = False + self.syslog = syslog + else: + self.createSocket() def _connect_unixsocket(self, address): use_socktype = self.socktype @@ -1024,13 +1037,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 ccc7cce86883c89..4425eb05c4dfddd 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: @@ -2194,6 +2199,31 @@ 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) + self.assertFalse(h.unixsocket) + 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) + + def test_reject_socktype_and_timeout(self): + with self.assertRaises(ValueError): + logging.handlers.SysLogHandler(address=None, + socktype=socket.SOCK_DGRAM) + with self.assertRaises(ValueError): + logging.handlers.SysLogHandler(address=None, timeout=1) + @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.