Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
initial import; it is NOT tested; sub-watcher is not yet ready to sub…
…scribe.
- Loading branch information
Showing
8 changed files
with
340 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
Copyright (c) 2011 Richard Bucker <richard@bucker.net> | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
include README.rst | ||
include LICENSE |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
===================================================== | ||
ZeroMQLog - A ZeroMQ Pub/Sub Logging Handler for Python | ||
===================================================== | ||
|
||
A logging handler for Python that publishes log messages using zeromq's | ||
pub/sub system. You can use this to read or respond to streaming log | ||
data in real time. If you are using sub-watcher then the messages can be | ||
redirected back to redis or syslog as needed with all sorts of filter goodness. | ||
|
||
This project and it's layout and details were inspired by Jed Parsons and | ||
python-redis-log (currently hosted on GitHub). | ||
|
||
Installation | ||
------------ | ||
|
||
The current stable release :: | ||
|
||
pip install python-zeromq-log | ||
|
||
or :: | ||
|
||
easy_install python-zeromq-log | ||
|
||
The latest from github_ :: | ||
|
||
git clone git://github.com/rbucker881/python-zeromq-log.git | ||
cd python-zeromq-log | ||
python setup.py build | ||
python setup.py install --prefix=$HOME # for example | ||
|
||
.. _github: https://github.com/rbucker881/python-zeromq-log | ||
|
||
Requirements | ||
------------ | ||
|
||
- redis_ | ||
- The `Python redis client`_ by Andy McCurdy | ||
- simplejson_ | ||
|
||
.. _redis: http://redis.io/ | ||
.. _Python redis client: https://github.com/andymccurdy/redis-py | ||
.. _simplejson: https://github.com/simplejson/simplejson | ||
|
||
Usage | ||
----- | ||
|
||
:: | ||
|
||
>>> from zeromqlog import handlers, logger | ||
>>> l = logger.ZeroMQLogger('my.logger') | ||
>>> l.addHandler(handlers.ZeroMQHandler.to("my:channel")) | ||
>>> l.info("I like chocolate cake") | ||
>>> l.error("Belts?", exc_info=True) | ||
|
||
ZeroMQ (rbucker881/sub-watcher) clients subscribed to ``my:channel`` will get a json log record like the | ||
following (sent from function ``foo()`` in file ``test.py``: :: | ||
|
||
{ username: 'richard', | ||
args: [], | ||
name: 'my.logger', | ||
level: 'info', | ||
line_no: 6, | ||
traceback: null, | ||
filename: 'qa.py', | ||
time: '2011-06-02T14:50:08.237052', | ||
msg: 'losing', | ||
funcname: 'bar', | ||
hostname: 'frosty.local' } | ||
|
||
If an exception is raised, and ``exc_info`` is ``True``, the log will include | ||
a formatted traceback in ``traceback``. | ||
|
||
The date is stored as an ISO 8601 string in GMT. | ||
|
||
|
||
Contributors | ||
------------ | ||
|
||
Just in case you missed this at the top of the readme. | ||
|
||
This project and it's layout and details were inspired by Jed Parsons and | ||
python-redis-log (currently hosted on GitHub). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
try: | ||
from setuptools import setup | ||
except ImportError: | ||
from distutils.core import setup | ||
|
||
from os import path | ||
|
||
README = path.abspath(path.join(path.dirname(__file__), 'README.rst')) | ||
|
||
setup( | ||
name='python-zeromq-log', | ||
version='0.0.1', | ||
description='ZeroMQ pub/sub logging handler for python', | ||
long_description=open(README).read(), | ||
author='Richard Bucker', | ||
author_email='richard@bucker.net', | ||
url='https://github.com/rbucker881/python-zeromq-log', | ||
packages=['zeromqlog'], | ||
license='MIT', | ||
install_requires=['pyzmq','simplejson'] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
""" | ||
zeromqlog - a zeromq logging handler for python | ||
>>> from zeromqlog import handlers, logger | ||
>>> l = logger.ZeroMQLogger('my.logger') | ||
>>> l.addHandler(handlers.ZeroMQHandler.to("my:channel")) | ||
>>> l.info("I like breakfast cereal!") | ||
>>> l.error("Oh snap crackle pop", exc_info=True) | ||
ZeroMQ clients subscribed to my:channel will get a json log record. | ||
depends on : https://github.com/zeromq/pyzmq.git | ||
On errors, if exc_info is True, a printed traceback will be included. | ||
""" | ||
|
||
__author__ = 'Richard Bucker <richard@bucker.net>' | ||
__version__ = (0, 0, 1) | ||
|
||
import logging | ||
import logger | ||
|
||
logging.setLoggerClass(logger.ZeroMQLogger) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
"""copied almost byte for byte from jedp/python-redis-log | ||
There was no copyright in the original. Therefore, none here. | ||
""" | ||
import logging | ||
import zmq | ||
import simplejson as json | ||
|
||
class ZeroMQFormatter(logging.Formatter): | ||
def format(self, record): | ||
""" | ||
JSON-encode a record for serializing through redis. | ||
Convert date to iso format, and stringify any exceptions. | ||
""" | ||
data = record._raw.copy() | ||
|
||
# serialize the datetime date as utc string | ||
data['time'] = data['time'].isoformat() | ||
|
||
# stringify exception data | ||
if data.get('traceback'): | ||
data['traceback'] = self.formatException(data['traceback']) | ||
|
||
return json.dumps(data) | ||
|
||
class ZeroMQHandler(logging.Handler): | ||
""" | ||
Publish messages to a zmq channel. | ||
As a convenience, the classmethod to() can be used as a | ||
constructor, just as in Andrei Savu's mongodb-log handler. | ||
""" | ||
|
||
@classmethod | ||
def to(cklass, channel, url='localhost', port=5555, level=logging.NOTSET): | ||
context = zmq.Context() | ||
publisher = context.socket (zmq.PUB) | ||
return cklass(channel, publisher.bind(url+':'+str(port)), level=level) | ||
|
||
def __init__(self, channel, zmq_client, level=logging.NOTSET): | ||
""" | ||
Create a new logger for the given channel and zmq_client. | ||
""" | ||
logging.Handler.__init__(self, level) | ||
self.channel = channel | ||
self.zmq_client = zmq_client | ||
self.formatter = ZeroMQFormatter() | ||
|
||
def emit(self, record): | ||
""" | ||
Publish record to redis logging channel | ||
""" | ||
try : | ||
msg = zmq.log.handlers.TOPIC_DELIM.join([self.channel,self.format(record)]) | ||
self.zmq_client.send(msg) | ||
except zmq.core.error.ZMQError: | ||
pass | ||
|
||
class ZeroMQListHandler(logging.Handler): | ||
""" | ||
Publish messages to redis a redis list. | ||
As a convenience, the classmethod to() can be used as a | ||
constructor, just as in Andrei Savu's mongodb-log handler. | ||
If max_messages is set, trim the list to this many items. | ||
""" | ||
|
||
@classmethod | ||
def to(cklass, key, max_messages=None, url='localhost', port=5555, level=logging.NOTSET): | ||
context = zmq.Context() | ||
publisher = context.socket (zmq.PUB) | ||
return cklass(key, max_messages, publisher.bind(url+':'+str(port)), level=level) | ||
|
||
def __init__(self, key, max_messages, zmq_client, level=logging.NOTSET): | ||
""" | ||
Create a new logger for the given key and redis_client. | ||
""" | ||
logging.Handler.__init__(self, level) | ||
self.key = key | ||
self.zmq_client = zmq_client | ||
self.formatter = ZeroMQFormatter() | ||
self.max_messages = max_messages | ||
|
||
def emit(self, record): | ||
""" | ||
Publish record to redis logging list | ||
""" | ||
try : | ||
self.zmq_client.send(record) | ||
except zmq.core.error.ZMQError: | ||
pass | ||
|
||
# __END__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
"""copied almost byte for byte from jedp/python-redis-log | ||
There was no copyright in the original. Therefore, none here. | ||
""" | ||
import zmq | ||
import socket | ||
import getpass | ||
import datetime | ||
import inspect | ||
import logging | ||
|
||
def levelAsString(level): | ||
return {logging.DEBUG: 'debug', | ||
logging.INFO: 'info', | ||
logging.WARNING: 'warning', | ||
logging.ERROR: 'error', | ||
logging.CRITICAL: 'critical', | ||
logging.FATAL: 'fatal'}.get(level, 'unknown') | ||
|
||
def _getCallingContext(): | ||
""" | ||
Utility function for the ZeroMQLogRecord. | ||
Returns the module, function, and lineno of the function | ||
that called the logger. | ||
We look way up in the stack. The stack at this point is: | ||
[0] logger.py _getCallingContext (hey, that's me!) | ||
[1] logger.py __init__ | ||
[2] logger.py makeRecord | ||
[3] _log | ||
[4] <logging method> | ||
[5] caller of logging method | ||
""" | ||
frames = inspect.stack() | ||
|
||
if len(frames) > 4: | ||
context = frames[5] | ||
else: | ||
context = frames[0] | ||
|
||
modname = context[1] | ||
lineno = context[2] | ||
|
||
if context[3]: | ||
funcname = context[3] | ||
else: | ||
funcname = "" | ||
|
||
# python docs say you don't want references to | ||
# frames lying around. Bad things can happen. | ||
del context | ||
del frames | ||
|
||
return modname, funcname, lineno | ||
|
||
|
||
class ZeroMQLogRecord(logging.LogRecord): | ||
def __init__(self, name, lvl, fn, lno, msg, args, exc_info, func=None, extra=None): | ||
logging.LogRecord.__init__(self, name, lvl, fn, lno, msg, args, exc_info, func) | ||
|
||
# You can also access the following instance variables via the | ||
# formatter as | ||
# %(hostname)s | ||
# %(username)s | ||
# %(modname)s | ||
# etc. | ||
self.hostname = socket.gethostname() | ||
self.username = getpass.getuser() | ||
self.modname, self.funcname, self.lineno = _getCallingContext() | ||
|
||
self._raw = { | ||
'name': name, | ||
'level': levelAsString(lvl), | ||
'filename': fn, | ||
'line_no': self.lineno, | ||
'msg': str(msg), | ||
'args': list(args), | ||
'time': datetime.datetime.utcnow(), | ||
'username': self.username, | ||
'funcname': self.funcname, | ||
'hostname': self.hostname, | ||
'traceback': exc_info | ||
} | ||
|
||
class ZeroMQLogger(logging.getLoggerClass()): | ||
def makeRecord(self, name, lvl, fn, lno, msg, args, exc_info, func=None, extra=None): | ||
record = ZeroMQLogRecord(name, lvl, fn, lno, msg, args, exc_info, func=None) | ||
|
||
if extra: | ||
for key in extra: | ||
if (key in ["message", "asctime"]) or (key in record.__dict__): | ||
raise KeyError("Attempt to overwrite %r in ZeroMQLogRecord" % key) | ||
record.__dict__[key] = extra[key] | ||
return record | ||
|
||
|
||
# __END__ |