handlers.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. """pyzmq logging handlers.
  2. This mainly defines the PUBHandler object for publishing logging messages over
  3. a zmq.PUB socket.
  4. The PUBHandler can be used with the regular logging module, as in::
  5. >>> import logging
  6. >>> handler = PUBHandler('tcp://127.0.0.1:12345')
  7. >>> handler.root_topic = 'foo'
  8. >>> logger = logging.getLogger('foobar')
  9. >>> logger.setLevel(logging.DEBUG)
  10. >>> logger.addHandler(handler)
  11. After this point, all messages logged by ``logger`` will be published on the
  12. PUB socket.
  13. Code adapted from StarCluster:
  14. http://github.com/jtriley/StarCluster/blob/master/starcluster/logger.py
  15. """
  16. # Copyright (C) PyZMQ Developers
  17. # Distributed under the terms of the Modified BSD License.
  18. import logging
  19. from logging import INFO, DEBUG, WARN, ERROR, FATAL
  20. import zmq
  21. from zmq.utils.strtypes import bytes, unicode, cast_bytes
  22. TOPIC_DELIM="::" # delimiter for splitting topics on the receiving end.
  23. class PUBHandler(logging.Handler):
  24. """A basic logging handler that emits log messages through a PUB socket.
  25. Takes a PUB socket already bound to interfaces or an interface to bind to.
  26. Example::
  27. sock = context.socket(zmq.PUB)
  28. sock.bind('inproc://log')
  29. handler = PUBHandler(sock)
  30. Or::
  31. handler = PUBHandler('inproc://loc')
  32. These are equivalent.
  33. Log messages handled by this handler are broadcast with ZMQ topics
  34. ``this.root_topic`` comes first, followed by the log level
  35. (DEBUG,INFO,etc.), followed by any additional subtopics specified in the
  36. message by: log.debug("subtopic.subsub::the real message")
  37. """
  38. socket = None
  39. def __init__(self, interface_or_socket, context=None, root_topic=''):
  40. logging.Handler.__init__(self)
  41. self._root_topic = root_topic
  42. self.formatters = {
  43. logging.DEBUG: logging.Formatter(
  44. "%(levelname)s %(filename)s:%(lineno)d - %(message)s\n"),
  45. logging.INFO: logging.Formatter("%(message)s\n"),
  46. logging.WARN: logging.Formatter(
  47. "%(levelname)s %(filename)s:%(lineno)d - %(message)s\n"),
  48. logging.ERROR: logging.Formatter(
  49. "%(levelname)s %(filename)s:%(lineno)d - %(message)s - %(exc_info)s\n"),
  50. logging.CRITICAL: logging.Formatter(
  51. "%(levelname)s %(filename)s:%(lineno)d - %(message)s\n")}
  52. if isinstance(interface_or_socket, zmq.Socket):
  53. self.socket = interface_or_socket
  54. self.ctx = self.socket.context
  55. else:
  56. self.ctx = context or zmq.Context()
  57. self.socket = self.ctx.socket(zmq.PUB)
  58. self.socket.bind(interface_or_socket)
  59. @property
  60. def root_topic(self):
  61. return self._root_topic
  62. @root_topic.setter
  63. def root_topic(self, value):
  64. self.setRootTopic(value)
  65. def setRootTopic(self, root_topic):
  66. """Set the root topic for this handler.
  67. This value is prepended to all messages published by this handler, and it
  68. defaults to the empty string ''. When you subscribe to this socket, you must
  69. set your subscription to an empty string, or to at least the first letter of
  70. the binary representation of this string to ensure you receive any messages
  71. from this handler.
  72. If you use the default empty string root topic, messages will begin with
  73. the binary representation of the log level string (INFO, WARN, etc.).
  74. Note that ZMQ SUB sockets can have multiple subscriptions.
  75. """
  76. self._root_topic = root_topic
  77. def setFormatter(self, fmt, level=logging.NOTSET):
  78. """Set the Formatter for this handler.
  79. If no level is provided, the same format is used for all levels. This
  80. will overwrite all selective formatters set in the object constructor.
  81. """
  82. if level==logging.NOTSET:
  83. for fmt_level in self.formatters.keys():
  84. self.formatters[fmt_level] = fmt
  85. else:
  86. self.formatters[level] = fmt
  87. def format(self,record):
  88. """Format a record."""
  89. return self.formatters[record.levelno].format(record)
  90. def emit(self, record):
  91. """Emit a log message on my socket."""
  92. try:
  93. topic, record.msg = record.msg.split(TOPIC_DELIM,1)
  94. except Exception:
  95. topic = ""
  96. try:
  97. bmsg = cast_bytes(self.format(record))
  98. except Exception:
  99. self.handleError(record)
  100. return
  101. topic_list = []
  102. if self.root_topic:
  103. topic_list.append(self.root_topic)
  104. topic_list.append(record.levelname)
  105. if topic:
  106. topic_list.append(topic)
  107. btopic = b'.'.join(cast_bytes(t) for t in topic_list)
  108. self.socket.send_multipart([btopic, bmsg])
  109. class TopicLogger(logging.Logger):
  110. """A simple wrapper that takes an additional argument to log methods.
  111. All the regular methods exist, but instead of one msg argument, two
  112. arguments: topic, msg are passed.
  113. That is::
  114. logger.debug('msg')
  115. Would become::
  116. logger.debug('topic.sub', 'msg')
  117. """
  118. def log(self, level, topic, msg, *args, **kwargs):
  119. """Log 'msg % args' with level and topic.
  120. To pass exception information, use the keyword argument exc_info
  121. with a True value::
  122. logger.log(level, "zmq.fun", "We have a %s",
  123. "mysterious problem", exc_info=1)
  124. """
  125. logging.Logger.log(self, level, '%s::%s'%(topic,msg), *args, **kwargs)
  126. # Generate the methods of TopicLogger, since they are just adding a
  127. # topic prefix to a message.
  128. for name in "debug warn warning error critical fatal".split():
  129. meth = getattr(logging.Logger,name)
  130. setattr(TopicLogger, name,
  131. lambda self, level, topic, msg, *args, **kwargs:
  132. meth(self, level, topic+TOPIC_DELIM+msg,*args, **kwargs))