@@ -1846,8 +1846,11 @@ the use of a :class:`Filter` does not provide the desired result.
18461846
18471847.. _zeromq-handlers :
18481848
1849- Subclassing QueueHandler - a ZeroMQ example
1850- -------------------------------------------
1849+ Subclassing QueueHandler and QueueListener- a ZeroMQ example
1850+ ------------------------------------------------------------
1851+
1852+ Subclass ``QueueHandler ``
1853+ ^^^^^^^^^^^^^^^^^^^^^^^^^
18511854
18521855You can use a :class: `QueueHandler ` subclass to send messages to other kinds
18531856of queues, for example a ZeroMQ 'publish' socket. In the example below,the
@@ -1885,8 +1888,8 @@ data needed by the handler to create the socket::
18851888 self.queue.close()
18861889
18871890
1888- Subclassing QueueListener - a ZeroMQ example
1889- --------------------------------------------
1891+ Subclass `` QueueListener ``
1892+ ^^^^^^^^^^^^^^^^^^^^^^^^^^
18901893
18911894You can also subclass :class: `QueueListener ` to get messages from other kinds
18921895of queues, for example a ZeroMQ 'subscribe' socket. Here's an example::
@@ -1903,25 +1906,134 @@ of queues, for example a ZeroMQ 'subscribe' socket. Here's an example::
19031906 msg = self.queue.recv_json()
19041907 return logging.makeLogRecord(msg)
19051908
1909+ .. _pynng-handlers :
19061910
1907- .. seealso ::
1911+ Subclassing QueueHandler and QueueListener- a ``pynng `` example
1912+ ---------------------------------------------------------------
19081913
1909- Module :mod: `logging `
1910- API reference for the logging module.
1914+ In a similar way to the above section, we can implement a listener and handler
1915+ using `pynng <https://pypi.org/project/pynng/ >`_, which is a Python binding to
1916+ `NNG <https://nng.nanomsg.org/ >`_, billed as a spiritual successor to ZeroMQ.
1917+ The following snippets illustrate -- you can test them in an environment which has
1918+ ``pynng `` installed. Juat for variety, we present the listener first.
19111919
1912- Module :mod: `logging.config `
1913- Configuration API for the logging module.
19141920
1915- Module :mod: `logging.handlers `
1916- Useful handlers included with the logging module.
1921+ Subclass ``QueueListener ``
1922+ ^^^^^^^^^^^^^^^^^^^^^^^^^^
1923+
1924+ .. code-block :: python
1925+
1926+ import json
1927+ import logging
1928+ import logging.handlers
1929+
1930+ import pynng
19171931
1918- :ref: ` A basic logging tutorial < logging-basic-tutorial >`
1932+ DEFAULT_ADDR = " tcp://localhost:13232 "
19191933
1920- :ref: ` A more advanced logging tutorial < logging-advanced-tutorial >`
1934+ interrupted = False
19211935
1936+ class NNGSocketListener (logging .handlers .QueueListener ):
1937+
1938+ def __init__ (self , uri , / , * handlers , ** kwargs ):
1939+ # Have a timeout for interruptability, and open a
1940+ # subscriber socket
1941+ socket = pynng.Sub0(listen = uri, recv_timeout = 500 )
1942+ # The b'' subscription matches all topics
1943+ topics = kwargs.pop(' topics' , None ) or b ' '
1944+ socket.subscribe(topics)
1945+ # We treat the socket as a queue
1946+ super ().__init__ (socket, * handlers, ** kwargs)
1947+
1948+ def dequeue (self , block ):
1949+ data = None
1950+ # Keep looping while not interrupted and no data received over the
1951+ # socket
1952+ while not interrupted:
1953+ try :
1954+ data = self .queue.recv(block = block)
1955+ break
1956+ except pynng.Timeout:
1957+ pass
1958+ except pynng.Closed: # sometimes hit when you hit Ctrl-C
1959+ break
1960+ if data is None :
1961+ return None
1962+ # Get the logging event sent from a publisher
1963+ event = json.loads(data.decode(' utf-8' ))
1964+ return logging.makeLogRecord(event)
1965+
1966+ def enqueue_sentinel (self ):
1967+ # Not used in this implementation, as the socket isn't really a
1968+ # queue
1969+ pass
1970+
1971+ logging.getLogger(' pynng' ).propagate = False
1972+ listener = NNGSocketListener(DEFAULT_ADDR , logging.StreamHandler(), topics = b ' ' )
1973+ listener.start()
1974+ print (' Press Ctrl-C to stop.' )
1975+ try :
1976+ while True :
1977+ pass
1978+ except KeyboardInterrupt :
1979+ interrupted = True
1980+ finally :
1981+ listener.stop()
1982+
1983+
1984+ Subclass ``QueueHandler ``
1985+ ^^^^^^^^^^^^^^^^^^^^^^^^^
19221986
19231987.. currentmodule :: logging
19241988
1989+ .. code-block :: python
1990+
1991+ import json
1992+ import logging
1993+ import logging.handlers
1994+ import time
1995+ import random
1996+
1997+ import pynng
1998+
1999+ DEFAULT_ADDR = " tcp://localhost:13232"
2000+
2001+ class NNGSocketHandler (logging .handlers .QueueHandler ):
2002+
2003+ def __init__ (self , uri ):
2004+ socket = pynng.Pub0(dial = uri, send_timeout = 500 )
2005+ super ().__init__ (socket)
2006+
2007+ def enqueue (self , record ):
2008+ # Send the record as UTF-8 encoded JSON
2009+ d = dict (record.__dict__ )
2010+ data = json.dumps(d)
2011+ self .queue.send(data.encode(' utf-8' ))
2012+
2013+ def close (self ):
2014+ self .queue.close()
2015+
2016+ logging.getLogger(' pynng' ).propagate = False
2017+ handler = NNGSocketHandler(DEFAULT_ADDR )
2018+ logging.basicConfig(level = logging.DEBUG ,
2019+ handlers = [logging.StreamHandler(), handler],
2020+ format = ' %(levelname)-8s %(name)10s %(message)s ' )
2021+ levels = (logging.DEBUG , logging.INFO , logging.WARNING , logging.ERROR ,
2022+ logging.CRITICAL )
2023+ logger_names = (' myapp' , ' myapp.lib1' , ' myapp.lib2' )
2024+ msgno = 1
2025+ while True :
2026+ # Just randomly select some loggers and levels and log away
2027+ level = random.choice(levels)
2028+ logger = logging.getLogger(random.choice(logger_names))
2029+ logger.log(level, ' Message no. %5d ' % msgno)
2030+ msgno += 1
2031+ delay = random.random() * 2 + 0.5
2032+ time.sleep(delay)
2033+
2034+ You can run the above two snippets in separate command shells.
2035+
2036+
19252037An example dictionary-based configuration
19262038-----------------------------------------
19272039
@@ -3418,7 +3530,7 @@ The worker thread is implemented using Qt's ``QThread`` class rather than the
34183530:mod: `threading ` module, as there are circumstances where one has to use
34193531``QThread ``, which offers better integration with other ``Qt `` components.
34203532
3421- The code should work with recent releases of either ``PySide6 ``, ``PyQt6 ``,
3533+ The code should work with recent releases of any of ``PySide6 ``, ``PyQt6 ``,
34223534``PySide2 `` or ``PyQt5 ``. You should be able to adapt the approach to earlier
34233535versions of Qt. Please refer to the comments in the code snippet for more
34243536detailed information.
0 commit comments