From 566e7874eb458128c53ed0e83cb368b2930f94e7 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 22 Jul 2026 21:10:42 +0300 Subject: [PATCH] gh-70990: Support bytes addresses of Unix sockets in SysLogHandler Only a str address was recognized as a Unix domain socket. A bytes address, which socket.bind() accepts, fell through to the (host, port) branch and raised ValueError. Co-Authored-By: Claude Opus 4.8 (1M context) --- Doc/library/logging.handlers.rst | 6 ++- Lib/logging/handlers.py | 7 ++-- Lib/test/test_logging.py | 39 +++++++++++++++++++ ...6-07-22-17-35-24.gh-issue-70990.xVHKwt.rst | 4 ++ 4 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-22-17-35-24.gh-issue-70990.xVHKwt.rst diff --git a/Doc/library/logging.handlers.rst b/Doc/library/logging.handlers.rst index 5152c7561fa1f26..7230224b4fc5323 100644 --- a/Doc/library/logging.handlers.rst +++ b/Doc/library/logging.handlers.rst @@ -631,7 +631,8 @@ supports sending logging messages to a remote or local Unix syslog. the form of a ``(host, port)`` tuple. If *address* is not specified, ``('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 + 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, :const:`LOG_USER` is used. The type of socket opened depends on the *socktype* argument, which defaults to :const:`socket.SOCK_DGRAM` and thus @@ -664,6 +665,9 @@ supports sending logging messages to a remote or local Unix syslog. .. versionchanged:: 3.14 *timeout* was added. + .. versionchanged:: next + *address* can now be a :class:`bytes` object. + .. method:: close() Closes the socket to the remote host. diff --git a/Lib/logging/handlers.py b/Lib/logging/handlers.py index a5394d2dbea6494..c78a30763d9fa08 100644 --- a/Lib/logging/handlers.py +++ b/Lib/logging/handlers.py @@ -886,8 +886,9 @@ def __init__(self, address=('localhost', SYSLOG_UDP_PORT), """ Initialize a handler. - 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 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 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 @@ -938,7 +939,7 @@ def createSocket(self): address = self.address socktype = self.socktype - if isinstance(address, str): + if not isinstance(address, (list, tuple)): self.unixsocket = True # Syslog server may be unavailable during handler initialisation. # C's openlog() function also ignores connection errors. diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 06b3aa66fc47a31..09b4c12cdad4472 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -2138,6 +2138,45 @@ def setUp(self): self.addCleanup(os_helper.unlink, self.address) SysLogHandlerTest.setUp(self) + def test_bytes_address(self): + # The Unix socket address can also be specified as bytes. + if self.server_exception: + self.skipTest(self.server_exception) + hdlr = logging.handlers.SysLogHandler(os.fsencode(self.address)) + self.addCleanup(hdlr.close) + self.assertTrue(hdlr.unixsocket) + logger = logging.getLogger("slh-bytes") + logger.addHandler(hdlr) + self.addCleanup(logger.removeHandler, hdlr) + logger.error("sp\xe4m") + self.handled.wait(support.LONG_TIMEOUT) + self.assertEqual(self.log_output, b'<11>sp\xc3\xa4m\x00') + +@unittest.skipUnless(sys.platform in ('linux', 'android'), + 'Linux specific test') +class AbstractNamespaceSysLogHandlerTest(BaseTest): + + """Test for SysLogHandler with a socket in the abstract namespace.""" + + def check(self, address): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) + self.addCleanup(sock.close) + sock.bind(address) + sock.settimeout(support.LONG_TIMEOUT) + hdlr = logging.handlers.SysLogHandler(address) + self.addCleanup(hdlr.close) + self.assertTrue(hdlr.unixsocket) + hdlr.emit(logging.makeLogRecord({'msg': 'sp\xe4m'})) + self.assertEqual(sock.recv(1024), b'<12>sp\xc3\xa4m\x00') + + def test_str_address(self): + # A str address is encoded with the filesystem encoding. + self.check('\0' + os_helper.TESTFN) + + def test_bytes_address(self): + # The name is an arbitrary byte sequence, it need not be decodable. + self.check(b'\0test_logging_%d_\xff\xfe' % os.getpid()) + @unittest.skipUnless(socket_helper.IPV6_ENABLED, 'IPv6 support required for this test.') class IPv6SysLogHandlerTest(SysLogHandlerTest): diff --git a/Misc/NEWS.d/next/Library/2026-07-22-17-35-24.gh-issue-70990.xVHKwt.rst b/Misc/NEWS.d/next/Library/2026-07-22-17-35-24.gh-issue-70990.xVHKwt.rst new file mode 100644 index 000000000000000..4af22f88ddc413f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-22-17-35-24.gh-issue-70990.xVHKwt.rst @@ -0,0 +1,4 @@ +:class:`logging.handlers.SysLogHandler` now accepts a :class:`bytes` address +of a Unix domain socket, including an address in the abstract namespace. +Previously only :class:`str` was recognized, +and a :class:`bytes` address raised :exc:`ValueError`.