Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

initial import; it is NOT tested; sub-watcher is not yet ready to sub…

…scribe.
  • Loading branch information...
commit 95ff362d09f48bdddd7b7703295e27a81d46f81d 1 parent 2950e2c
@rbucker authored
View
20 LICENSE
@@ -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.
+
View
2  MANIFEST.in
@@ -0,0 +1,2 @@
+include README.rst
+include LICENSE
View
0  README
No changes.
View
82 README.rst
@@ -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).
View
21 setup.py
@@ -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']
+)
View
24 zeromqlog/__init__.py
@@ -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)
+
+
View
94 zeromqlog/handlers.py
@@ -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__
View
97 zeromqlog/logger.py
@@ -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__
Please sign in to comment.
Something went wrong with that request. Please try again.