Skip to content

Commit 46acb79

Browse files
gh-70990: Support bytes addresses of Unix sockets in SysLogHandler (GH-154500)
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) <noreply@anthropic.com>
1 parent 6cb93bd commit 46acb79

4 files changed

Lines changed: 52 additions & 4 deletions

File tree

Doc/library/logging.handlers.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,8 @@ supports sending logging messages to a remote or local Unix syslog.
631631
the form of a ``(host, port)`` tuple. If *address* is not specified,
632632
``('localhost', 514)`` is used. The address is used to open a socket. An
633633
alternative to providing a ``(host, port)`` tuple is providing an address as a
634-
string, for example '/dev/log'. In this case, a Unix domain socket is used to
634+
string or a :class:`bytes` object, for example '/dev/log'.
635+
In this case, a Unix domain socket is used to
635636
send the message to the syslog. If *facility* is not specified,
636637
:const:`LOG_USER` is used. The type of socket opened depends on the
637638
*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.
664665
.. versionchanged:: 3.14
665666
*timeout* was added.
666667

668+
.. versionchanged:: next
669+
*address* can now be a :class:`bytes` object.
670+
667671
.. method:: close()
668672

669673
Closes the socket to the remote host.

Lib/logging/handlers.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -886,8 +886,9 @@ def __init__(self, address=('localhost', SYSLOG_UDP_PORT),
886886
"""
887887
Initialize a handler.
888888
889-
If address is specified as a string, a UNIX socket is used. To log to a
890-
local syslogd, "SysLogHandler(address="/dev/log")" can be used.
889+
If address is specified as a string or bytes, a UNIX socket is used.
890+
To log to a local syslogd, "SysLogHandler(address="/dev/log")" can be
891+
used.
891892
If facility is not specified, LOG_USER is used. If socktype is
892893
specified as socket.SOCK_DGRAM or socket.SOCK_STREAM, that specific
893894
socket type will be used. For Unix sockets, you can also specify a
@@ -938,7 +939,7 @@ def createSocket(self):
938939
address = self.address
939940
socktype = self.socktype
940941

941-
if isinstance(address, str):
942+
if not isinstance(address, (list, tuple)):
942943
self.unixsocket = True
943944
# Syslog server may be unavailable during handler initialisation.
944945
# C's openlog() function also ignores connection errors.

Lib/test/test_logging.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2138,6 +2138,45 @@ def setUp(self):
21382138
self.addCleanup(os_helper.unlink, self.address)
21392139
SysLogHandlerTest.setUp(self)
21402140

2141+
def test_bytes_address(self):
2142+
# The Unix socket address can also be specified as bytes.
2143+
if self.server_exception:
2144+
self.skipTest(self.server_exception)
2145+
hdlr = logging.handlers.SysLogHandler(os.fsencode(self.address))
2146+
self.addCleanup(hdlr.close)
2147+
self.assertTrue(hdlr.unixsocket)
2148+
logger = logging.getLogger("slh-bytes")
2149+
logger.addHandler(hdlr)
2150+
self.addCleanup(logger.removeHandler, hdlr)
2151+
logger.error("sp\xe4m")
2152+
self.handled.wait(support.LONG_TIMEOUT)
2153+
self.assertEqual(self.log_output, b'<11>sp\xc3\xa4m\x00')
2154+
2155+
@unittest.skipUnless(sys.platform in ('linux', 'android'),
2156+
'Linux specific test')
2157+
class AbstractNamespaceSysLogHandlerTest(BaseTest):
2158+
2159+
"""Test for SysLogHandler with a socket in the abstract namespace."""
2160+
2161+
def check(self, address):
2162+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
2163+
self.addCleanup(sock.close)
2164+
sock.bind(address)
2165+
sock.settimeout(support.LONG_TIMEOUT)
2166+
hdlr = logging.handlers.SysLogHandler(address)
2167+
self.addCleanup(hdlr.close)
2168+
self.assertTrue(hdlr.unixsocket)
2169+
hdlr.emit(logging.makeLogRecord({'msg': 'sp\xe4m'}))
2170+
self.assertEqual(sock.recv(1024), b'<12>sp\xc3\xa4m\x00')
2171+
2172+
def test_str_address(self):
2173+
# A str address is encoded with the filesystem encoding.
2174+
self.check('\0' + os_helper.TESTFN)
2175+
2176+
def test_bytes_address(self):
2177+
# The name is an arbitrary byte sequence, it need not be decodable.
2178+
self.check(b'\0test_logging_%d_\xff\xfe' % os.getpid())
2179+
21412180
@unittest.skipUnless(socket_helper.IPV6_ENABLED,
21422181
'IPv6 support required for this test.')
21432182
class IPv6SysLogHandlerTest(SysLogHandlerTest):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:class:`logging.handlers.SysLogHandler` now accepts a :class:`bytes` address
2+
of a Unix domain socket, including an address in the abstract namespace.
3+
Previously only :class:`str` was recognized,
4+
and a :class:`bytes` address raised :exc:`ValueError`.

0 commit comments

Comments
 (0)