gh-126400: Add TCP socket timeout to SysLogHandler to prevent blocking (GH-126716)

Co-authored-by: Vinay Sajip <vinay_sajip@yahoo.co.uk>
This commit is contained in:
Hod 2025-01-29 21:37:43 +02:00 committed by GitHub
parent 002c4e2982
commit fdcedfd3cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 27 additions and 2 deletions

View file

@ -613,7 +613,7 @@ The :class:`SysLogHandler` class, located in the :mod:`logging.handlers` module,
supports sending logging messages to a remote or local Unix syslog.
.. class:: SysLogHandler(address=('localhost', SYSLOG_UDP_PORT), facility=LOG_USER, socktype=socket.SOCK_DGRAM)
.. class:: SysLogHandler(address=('localhost', SYSLOG_UDP_PORT), facility=LOG_USER, socktype=socket.SOCK_DGRAM, timeout=None)
Returns a new instance of the :class:`SysLogHandler` class intended to
communicate with a remote Unix machine whose address is given by *address* in
@ -626,6 +626,11 @@ supports sending logging messages to a remote or local Unix syslog.
*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
daemons such as rsyslog), specify a value of :const:`socket.SOCK_STREAM`.
If *timeout* is specified, it sets a timeout (in seconds) for the socket operations.
This can help prevent the program from hanging indefinitely if the syslog server is
unreachable. By default, *timeout* is ``None``, meaning no timeout is applied.
Note that if your server is not listening on UDP port 514,
:class:`SysLogHandler` may appear not to work. In that case, check what
@ -645,6 +650,8 @@ supports sending logging messages to a remote or local Unix syslog.
.. versionchanged:: 3.2
*socktype* was added.
.. versionchanged:: 3.14
*timeout* was added.
.. method:: close()

View file

@ -855,7 +855,7 @@ class SysLogHandler(logging.Handler):
}
def __init__(self, address=('localhost', SYSLOG_UDP_PORT),
facility=LOG_USER, socktype=None):
facility=LOG_USER, socktype=None, timeout=None):
"""
Initialize a handler.
@ -872,6 +872,7 @@ class SysLogHandler(logging.Handler):
self.address = address
self.facility = facility
self.socktype = socktype
self.timeout = timeout
self.socket = None
self.createSocket()
@ -933,6 +934,8 @@ class SysLogHandler(logging.Handler):
err = sock = None
try:
sock = socket.socket(af, socktype, proto)
if self.timeout:
sock.settimeout(self.timeout)
if socktype == socket.SOCK_STREAM:
sock.connect(sa)
break

View file

@ -22,6 +22,7 @@ import logging
import logging.handlers
import logging.config
import codecs
import configparser
import copy
@ -2095,6 +2096,18 @@ class SysLogHandlerTest(BaseTest):
self.handled.wait(support.LONG_TIMEOUT)
self.assertEqual(self.log_output, b'<11>sp\xc3\xa4m\x00')
@patch('socket.socket')
def test_tcp_timeout(self, mock_socket):
instance_mock_sock = mock_socket.return_value
instance_mock_sock.connect.side_effect = socket.timeout
with self.assertRaises(socket.timeout):
logging.handlers.SysLogHandler(address=('localhost', 514),
socktype=socket.SOCK_STREAM,
timeout=1)
instance_mock_sock.close.assert_called()
@unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required")
class UnixSysLogHandlerTest(SysLogHandlerTest):

View file

@ -0,0 +1,2 @@
Add a socket *timeout* keyword argument to
:class:`logging.handlers.SysLogHandler`.