"""pyzmq logging handlers. This mainly defines the PUBHandler object for publishing logging messages over a zmq.PUB socket. The PUBHandler can be used with the regular logging module, as in:: >>> import logging >>> handler = PUBHandler('tcp://127.0.0.1:12345') >>> handler.root_topic = 'foo' >>> logger = logging.getLogger('foobar') >>> logger.setLevel(logging.DEBUG) >>> logger.addHandler(handler) After this point, all messages logged by ``logger`` will be published on the PUB socket. Code adapted from StarCluster: http://github.com/jtriley/StarCluster/blob/master/starcluster/logger.py """ # Copyright (C) PyZMQ Developers # Distributed under the terms of the Modified BSD License. import logging from logging import INFO, DEBUG, WARN, ERROR, FATAL import zmq from zmq.utils.strtypes import bytes, unicode, cast_bytes TOPIC_DELIM="::" # delimiter for splitting topics on the receiving end. class PUBHandler(logging.Handler): """A basic logging handler that emits log messages through a PUB socket. Takes a PUB socket already bound to interfaces or an interface to bind to. Example:: sock = context.socket(zmq.PUB) sock.bind('inproc://log') handler = PUBHandler(sock) Or:: handler = PUBHandler('inproc://loc') These are equivalent. Log messages handled by this handler are broadcast with ZMQ topics ``this.root_topic`` comes first, followed by the log level (DEBUG,INFO,etc.), followed by any additional subtopics specified in the message by: log.debug("subtopic.subsub::the real message") """ root_topic="" socket = None formatters = { logging.DEBUG: logging.Formatter( "%(levelname)s %(filename)s:%(lineno)d - %(message)s\n"), logging.INFO: logging.Formatter("%(message)s\n"), logging.WARN: logging.Formatter( "%(levelname)s %(filename)s:%(lineno)d - %(message)s\n"), logging.ERROR: logging.Formatter( "%(levelname)s %(filename)s:%(lineno)d - %(message)s - %(exc_info)s\n"), logging.CRITICAL: logging.Formatter( "%(levelname)s %(filename)s:%(lineno)d - %(message)s\n")} def __init__(self, interface_or_socket, context=None): logging.Handler.__init__(self) if isinstance(interface_or_socket, zmq.Socket): self.socket = interface_or_socket self.ctx = self.socket.context else: self.ctx = context or zmq.Context() self.socket = self.ctx.socket(zmq.PUB) self.socket.bind(interface_or_socket) def format(self,record): """Format a record.""" return self.formatters[record.levelno].format(record) def emit(self, record): """Emit a log message on my socket.""" try: topic, record.msg = record.msg.split(TOPIC_DELIM,1) except Exception: topic = "" try: bmsg = cast_bytes(self.format(record)) except Exception: self.handleError(record) return topic_list = [] if self.root_topic: topic_list.append(self.root_topic) topic_list.append(record.levelname) if topic: topic_list.append(topic) btopic = b'.'.join(cast_bytes(t) for t in topic_list) self.socket.send_multipart([btopic, bmsg]) class TopicLogger(logging.Logger): """A simple wrapper that takes an additional argument to log methods. All the regular methods exist, but instead of one msg argument, two arguments: topic, msg are passed. That is:: logger.debug('msg') Would become:: logger.debug('topic.sub', 'msg') """ def log(self, level, topic, msg, *args, **kwargs): """Log 'msg % args' with level and topic. To pass exception information, use the keyword argument exc_info with a True value:: logger.log(level, "zmq.fun", "We have a %s", "mysterious problem", exc_info=1) """ logging.Logger.log(self, level, '%s::%s'%(topic,msg), *args, **kwargs) # Generate the methods of TopicLogger, since they are just adding a # topic prefix to a message. for name in "debug warn warning error critical fatal".split(): meth = getattr(logging.Logger,name) setattr(TopicLogger, name, lambda self, level, topic, msg, *args, **kwargs: meth(self, level, topic+TOPIC_DELIM+msg,*args, **kwargs))