]> granicus.if.org Git - python/commitdiff
logging: added support for Unix domain sockets to SocketHandler and DatagramHandler.
authorVinay Sajip <vinay_sajip@yahoo.co.uk>
Fri, 27 Sep 2013 17:18:28 +0000 (18:18 +0100)
committerVinay Sajip <vinay_sajip@yahoo.co.uk>
Fri, 27 Sep 2013 17:18:28 +0000 (18:18 +0100)
Doc/howto/logging.rst
Doc/library/logging.handlers.rst
Lib/logging/handlers.py
Lib/test/test_logging.py
Misc/NEWS

index 165486aa3fed1ef27743d8aaaed81b6f6732da0f..c7a802a1e32c2f35a8ac49424f62fe97e6cf4050 100644 (file)
@@ -900,10 +900,10 @@ provided:
    disk files, rotating the log file at certain timed intervals.
 
 #. :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
-   sockets.
+   sockets. Since 3.4, Unix domain sockets are also supported.
 
 #. :class:`~handlers.SMTPHandler` instances send messages to a designated
    email address.
index 65e0b67d32b49e96144bb52c68a13a70769512f0..f52060a40b4c4422bdaecc4e675cd3b5d4674988 100644 (file)
@@ -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
    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()
 
@@ -466,6 +469,9 @@ over UDP sockets.
    Returns a new instance of the :class:`DatagramHandler` class intended to
    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()
 
index 0ae6e45abf290b87582c45c5a5a2ff25c43c3dd9..b0b0a1660f08dcc6ab487124f34c203ef729a285 100644 (file)
@@ -494,6 +494,10 @@ class SocketHandler(logging.Handler):
         logging.Handler.__init__(self)
         self.host = host
         self.port = port
+        if port is None:
+            self.address = host
+        else:
+            self.address = (host, port)
         self.sock = None
         self.closeOnError = False
         self.retryTime = None
@@ -509,7 +513,13 @@ class SocketHandler(logging.Handler):
         A factory method which allows subclasses to define the precise
         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):
         """
@@ -643,7 +653,11 @@ class DatagramHandler(SocketHandler):
         The factory method of SocketHandler is here overridden to create
         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
 
     def send(self, s):
@@ -656,7 +670,7 @@ class DatagramHandler(SocketHandler):
         """
         if self.sock is None:
             self.createSocket()
-        self.sock.sendto(s, (self.host, self.port))
+        self.sock.sendto(s, self.address)
 
 class SysLogHandler(logging.Handler):
     """
index 85e1898b6cdd9125c66c64b1421de79c11d09ffb..cbdc78ce27ee33dda862e524575646656d39a551 100644 (file)
@@ -59,7 +59,9 @@ try:
     import smtpd
     from urllib.parse import urlparse, parse_qs
     from socketserver import (ThreadingUDPServer, DatagramRequestHandler,
-                              ThreadingTCPServer, StreamRequestHandler)
+                              ThreadingTCPServer, StreamRequestHandler,
+                              ThreadingUnixStreamServer,
+                              ThreadingUnixDatagramServer)
 except ImportError:
     threading = None
 try:
@@ -854,6 +856,9 @@ if threading:
             super(TestTCPServer, self).server_bind()
             self.port = self.socket.getsockname()[1]
 
+    class TestUnixStreamServer(TestTCPServer):
+        address_family = socket.AF_UNIX
+
     class TestUDPServer(ControlMixin, ThreadingUDPServer):
         """
         A UDP server which is controllable using :class:`ControlMixin`.
@@ -901,6 +906,9 @@ if threading:
             super(TestUDPServer, self).server_close()
             self._closed = True
 
+    class TestUnixDatagramServer(TestUDPServer):
+        address_family = socket.AF_UNIX
+
 # - end of server_helper section
 
 @unittest.skipUnless(threading, 'Threading required for this test.')
@@ -1358,17 +1366,22 @@ class SocketHandlerTest(BaseTest):
 
     """Test for SocketHandler objects."""
 
+    server_class = TestTCPServer
+    address = ('localhost', 0)
+
     def setUp(self):
         """Set up a TCP server to receive log messages, and a SocketHandler
         pointing to that server's address and port."""
         BaseTest.setUp(self)
-        addr = ('localhost', 0)
-        self.server = server = TestTCPServer(addr, self.handle_socket,
-                                                0.01)
+        self.server = server = self.server_class(self.address,
+                                                 self.handle_socket, 0.01)
         server.start()
         server.ready.wait()
-        self.sock_hdlr = logging.handlers.SocketHandler('localhost',
-                                                        server.port)
+        hcls = logging.handlers.SocketHandler
+        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.root_logger.removeHandler(self.root_logger.handlers[0])
         self.root_logger.addHandler(self.sock_hdlr)
@@ -1425,21 +1438,46 @@ class SocketHandlerTest(BaseTest):
         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.')
 class DatagramHandlerTest(BaseTest):
 
     """Test for DatagramHandler."""
 
+    server_class = TestUDPServer
+    address = ('localhost', 0)
+
     def setUp(self):
         """Set up a UDP server to receive log messages, and a DatagramHandler
         pointing to that server's address and port."""
         BaseTest.setUp(self)
-        addr = ('localhost', 0)
-        self.server = server = TestUDPServer(addr, self.handle_datagram, 0.01)
+        self.server = server = self.server_class(self.address,
+                                                 self.handle_datagram, 0.01)
         server.start()
         server.ready.wait()
-        self.sock_hdlr = logging.handlers.DatagramHandler('localhost',
-                                                          server.port)
+        hcls = logging.handlers.DatagramHandler
+        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.root_logger.removeHandler(self.root_logger.handlers[0])
         self.root_logger.addHandler(self.sock_hdlr)
@@ -1473,22 +1511,46 @@ class DatagramHandlerTest(BaseTest):
         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.')
 class SysLogHandlerTest(BaseTest):
 
     """Test for SysLogHandler using UDP."""
 
+    server_class = TestUDPServer
+    address = ('localhost', 0)
+
     def setUp(self):
         """Set up a UDP server to receive log messages, and a SysLogHandler
         pointing to that server's address and port."""
         BaseTest.setUp(self)
-        addr = ('localhost', 0)
-        self.server = server = TestUDPServer(addr, self.handle_datagram,
-                                                0.01)
+        self.server = server = self.server_class(self.address,
+                                                 self.handle_datagram, 0.01)
         server.start()
         server.ready.wait()
-        self.sl_hdlr = logging.handlers.SysLogHandler(('localhost',
-                                                       server.port))
+        hcls = logging.handlers.SysLogHandler
+        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.root_logger.removeHandler(self.root_logger.handlers[0])
         self.root_logger.addHandler(self.sl_hdlr)
@@ -1525,6 +1587,29 @@ class SysLogHandlerTest(BaseTest):
         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.')
 class HTTPHandlerTest(BaseTest):
     """Test for HTTPHandler."""
@@ -4034,7 +4119,8 @@ def test_main():
                  SMTPHandlerTest, FileHandlerTest, RotatingFileHandlerTest,
                  LastResortTest, LogRecordTest, ExceptionTest,
                  SysLogHandlerTest, HTTPHandlerTest, NTEventLogHandlerTest,
-                 TimedRotatingFileHandlerTest
+                 TimedRotatingFileHandlerTest, UnixSocketHandlerTest,
+                 UnixDatagramHandlerTest, UnixSysLogHandlerTest
                 )
 
 if __name__ == "__main__":
index 0b34293ab211184d4fe00b1c5d97a4d4932e2a22..6239614bf38ca4da79daa3aaa7506ad6a31db8ac 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -15,6 +15,9 @@ Core and Builtins
 Library
 -------
 
+- logging: added support for Unix domain sockets to SocketHandler and
+  DatagramHandler.
+
 - Issue #18996: TestCase.assertEqual() now more cleverly shorten differing
   strings in error report.