2
"""pyzmq logging handlers.
4
This mainly defines the PUBHandler object for publishing logging messages over
7
The PUBHandler can be used with the regular logging module, as in::
10
>>> handler = PUBHandler('tcp://127.0.0.1:12345')
11
>>> handler.root_topic = 'foo'
12
>>> logger = logging.getLogger('foobar')
13
>>> logger.addHandler(logging.DEBUG, handler)
15
After this point, all messages logged by ``logger`` will be published on the
18
Code adapted from StarCluster:
20
http://github.com/jtriley/StarCluster/blob/master/starcluster/logger.py
28
# Copyright (c) 2010 Min Ragan-Kelley
30
# This file is part of pyzmq.
32
# pyzmq is free software; you can redistribute it and/or modify it under
33
# the terms of the Lesser GNU General Public License as published by
34
# the Free Software Foundation; either version 3 of the License, or
35
# (at your option) any later version.
37
# pyzmq is distributed in the hope that it will be useful,
38
# but WITHOUT ANY WARRANTY; without even the implied warranty of
39
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
40
# Lesser GNU General Public License for more details.
42
# You should have received a copy of the Lesser GNU General Public License
43
# along with this program. If not, see <http://www.gnu.org/licenses/>.
46
#-----------------------------------------------------------------------------
48
#-----------------------------------------------------------------------------
51
from logging import INFO, DEBUG, WARN, ERROR, FATAL
54
from zmq.utils.strtypes import bytes,unicode
56
#-----------------------------------------------------------------------------
58
#-----------------------------------------------------------------------------
60
TOPIC_DELIM="::" # delimiter for splitting topics on the receiving end.
63
class PUBHandler(logging.Handler):
64
"""A basic logging handler that emits log messages through a PUB socket.
66
Takes a PUB socket already bound to interfaces or an interface to bind to.
70
sock = context.socket(zmq.PUB)
71
sock.bind('inproc://log')
72
handler = PUBHandler(sock)
76
handler = PUBHandler('inproc://loc')
80
Log messages handled by this handler are broadcast with ZMQ topics
81
``this.root_topic`` comes first, followed by the log level
82
(DEBUG,INFO,etc.), followed by any additional subtopics specified in the
83
message by: log.debug("subtopic.subsub::the real message")
85
root_topic="".encode()
89
logging.DEBUG: logging.Formatter(
90
"%(levelname)s %(filename)s:%(lineno)d - %(message)s\n"),
91
logging.INFO: logging.Formatter("%(message)s\n"),
92
logging.WARN: logging.Formatter(
93
"%(levelname)s %(filename)s:%(lineno)d - %(message)s\n"),
94
logging.ERROR: logging.Formatter(
95
"%(levelname)s %(filename)s:%(lineno)d - %(message)s - %(exc_info)s\n"),
96
logging.CRITICAL: logging.Formatter(
97
"%(levelname)s %(filename)s:%(lineno)d - %(message)s\n")}
99
def __init__(self, interface_or_socket, context=None):
100
logging.Handler.__init__(self)
101
if isinstance(interface_or_socket, zmq.Socket):
102
self.socket = interface_or_socket
103
self.ctx = self.socket.context
105
self.ctx = context or zmq.Context()
106
self.socket = self.ctx.socket(zmq.PUB)
107
self.socket.bind(interface_or_socket)
109
def format(self,record):
110
"""Format a record."""
111
return self.formatters[record.levelno].format(record)
113
def emit(self, record):
114
"""Emit a log message on my socket."""
116
topic, record.msg = record.msg.split(TOPIC_DELIM,1)
117
topic = topic.encode()
121
msg = self.format(record).encode()
122
except (KeyboardInterrupt, SystemExit):
125
self.handleError(record)
129
topic_list.append(self.root_topic)
131
topic_list.append(record.levelname.encode())
134
topic_list.append(topic)
136
topic = '.'.encode().join(topic_list)
138
# map str, since sometimes we get unicode, and zmq can't deal with it
139
self.socket.send_multipart([topic,msg])
142
class TopicLogger(logging.Logger):
143
"""A simple wrapper that takes an additional argument to log methods.
145
All the regular methods exist, but instead of one msg argument, two
146
arguments: topic, msg are passed.
154
logger.debug('topic.sub', 'msg')
156
def log(self, level, topic, msg, *args, **kwargs):
157
"""Log 'msg % args' with level and topic.
159
To pass exception information, use the keyword argument exc_info
162
logger.log(level, "zmq.fun", "We have a %s",
163
"mysterious problem", exc_info=1)
165
logging.Logger.log(self, level, '%s::%s'%(topic,msg), *args, **kwargs)
167
# Generate the methods of TopicLogger, since they are just adding a
168
# topic prefix to a message.
169
for name in "debug warn warning error critical fatal".split():
170
meth = getattr(logging.Logger,name)
171
setattr(TopicLogger, name,
172
lambda self, level, topic, msg, *args, **kwargs:
173
meth(self, level, topic+TOPIC_DELIM+msg,*args, **kwargs))