Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Doc/library/logging.handlers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,9 @@ over UDP sockets.
If ``port`` is specified as ``None``, a Unix domain socket is created
using the value in ``host`` - otherwise, a UDP socket is created.

.. versionchanged:: next
Added support for IPv6.

.. method:: emit()

Pickles the record's attribute dictionary and writes it to the socket in
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,11 @@ logging
before the rotation interval expires.
(Contributed by Iván Márton and Serhiy Storchaka in :gh:`84649`.)

* :class:`~logging.handlers.DatagramHandler` now supports IPv6.
Previously it always created an IPv4 socket, so sending to an IPv6 address
failed and the log record was silently lost.
(Contributed by Serhiy Storchaka in :gh:`59060`.)


lzma
----
Expand Down
10 changes: 10 additions & 0 deletions Lib/logging/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,16 @@ def makeSocket(self):
family = socket.AF_UNIX
else:
family = socket.AF_INET
# The host can have no IPv4 address, for example if it is an
# IPv6 address. Leave resolution errors to sendto().
try:
infos = socket.getaddrinfo(self.host, self.port,
type=socket.SOCK_DGRAM)
except OSError:
pass
else:
if not any(info[0] == family for info in infos):
family = infos[0][0]
s = socket.socket(family, socket.SOCK_DGRAM)
return s

Expand Down
23 changes: 20 additions & 3 deletions Lib/test/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -1982,7 +1982,7 @@ def setUp(self):
server.ready.wait()
hcls = logging.handlers.DatagramHandler
if isinstance(server.server_address, tuple):
self.sock_hdlr = hcls('localhost', server.port)
self.sock_hdlr = hcls(self.address[0], server.port)
else:
self.sock_hdlr = hcls(server.server_address, None)
self.log_output = ''
Expand Down Expand Up @@ -2015,12 +2015,29 @@ def test_output(self):
self.skipTest(self.server_exception)
logger = logging.getLogger("udp")
logger.error("spam")
self.handled.wait()
self.handled.wait(support.LONG_TIMEOUT)
self.handled.clear()
logger.error("eggs")
self.handled.wait()
self.handled.wait(support.LONG_TIMEOUT)
self.assertEqual(self.log_output, "spam\neggs\n")

@unittest.skipUnless(socket_helper.IPV6_ENABLED,
'IPv6 support required for this test.')
class IPv6DatagramHandlerTest(DatagramHandlerTest):

"""Test for DatagramHandler with IPv6 host."""

server_class = TestUDPServer
address = ('::1', 0)

def setUp(self):
self.server_class.address_family = socket.AF_INET6
super().setUp()

def tearDown(self):
self.server_class.address_family = socket.AF_INET
super().tearDown()

@unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required")
class UnixDatagramHandlerTest(DatagramHandlerTest):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:class:`logging.handlers.DatagramHandler` now supports IPv6:
it creates an IPv6 socket if the host has no IPv4 address.
Previously it always created an IPv4 socket,
so the log records were silently lost.
Loading