Source code for pynedm.log
from twisted.internet import reactor
from threading import Thread
import logging
import json
from autobahn.twisted.websocket import (WebSocketServerFactory,
listenWS,
WebSocketServerProtocol)
__all__ = [
"debug", "log", "error", "exception", "listening_addresses",
"BroadcastLogFactory", "BroadcastLogHandler", "BroadcastLogProtocol",
"use_broadcaster"
]
[docs]def debug(*args):
"""
Alias for logging.debug
"""
_logger.debug(*args)
[docs]def log(*args):
"""
Alias for logging.info
"""
_logger.info(*args)
[docs]def error(*args):
"""
Alias for logging.error
"""
_logger.error(*args)
[docs]def exception(*args):
"""
Alias for logging.exception
"""
_logger.exception(*args)
[docs]class BroadcastLogProtocol(WebSocketServerProtocol):
"""
Internal class to define broadcast log protocol
"""
def onOpen(self):
self.factory.register(self)
def connectionLost(self, reason):
super(BroadcastLogProtocol, self).connectionLost(reason)
self.factory.unregister(self)
[docs]class BroadcastLogFactory(WebSocketServerFactory):
"""
Factory for broadcast logger
"""
def __init__(self, *args, **kw):
super(BroadcastLogFactory, self).__init__(*args, **kw)
self.clients = []
def register(self, client):
if client not in self.clients:
self.clients.append(client)
def unregister(self, client):
if client in self.clients:
self.clients.remove(client)
def broadcast(self, msg):
prepMsg = self.prepareMessage(json.dumps(msg))
for c in self.clients:
c.sendPreparedMessage(prepMsg)
[docs]class BroadcastLogHandler(logging.Handler):
"""
Listens on 0.0.0.0 (all interfaces) and sends log messages to connected
clients. Clients must connect via WebSocket and receive logging
information in JSON format, e.g.::
{ 'level' : 'INFO', 'msg' : 'A sent message' }
This is used in live logging of Raspberry Pis, for example in the
nEDM-Interface.
"""
def __init__(self):
logging.Handler.__init__(self)
factory = BroadcastLogFactory("ws://0.0.0.0:0")
factory.protocol = BroadcastLogProtocol
self.port = listenWS(factory).getHost()
self.factory = factory
# Start the reactor thread, setting daemon to True
# (daemon = True) allows the program to normally end, which allows the
# close function to be called, where we explicitly join the reactor
# thread
self._th = Thread(target=reactor.run, args=(False,))
self._th.daemon = True
self._th.start()
def getPort(self):
return self.port
def _sendRecord(self, msg):
reactor.callFromThread(self.factory.broadcast, msg)
def emit(self, record):
self._sendRecord(dict(level=record.levelname, msg=self.format(record)))
def close(self):
logging.Handler.close(self)
reactor.callFromThread(reactor.stop)
self._th.join()
_logger = logging.getLogger(__name__)
_logger.setLevel(logging.INFO)
_formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
_stream_hdlr = logging.StreamHandler()
_stream_hdlr.setFormatter(_formatter)
_logger.addHandler(_stream_hdlr)
_handler = None
[docs]def use_broadcaster():
"""
Call this function to enable the use of BroadcastLogHandler
Note, you cannot use this in a function/application that uses
twisted.reactor
"""
global _handler
if _handler: return
_handler = BroadcastLogHandler()
_handler.setFormatter(_formatter)
_logger.addHandler(_handler)
[docs]def listening_addresses():
"""
Return external addresses where we are listening to broadcast the log:
:returns: list -- [ 'ws://x.x.x.x:y', ... ]
"""
if not _handler:
return []
import netifaces
port = _handler.getPort().port
obj = [x[netifaces.AF_INET][0]['addr']
for x in map(netifaces.ifaddresses, netifaces.interfaces())
if netifaces.AF_INET in x]
obj.remove("127.0.0.1")
return map(lambda x: "ws://{}:{}".format(x,port), obj)