Commit 5421f35d authored by Vinay Sajip's avatar Vinay Sajip

logging: added support for Unix domain sockets to SocketHandler and DatagramHandler.

parent 55798896
...@@ -900,10 +900,10 @@ provided: ...@@ -900,10 +900,10 @@ provided:
disk files, rotating the log file at certain timed intervals. disk files, rotating the log file at certain timed intervals.
#. :class:`~handlers.SocketHandler` instances send messages to TCP/IP #. :class:`~handlers.SocketHandler` instances send messages to TCP/IP
sockets. sockets. Since 3.4, Unix domain sockets are also supported.
#. :class:`~handlers.DatagramHandler` instances send messages to UDP #. :class:`~handlers.DatagramHandler` instances send messages to UDP
sockets. sockets. Since 3.4, Unix domain sockets are also supported.
#. :class:`~handlers.SMTPHandler` instances send messages to a designated #. :class:`~handlers.SMTPHandler` instances send messages to a designated
email address. email address.
......
...@@ -381,6 +381,9 @@ sends logging output to a network socket. The base class uses a TCP socket. ...@@ -381,6 +381,9 @@ sends logging output to a network socket. The base class uses a TCP socket.
Returns a new instance of the :class:`SocketHandler` class intended to Returns a new instance of the :class:`SocketHandler` class intended to
communicate with a remote machine whose address is given by *host* and *port*. communicate with a remote machine whose address is given by *host* and *port*.
.. versionchanged:: 3.4
If ``port`` is specified as ``None``, a Unix domain socket is created
using the value in ``host`` - otherwise, a TCP socket is created.
.. method:: close() .. method:: close()
...@@ -466,6 +469,9 @@ over UDP sockets. ...@@ -466,6 +469,9 @@ over UDP sockets.
Returns a new instance of the :class:`DatagramHandler` class intended to Returns a new instance of the :class:`DatagramHandler` class intended to
communicate with a remote machine whose address is given by *host* and *port*. communicate with a remote machine whose address is given by *host* and *port*.
.. versionchanged:: 3.4
If ``port`` is specified as ``None``, a Unix domain socket is created
using the value in ``host`` - otherwise, a TCP socket is created.
.. method:: emit() .. method:: emit()
......
...@@ -494,6 +494,10 @@ class SocketHandler(logging.Handler): ...@@ -494,6 +494,10 @@ class SocketHandler(logging.Handler):
logging.Handler.__init__(self) logging.Handler.__init__(self)
self.host = host self.host = host
self.port = port self.port = port
if port is None:
self.address = host
else:
self.address = (host, port)
self.sock = None self.sock = None
self.closeOnError = False self.closeOnError = False
self.retryTime = None self.retryTime = None
...@@ -509,7 +513,13 @@ class SocketHandler(logging.Handler): ...@@ -509,7 +513,13 @@ class SocketHandler(logging.Handler):
A factory method which allows subclasses to define the precise A factory method which allows subclasses to define the precise
type of socket they want. type of socket they want.
""" """
return socket.create_connection((self.host, self.port), timeout=timeout) if self.port is not None:
result = socket.create_connection(self.address, timeout=timeout)
else:
result = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
result.settimeout(timeout)
result.connect(self.address)
return result
def createSocket(self): def createSocket(self):
""" """
...@@ -643,7 +653,11 @@ class DatagramHandler(SocketHandler): ...@@ -643,7 +653,11 @@ class DatagramHandler(SocketHandler):
The factory method of SocketHandler is here overridden to create The factory method of SocketHandler is here overridden to create
a UDP socket (SOCK_DGRAM). a UDP socket (SOCK_DGRAM).
""" """
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) if self.port is None:
family = socket.AF_UNIX
else:
family = socket.AF_INET
s = socket.socket(family, socket.SOCK_DGRAM)
return s return s
def send(self, s): def send(self, s):
...@@ -656,7 +670,7 @@ class DatagramHandler(SocketHandler): ...@@ -656,7 +670,7 @@ class DatagramHandler(SocketHandler):
""" """
if self.sock is None: if self.sock is None:
self.createSocket() self.createSocket()
self.sock.sendto(s, (self.host, self.port)) self.sock.sendto(s, self.address)
class SysLogHandler(logging.Handler): class SysLogHandler(logging.Handler):
""" """
......
...@@ -59,7 +59,9 @@ try: ...@@ -59,7 +59,9 @@ try:
import smtpd import smtpd
from urllib.parse import urlparse, parse_qs from urllib.parse import urlparse, parse_qs
from socketserver import (ThreadingUDPServer, DatagramRequestHandler, from socketserver import (ThreadingUDPServer, DatagramRequestHandler,
ThreadingTCPServer, StreamRequestHandler) ThreadingTCPServer, StreamRequestHandler,
ThreadingUnixStreamServer,
ThreadingUnixDatagramServer)
except ImportError: except ImportError:
threading = None threading = None
try: try:
...@@ -854,6 +856,9 @@ if threading: ...@@ -854,6 +856,9 @@ if threading:
super(TestTCPServer, self).server_bind() super(TestTCPServer, self).server_bind()
self.port = self.socket.getsockname()[1] self.port = self.socket.getsockname()[1]
class TestUnixStreamServer(TestTCPServer):
address_family = socket.AF_UNIX
class TestUDPServer(ControlMixin, ThreadingUDPServer): class TestUDPServer(ControlMixin, ThreadingUDPServer):
""" """
A UDP server which is controllable using :class:`ControlMixin`. A UDP server which is controllable using :class:`ControlMixin`.
...@@ -901,6 +906,9 @@ if threading: ...@@ -901,6 +906,9 @@ if threading:
super(TestUDPServer, self).server_close() super(TestUDPServer, self).server_close()
self._closed = True self._closed = True
class TestUnixDatagramServer(TestUDPServer):
address_family = socket.AF_UNIX
# - end of server_helper section # - end of server_helper section
@unittest.skipUnless(threading, 'Threading required for this test.') @unittest.skipUnless(threading, 'Threading required for this test.')
...@@ -1358,17 +1366,22 @@ class SocketHandlerTest(BaseTest): ...@@ -1358,17 +1366,22 @@ class SocketHandlerTest(BaseTest):
"""Test for SocketHandler objects.""" """Test for SocketHandler objects."""
server_class = TestTCPServer
address = ('localhost', 0)
def setUp(self): def setUp(self):
"""Set up a TCP server to receive log messages, and a SocketHandler """Set up a TCP server to receive log messages, and a SocketHandler
pointing to that server's address and port.""" pointing to that server's address and port."""
BaseTest.setUp(self) BaseTest.setUp(self)
addr = ('localhost', 0) self.server = server = self.server_class(self.address,
self.server = server = TestTCPServer(addr, self.handle_socket, self.handle_socket, 0.01)
0.01)
server.start() server.start()
server.ready.wait() server.ready.wait()
self.sock_hdlr = logging.handlers.SocketHandler('localhost', hcls = logging.handlers.SocketHandler
server.port) if isinstance(server.server_address, tuple):
self.sock_hdlr = hcls('localhost', server.port)
else:
self.sock_hdlr = hcls(server.server_address, None)
self.log_output = '' self.log_output = ''
self.root_logger.removeHandler(self.root_logger.handlers[0]) self.root_logger.removeHandler(self.root_logger.handlers[0])
self.root_logger.addHandler(self.sock_hdlr) self.root_logger.addHandler(self.sock_hdlr)
...@@ -1425,21 +1438,46 @@ class SocketHandlerTest(BaseTest): ...@@ -1425,21 +1438,46 @@ class SocketHandlerTest(BaseTest):
self.root_logger.error('Nor this') self.root_logger.error('Nor this')
@unittest.skipUnless(threading, 'Threading required for this test.')
class UnixSocketHandlerTest(SocketHandlerTest):
"""Test for SocketHandler with unix sockets."""
server_class = TestUnixStreamServer
def setUp(self):
# override the definition in the base class
fd, self.address = tempfile.mkstemp(prefix='test_logging_',
suffix='.sock')
os.close(fd)
os.remove(self.address) # just need a name - file can't be present
SocketHandlerTest.setUp(self)
def tearDown(self):
SocketHandlerTest.tearDown(self)
os.remove(self.address)
@unittest.skipUnless(threading, 'Threading required for this test.') @unittest.skipUnless(threading, 'Threading required for this test.')
class DatagramHandlerTest(BaseTest): class DatagramHandlerTest(BaseTest):
"""Test for DatagramHandler.""" """Test for DatagramHandler."""
server_class = TestUDPServer
address = ('localhost', 0)
def setUp(self): def setUp(self):
"""Set up a UDP server to receive log messages, and a DatagramHandler """Set up a UDP server to receive log messages, and a DatagramHandler
pointing to that server's address and port.""" pointing to that server's address and port."""
BaseTest.setUp(self) BaseTest.setUp(self)
addr = ('localhost', 0) self.server = server = self.server_class(self.address,
self.server = server = TestUDPServer(addr, self.handle_datagram, 0.01) self.handle_datagram, 0.01)
server.start() server.start()
server.ready.wait() server.ready.wait()
self.sock_hdlr = logging.handlers.DatagramHandler('localhost', hcls = logging.handlers.DatagramHandler
server.port) if isinstance(server.server_address, tuple):
self.sock_hdlr = hcls('localhost', server.port)
else:
self.sock_hdlr = hcls(server.server_address, None)
self.log_output = '' self.log_output = ''
self.root_logger.removeHandler(self.root_logger.handlers[0]) self.root_logger.removeHandler(self.root_logger.handlers[0])
self.root_logger.addHandler(self.sock_hdlr) self.root_logger.addHandler(self.sock_hdlr)
...@@ -1473,22 +1511,46 @@ class DatagramHandlerTest(BaseTest): ...@@ -1473,22 +1511,46 @@ class DatagramHandlerTest(BaseTest):
self.assertEqual(self.log_output, "spam\neggs\n") self.assertEqual(self.log_output, "spam\neggs\n")
@unittest.skipUnless(threading, 'Threading required for this test.')
class UnixDatagramHandlerTest(DatagramHandlerTest):
"""Test for DatagramHandler using Unix sockets."""
server_class = TestUnixDatagramServer
def setUp(self):
# override the definition in the base class
fd, self.address = tempfile.mkstemp(prefix='test_logging_',
suffix='.sock')
os.close(fd)
os.remove(self.address) # just need a name - file can't be present
DatagramHandlerTest.setUp(self)
def tearDown(self):
DatagramHandlerTest.tearDown(self)
os.remove(self.address)
@unittest.skipUnless(threading, 'Threading required for this test.') @unittest.skipUnless(threading, 'Threading required for this test.')
class SysLogHandlerTest(BaseTest): class SysLogHandlerTest(BaseTest):
"""Test for SysLogHandler using UDP.""" """Test for SysLogHandler using UDP."""
server_class = TestUDPServer
address = ('localhost', 0)
def setUp(self): def setUp(self):
"""Set up a UDP server to receive log messages, and a SysLogHandler """Set up a UDP server to receive log messages, and a SysLogHandler
pointing to that server's address and port.""" pointing to that server's address and port."""
BaseTest.setUp(self) BaseTest.setUp(self)
addr = ('localhost', 0) self.server = server = self.server_class(self.address,
self.server = server = TestUDPServer(addr, self.handle_datagram, self.handle_datagram, 0.01)
0.01)
server.start() server.start()
server.ready.wait() server.ready.wait()
self.sl_hdlr = logging.handlers.SysLogHandler(('localhost', hcls = logging.handlers.SysLogHandler
server.port)) if isinstance(server.server_address, tuple):
self.sl_hdlr = hcls(('localhost', server.port))
else:
self.sl_hdlr = hcls(server.server_address)
self.log_output = '' self.log_output = ''
self.root_logger.removeHandler(self.root_logger.handlers[0]) self.root_logger.removeHandler(self.root_logger.handlers[0])
self.root_logger.addHandler(self.sl_hdlr) self.root_logger.addHandler(self.sl_hdlr)
...@@ -1525,6 +1587,29 @@ class SysLogHandlerTest(BaseTest): ...@@ -1525,6 +1587,29 @@ class SysLogHandlerTest(BaseTest):
self.assertEqual(self.log_output, b'<11>h\xc3\xa4m-sp\xc3\xa4m') self.assertEqual(self.log_output, b'<11>h\xc3\xa4m-sp\xc3\xa4m')
@unittest.skipUnless(threading, 'Threading required for this test.')
class UnixSysLogHandlerTest(SysLogHandlerTest):
"""Test for SysLogHandler with Unix sockets."""
server_class = TestUnixDatagramServer
def setUp(self):
# override the definition in the base class
fd, self.address = tempfile.mkstemp(prefix='test_logging_',
suffix='.sock')
os.close(fd)
os.remove(self.address) # just need a name - file can't be present
SysLogHandlerTest.setUp(self)
def tearDown(self):
SysLogHandlerTest.tearDown(self)
os.remove(self.address)
# def test_output(self):
# import pdb; pdb.set_trace()
# SysLogHandlerTest.test_output(self)
@unittest.skipUnless(threading, 'Threading required for this test.') @unittest.skipUnless(threading, 'Threading required for this test.')
class HTTPHandlerTest(BaseTest): class HTTPHandlerTest(BaseTest):
"""Test for HTTPHandler.""" """Test for HTTPHandler."""
...@@ -4034,7 +4119,8 @@ def test_main(): ...@@ -4034,7 +4119,8 @@ def test_main():
SMTPHandlerTest, FileHandlerTest, RotatingFileHandlerTest, SMTPHandlerTest, FileHandlerTest, RotatingFileHandlerTest,
LastResortTest, LogRecordTest, ExceptionTest, LastResortTest, LogRecordTest, ExceptionTest,
SysLogHandlerTest, HTTPHandlerTest, NTEventLogHandlerTest, SysLogHandlerTest, HTTPHandlerTest, NTEventLogHandlerTest,
TimedRotatingFileHandlerTest TimedRotatingFileHandlerTest, UnixSocketHandlerTest,
UnixDatagramHandlerTest, UnixSysLogHandlerTest
) )
if __name__ == "__main__": if __name__ == "__main__":
......
...@@ -15,6 +15,9 @@ Core and Builtins ...@@ -15,6 +15,9 @@ Core and Builtins
Library Library
------- -------
- logging: added support for Unix domain sockets to SocketHandler and
DatagramHandler.
- Issue #18996: TestCase.assertEqual() now more cleverly shorten differing - Issue #18996: TestCase.assertEqual() now more cleverly shorten differing
strings in error report. strings in error report.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment