logutils-0.3.3/0000755000175000017500000000000012100227273012411 5ustar vinayvinaylogutils-0.3.3/PKG-INFO0000644000175000017500000000234612100227273013513 0ustar vinayvinayMetadata-Version: 1.0 Name: logutils Version: 0.3.3 Summary: Logging utilities Home-page: http://code.google.com/p/logutils/ Author: Vinay Sajip Author-email: vinay_sajip@red-dove.com License: Copyright (C) 2010-2013 by Vinay Sajip. All Rights Reserved. See LICENSE.txt for license. Description: The logutils package provides a set of handlers for the Python standard library's logging package. Some of these handlers are out-of-scope for the standard library, and so they are packaged here. Others are updated versions which have appeared in recent Python releases, but are usable with older versions of Python and so are packaged here. The latest version of logutils can be found at: http://code.google.com/p/logutils/ Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development logutils-0.3.3/setup.py0000644000175000017500000000346012100225211014114 0ustar vinayvinay# -*- coding: utf-8 -*- import distutils.core import logutils from os.path import join, dirname, abspath import re def description(): f = open(join(dirname(__file__), 'README.txt')) readme = f.read() f.close() regexp = r'^logutils\s*[\d.]*\s*\n=======+\s*\n(.*)Requirements ' reqts, = re.findall(regexp, readme, re.DOTALL) regexp = r'Availability & Documentation\s*\n-----+\s*\n(.*)' avail, = re.findall(regexp, readme, re.DOTALL) return reqts + avail class TestCommand(distutils.core.Command): user_options = [] def run(self): import sys import unittest sys.path.append(join(dirname(__file__), 'tests')) import logutil_tests loader = unittest.TestLoader() runner = unittest.TextTestRunner() runner.run(loader.loadTestsFromModule(logutil_tests)) def initialize_options(self): pass def finalize_options(self): pass distutils.core.setup( name='logutils', version=logutils.__version__, author='Vinay Sajip', author_email='vinay_sajip@red-dove.com', url='http://code.google.com/p/logutils/', description='Logging utilities', long_description = description(), license='Copyright (C) 2010-2013 by Vinay Sajip. All Rights Reserved. See LICENSE.txt for license.', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", 'Topic :: Software Development', ], packages=['logutils'], cmdclass={ 'test': TestCommand, }, ) logutils-0.3.3/tests/0000755000175000017500000000000012100227273013553 5ustar vinayvinaylogutils-0.3.3/tests/test_adapter.py0000644000175000017500000000524212100226454016607 0ustar vinayvinay# # Copyright (C) 2008-2013 Vinay Sajip. See LICENSE.txt for details. # import logging from logutils.adapter import LoggerAdapter from logutils.testing import TestHandler, Matcher import unittest class AdapterTest(unittest.TestCase): def setUp(self): self.handler = h = TestHandler(Matcher()) self.logger = l = logging.getLogger() l.addHandler(h) self.adapter = LoggerAdapter(l, {}) def tearDown(self): self.logger.removeHandler(self.handler) self.handler.close() def test_simple(self): "Simple test of logging test harness." # Just as a demo, let's log some messages. # Only one should show up in the log. self.adapter.debug("This won't show up.") self.adapter.info("Neither will this.") self.adapter.warning("But this will.") h = self.handler self.assertTrue(h.matches(levelno=logging.WARNING)) self.assertFalse(h.matches(levelno=logging.DEBUG)) self.assertFalse(h.matches(levelno=logging.INFO)) def test_partial(self): "Test of partial matching in logging test harness." # Just as a demo, let's log some messages. # Only one should show up in the log. self.adapter.debug("This won't show up.") self.adapter.info("Neither will this.") self.adapter.warning("But this will.") h = self.handler self.assertTrue(h.matches(msg="ut th")) # from "But this will" self.assertTrue(h.matches(message="ut th")) # from "But this will" self.assertFalse(h.matches(message="either")) self.assertFalse(h.matches(message="won't")) def test_multiple(self): "Test of matching multiple values in logging test harness." # Just as a demo, let's log some messages. # Only one should show up in the log. self.adapter.debug("This won't show up.") self.adapter.info("Neither will this.") self.adapter.warning("But this will.") self.adapter.error("And so will this.") h = self.handler self.assertTrue(h.matches(levelno=logging.WARNING, message='ut th')) self.assertTrue(h.matches(levelno=logging.ERROR, message='nd so w')) self.assertFalse(h.matches(levelno=logging.INFO)) def test_hashandlers(self): "Test of hasHandlers() functionality." self.assertTrue(self.adapter.hasHandlers()) self.logger.removeHandler(self.handler) self.assertFalse(self.adapter.hasHandlers()) self.logger.addHandler(self.handler) self.assertTrue(self.adapter.hasHandlers()) if __name__ == '__main__': unittest.main() logutils-0.3.3/tests/test_messages.py0000644000175000017500000000254611462231313017002 0ustar vinayvinayimport logutils import sys import unittest class MessageTest(unittest.TestCase): if sys.version_info[:2] >= (2, 6): def test_braces(self): "Test whether brace-formatting works." __ = logutils.BraceMessage m = __('Message with {0} {1}', 2, 'placeholders') self.assertEqual(str(m), 'Message with 2 placeholders') m = __('Message with {0:d} {1}', 2, 'placeholders') self.assertEqual(str(m), 'Message with 2 placeholders') m = __('Message without {0:x} {1}', 16, 'placeholders') self.assertEqual(str(m), 'Message without 10 placeholders') class Dummy: pass dummy = Dummy() dummy.x, dummy.y = 0.0, 1.0 m = __('Message with coordinates: ({point.x:.2f}, {point.y:.2f})', point=dummy) self.assertEqual(str(m), 'Message with coordinates: (0.00, 1.00)') def test_dollars(self): "Test whether dollar-formatting works." __ = logutils.DollarMessage m = __('Message with $num ${what}', num=2, what='placeholders') self.assertEqual(str(m), 'Message with 2 placeholders') ignored = object() self.assertRaises(TypeError, __, 'Message with $num ${what}', ignored, num=2, what='placeholders') logutils-0.3.3/tests/test_redis.py0000644000175000017500000000726112100226551016276 0ustar vinayvinay# # Copyright (C) 2011-2013 Vinay Sajip. See LICENSE.txt for details. # import logging from logutils.testing import TestHandler, Matcher from logutils.redis import RedisQueueHandler, RedisQueueListener from redis import Redis import socket import subprocess import time import unittest class QueueListener(RedisQueueListener): def dequeue(self, block): record = RedisQueueListener.dequeue(self, block) if record: record = logging.makeLogRecord(record) return record class RedisQueueTest(unittest.TestCase): def setUp(self): self.handler = h = TestHandler(Matcher()) self.logger = l = logging.getLogger() self.server = subprocess.Popen(['redis-server'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.wait_for_server() self.queue = q = Redis() self.qh = qh = RedisQueueHandler(redis=q) self.ql = ql = QueueListener(h, redis=q) ql.start() l.addHandler(qh) def tearDown(self): self.logger.removeHandler(self.qh) self.qh.close() self.handler.close() self.server.terminate() def wait_for_server(self): maxtime = time.time() + 2 # 2 seconds to wait for server sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) while time.time() < maxtime: try: sock.connect(('localhost', 6379)) break except socket.error: pass if time.time() >= maxtime: raise Exception('unable to connect to Redis server') sock.close() def test_simple(self): "Simple test of queue handling and listening." # Just as a demo, let's log some messages. # Only one should show up in the log. self.logger.debug("This won't show up.") self.logger.info("Neither will this.") self.logger.warning("But this will.") self.ql.stop() #ensure all records have come through. h = self.handler #import pdb; pdb.set_trace() self.assertTrue(h.matches(levelno=logging.WARNING)) self.assertFalse(h.matches(levelno=logging.DEBUG)) self.assertFalse(h.matches(levelno=logging.INFO)) def test_partial(self): "Test of partial matching through queues." # Just as a demo, let's log some messages. # Only one should show up in the log. self.logger.debug("This won't show up.") self.logger.info("Neither will this.") self.logger.warning("But this will.") self.ql.stop() #ensure all records have come through. h = self.handler self.assertTrue(h.matches(msg="ut th")) # from "But this will" self.assertTrue(h.matches(message="ut th")) # from "But this will" self.assertFalse(h.matches(message="either")) self.assertFalse(h.matches(message="won't")) def test_multiple(self): "Test of matching multiple values through queues." # Just as a demo, let's log some messages. # Only one should show up in the log. self.logger.debug("This won't show up.") self.logger.info("Neither will this.") self.logger.warning("But this will.") self.logger.error("And so will this.") self.ql.stop() #ensure all records have come through. h = self.handler self.assertTrue(h.matches(levelno=logging.WARNING, message='ut thi')) self.assertTrue(h.matches(levelno=logging.ERROR, message='nd so wi')) self.assertFalse(h.matches(levelno=logging.INFO)) if __name__ == '__main__': unittest.main() logutils-0.3.3/tests/test_queue.py0000644000175000017500000000525412100226542016314 0ustar vinayvinay# # Copyright (C) 2010-2013 Vinay Sajip. See LICENSE.txt for details. # import logging from logutils.testing import TestHandler, Matcher from logutils.queue import QueueHandler, QueueListener, queue import unittest class QueueTest(unittest.TestCase): def setUp(self): self.handler = h = TestHandler(Matcher()) self.logger = l = logging.getLogger() self.queue = q = queue.Queue(-1) self.qh = qh = QueueHandler(q) self.ql = ql = QueueListener(q, h) ql.start() l.addHandler(qh) def tearDown(self): self.logger.removeHandler(self.qh) self.qh.close() self.handler.close() def test_simple(self): "Simple test of queue handling and listening." # Just as a demo, let's log some messages. # Only one should show up in the log. self.logger.debug("This won't show up.") self.logger.info("Neither will this.") self.logger.warning("But this will.") self.ql.stop() #ensure all records have come through. h = self.handler #import pdb; pdb.set_trace() self.assertTrue(h.matches(levelno=logging.WARNING)) self.assertFalse(h.matches(levelno=logging.DEBUG)) self.assertFalse(h.matches(levelno=logging.INFO)) def test_partial(self): "Test of partial matching through queues." # Just as a demo, let's log some messages. # Only one should show up in the log. self.logger.debug("This won't show up.") self.logger.info("Neither will this.") self.logger.warning("But this will.") self.ql.stop() #ensure all records have come through. h = self.handler self.assertTrue(h.matches(msg="ut th")) # from "But this will" self.assertTrue(h.matches(message="ut th")) # from "But this will" self.assertFalse(h.matches(message="either")) self.assertFalse(h.matches(message="won't")) def test_multiple(self): "Test of matching multiple values through queues." # Just as a demo, let's log some messages. # Only one should show up in the log. self.logger.debug("This won't show up.") self.logger.info("Neither will this.") self.logger.warning("But this will.") self.logger.error("And so will this.") self.ql.stop() #ensure all records have come through. h = self.handler self.assertTrue(h.matches(levelno=logging.WARNING, message='ut thi')) self.assertTrue(h.matches(levelno=logging.ERROR, message='nd so wi')) self.assertFalse(h.matches(levelno=logging.INFO)) if __name__ == '__main__': unittest.main() logutils-0.3.3/tests/test_formatter.py0000644000175000017500000000516412100226473017176 0ustar vinayvinay# # Copyright (C) 2009-2013 Vinay Sajip. See LICENSE.txt for details. # import logging import logutils import os import sys import unittest class FormatterTest(unittest.TestCase): def setUp(self): self.common = { 'name': 'formatter.test', 'level': logging.DEBUG, 'pathname': os.path.join('path', 'to', 'dummy.ext'), 'lineno': 42, 'exc_info': None, 'func': None, 'msg': 'Message with %d %s', 'args': (2, 'placeholders'), } self.variants = { } def get_record(self, name=None): result = dict(self.common) if name is not None: result.update(self.variants[name]) return logging.makeLogRecord(result) def test_percent(self): "Test %-formatting" r = self.get_record() f = logutils.Formatter('${%(message)s}') self.assertEqual(f.format(r), '${Message with 2 placeholders}') f = logutils.Formatter('%(random)s') self.assertRaises(KeyError, f.format, r) self.assertFalse(f.usesTime()) f = logutils.Formatter('%(asctime)s') self.assertTrue(f.usesTime()) f = logutils.Formatter('asctime') self.assertFalse(f.usesTime()) if sys.version_info[:2] >= (2, 6): def test_braces(self): "Test {}-formatting" r = self.get_record() f = logutils.Formatter('$%{message}%$', style='{') self.assertEqual(f.format(r), '$%Message with 2 placeholders%$') f = logutils.Formatter('{random}', style='{') self.assertRaises(KeyError, f.format, r) self.assertFalse(f.usesTime()) f = logutils.Formatter('{asctime}', style='{') self.assertTrue(f.usesTime()) f = logutils.Formatter('asctime', style='{') self.assertFalse(f.usesTime()) def test_dollars(self): "Test $-formatting" r = self.get_record() f = logutils.Formatter('$message', style='$') self.assertEqual(f.format(r), 'Message with 2 placeholders') f = logutils.Formatter('$$%${message}%$$', style='$') self.assertEqual(f.format(r), '$%Message with 2 placeholders%$') f = logutils.Formatter('${random}', style='$') self.assertRaises(KeyError, f.format, r) self.assertFalse(f.usesTime()) f = logutils.Formatter('${asctime}', style='$') self.assertTrue(f.usesTime()) f = logutils.Formatter('$asctime', style='$') self.assertTrue(f.usesTime()) f = logutils.Formatter('asctime', style='$') self.assertFalse(f.usesTime()) logutils-0.3.3/tests/logutil_tests.py0000644000175000017500000000124312100226413017021 0ustar vinayvinay# # Copyright (C) 2008-2013 Vinay Sajip. See LICENSE.txt for details. # import sys from test_testing import LoggingTest from test_dictconfig import ConfigDictTest from test_queue import QueueTest from test_formatter import FormatterTest from test_messages import MessageTest from test_colorize import ColorizeTest try: from test_redis import RedisQueueTest except ImportError: pass # The adapter won't work in < 2.5 because the "extra" parameter used by it # only appeared in 2.5 :-( if sys.version_info[:2] >= (2, 5): from test_adapter import AdapterTest else: print("LoggerAdapter won't work in Python < 2.5, so its tests are being " "skipped.") logutils-0.3.3/tests/test_testing.py0000644000175000017500000000437112100226557016652 0ustar vinayvinay# # Copyright (C) 2010-2013 Vinay Sajip. See LICENSE.txt for details. # import logging from logutils.testing import TestHandler, Matcher import unittest class LoggingTest(unittest.TestCase): def setUp(self): self.handler = h = TestHandler(Matcher()) self.logger = l = logging.getLogger() l.addHandler(h) def tearDown(self): self.logger.removeHandler(self.handler) self.handler.close() def test_simple(self): "Simple test of logging test harness." # Just as a demo, let's log some messages. # Only one should show up in the log. self.logger.debug("This won't show up.") self.logger.info("Neither will this.") self.logger.warning("But this will.") h = self.handler self.assertTrue(h.matches(levelno=logging.WARNING)) self.assertFalse(h.matches(levelno=logging.DEBUG)) self.assertFalse(h.matches(levelno=logging.INFO)) def test_partial(self): "Test of partial matching in logging test harness." # Just as a demo, let's log some messages. # Only one should show up in the log. self.logger.debug("This won't show up.") self.logger.info("Neither will this.") self.logger.warning("But this will.") h = self.handler self.assertTrue(h.matches(msg="ut th")) # from "But this will" self.assertTrue(h.matches(message="ut th")) # from "But this will" self.assertFalse(h.matches(message="either")) self.assertFalse(h.matches(message="won't")) def test_multiple(self): "Test of matching multiple values in logging test harness." # Just as a demo, let's log some messages. # Only one should show up in the log. self.logger.debug("This won't show up.") self.logger.info("Neither will this.") self.logger.warning("But this will.") self.logger.error("And so will this.") h = self.handler self.assertTrue(h.matches(levelno=logging.WARNING, message='ut thi')) self.assertTrue(h.matches(levelno=logging.ERROR, message='nd so wi')) self.assertFalse(h.matches(levelno=logging.INFO)) if __name__ == '__main__': unittest.main() logutils-0.3.3/tests/test_colorize.py0000644000175000017500000000116212100226265017012 0ustar vinayvinay# # Copyright (C) 2012-2013 Vinay Sajip. See LICENSE.txt for details. # import logging import logutils.colorize import os import sys import unittest if sys.version_info[0] < 3: u = lambda o: unicode(o, 'unicode_escape') else: u = lambda o: o class ColorizeTest(unittest.TestCase): def test_colorize(self): logger = logging.getLogger() handler = logutils.colorize.ColorizingStreamHandler() logger.addHandler(handler) try: logger.warning(u('Some unicode string with some \u015b\u0107\u017a\xf3\u0142 chars')) finally: logger.removeHandler(handler) logutils-0.3.3/tests/mytest.py0000644000175000017500000000030511662473756015475 0ustar vinayvinayfrom __future__ import absolute_import from logutils.testing import TestHandler, Matcher class MyTestHandler(TestHandler): def __init__(self): TestHandler.__init__(self, Matcher()) logutils-0.3.3/tests/test_dictconfig.py0000644000175000017500000004751012100226312017275 0ustar vinayvinay# # Copyright 2009-2013 by Vinay Sajip. See LICENSE.txt for details. # import logging from logutils.adapter import LoggerAdapter from logutils.dictconfig import dictConfig, named_handlers_supported from logutils.testing import TestHandler, Matcher import sys import unittest try: StandardError except NameError: StandardError = Exception class ExceptionFormatter(logging.Formatter): """A special exception formatter.""" def formatException(self, ei): return "Got a [%s]" % ei[0].__name__ def formatFunc(format, datefmt=None): return logging.Formatter(format, datefmt) def testHandler(): return TestHandler(Matcher()) def handlerFunc(): return logging.StreamHandler() class CustomHandler(logging.StreamHandler): pass class ConfigDictTest(unittest.TestCase): """Reading logging config from a dictionary.""" def setUp(self): self.logger = l = logging.getLogger() self.adapter = LoggerAdapter(l, {}) logger_dict = logging.getLogger().manager.loggerDict logging._acquireLock() try: self.saved_handlers = logging._handlers.copy() self.saved_handler_list = logging._handlerList[:] self.saved_loggers = logger_dict.copy() self.saved_level_names = logging._levelNames.copy() finally: logging._releaseLock() self.root_logger = logging.getLogger("") self.original_logging_level = self.root_logger.getEffectiveLevel() def tearDown(self): self.root_logger.setLevel(self.original_logging_level) logging._acquireLock() try: logging._levelNames.clear() logging._levelNames.update(self.saved_level_names) logging._handlers.clear() logging._handlers.update(self.saved_handlers) logging._handlerList[:] = self.saved_handler_list loggerDict = logging.getLogger().manager.loggerDict loggerDict.clear() loggerDict.update(self.saved_loggers) finally: logging._releaseLock() message_num = 0 def next_message(self): """Generate a message consisting solely of an auto-incrementing integer.""" self.message_num += 1 return "%d" % self.message_num # config0 is a standard configuration. config0 = { 'version': 1, 'formatters': { 'form1' : { 'format' : '%(levelname)s ++ %(message)s', }, }, 'handlers' : { 'hand1' : { '()': testHandler, 'formatter': 'form1', } }, 'root' : { 'level' : 'WARNING', 'handlers' : ['hand1'], }, } # config1 adds a little to the standard configuration. config1 = { 'version': 1, 'formatters': { 'form1' : { 'format' : '%(levelname)s ++ %(message)s', }, }, 'handlers' : { 'hand1' : { '()': testHandler, 'formatter': 'form1', } }, 'loggers' : { 'compiler.parser' : { 'level' : 'DEBUG', 'handlers' : ['hand1'], }, }, 'root' : { 'level' : 'WARNING', }, } # config2 has a subtle configuration error that should be reported config2 = { 'formatters': { 'form1' : { 'format' : '%(levelname)s ++ %(message)s', }, }, 'handlers' : { 'hand1' : { 'class' : 'logging.StreamHandler', 'formatter' : 'form1', 'level' : 'NOTSET', 'stream' : 'ext://sys.stdbout', }, }, 'loggers' : { 'compiler.parser' : { 'level' : 'DEBUG', 'handlers' : ['hand1'], }, }, 'root' : { 'level' : 'WARNING', }, } #As config1 but with a misspelt level on a handler config2a = { 'formatters': { 'form1' : { 'format' : '%(levelname)s ++ %(message)s', }, }, 'handlers' : { 'hand1' : { 'class' : 'logging.StreamHandler', 'formatter' : 'form1', 'level' : 'NTOSET', 'stream' : 'ext://sys.stdout', }, }, 'loggers' : { 'compiler.parser' : { 'level' : 'DEBUG', 'handlers' : ['hand1'], }, }, 'root' : { 'level' : 'WARNING', }, } #As config1 but with a misspelt level on a logger config2b = { 'formatters': { 'form1' : { 'format' : '%(levelname)s ++ %(message)s', }, }, 'handlers' : { 'hand1' : { 'class' : 'logging.StreamHandler', 'formatter' : 'form1', 'level' : 'NOTSET', 'stream' : 'ext://sys.stdout', }, }, 'loggers' : { 'compiler.parser' : { 'level' : 'DEBUG', 'handlers' : ['hand1'], }, }, 'root' : { 'level' : 'WRANING', }, } # config3 has a less subtle configuration error config3 = { 'formatters': { 'form1' : { 'format' : '%(levelname)s ++ %(message)s', }, }, 'handlers' : { 'hand1' : { 'class' : 'logging.StreamHandler', 'formatter' : 'misspelled_name', 'level' : 'NOTSET', 'stream' : 'ext://sys.stdout', }, }, 'loggers' : { 'compiler.parser' : { 'level' : 'DEBUG', 'handlers' : ['hand1'], }, }, 'root' : { 'level' : 'WARNING', }, } # config4 specifies a custom formatter class to be loaded config4 = { 'version': 1, 'formatters': { 'form1' : { '()' : __name__ + '.ExceptionFormatter', 'format' : '%(levelname)s:%(name)s:%(message)s', }, }, 'handlers' : { 'hand1' : { '()': testHandler, 'formatter': 'form1', } }, 'root' : { 'level' : 'NOTSET', 'handlers' : ['hand1'], }, } # As config4 but using an actual callable rather than a string config4a = { 'version': 1, 'formatters': { 'form1' : { '()' : ExceptionFormatter, 'format' : '%(levelname)s:%(name)s:%(message)s', }, 'form2' : { '()' : __name__ + '.formatFunc', 'format' : '%(levelname)s:%(name)s:%(message)s', }, 'form3' : { '()' : formatFunc, 'format' : '%(levelname)s:%(name)s:%(message)s', }, }, 'handlers' : { 'hand1' : { '()': testHandler, 'formatter': 'form1', }, 'hand2' : { '()' : handlerFunc, }, }, 'root' : { 'level' : 'NOTSET', 'handlers' : ['hand1'], }, } # config5 specifies a custom handler class to be loaded config5 = { 'version': 1, 'formatters': { 'form1' : { 'format' : '%(levelname)s ++ %(message)s', }, }, 'handlers' : { 'hand1' : { '()': testHandler, 'formatter': 'form1', } }, 'loggers' : { 'compiler.parser' : { 'level' : 'DEBUG', 'handlers' : ['hand1'], }, }, 'root' : { 'level' : 'WARNING', }, } # config6 specifies a custom handler class to be loaded # but has bad arguments config6 = { 'formatters': { 'form1' : { 'format' : '%(levelname)s ++ %(message)s', }, }, 'handlers' : { 'hand1' : { 'class' : __name__ + '.CustomHandler', 'formatter' : 'form1', 'level' : 'NOTSET', 'stream' : 'ext://sys.stdout', '9' : 'invalid parameter name', }, }, 'loggers' : { 'compiler.parser' : { 'level' : 'DEBUG', 'handlers' : ['hand1'], }, }, 'root' : { 'level' : 'WARNING', }, } #config 7 does not define compiler.parser but defines compiler.lexer #so compiler.parser should be disabled after applying it config7 = { 'version': 1, 'formatters': { 'form1' : { 'format' : '%(levelname)s ++ %(message)s', }, }, 'handlers' : { 'hand1' : { '()': testHandler, 'formatter': 'form1', } }, 'loggers' : { 'compiler.lexer' : { 'level' : 'DEBUG', 'handlers' : ['hand1'], }, }, 'root' : { 'level' : 'WARNING', }, } config8 = { 'version': 1, 'disable_existing_loggers' : False, 'formatters': { 'form1' : { 'format' : '%(levelname)s ++ %(message)s', }, }, 'handlers' : { 'hand1' : { '()': testHandler, 'formatter': 'form1', } }, 'loggers' : { 'compiler' : { 'level' : 'DEBUG', 'handlers' : ['hand1'], }, 'compiler.lexer' : { }, }, 'root' : { 'level' : 'WARNING', }, } config9 = { 'version': 1, 'formatters': { 'form1' : { 'format' : '%(levelname)s ++ %(message)s', }, }, 'handlers' : { 'hand1' : { '()': testHandler, 'formatter': 'form1', } }, 'loggers' : { 'compiler.parser' : { 'level' : 'WARNING', 'handlers' : ['hand1'], }, }, 'root' : { 'level' : 'NOTSET', }, } config9a = { 'version': 1, 'incremental' : True, 'handlers' : { 'hand1' : { 'level' : 'WARNING', }, }, 'loggers' : { 'compiler.parser' : { 'level' : 'INFO', }, }, } config9b = { 'version': 1, 'incremental' : True, 'handlers' : { 'hand1' : { 'level' : 'INFO', }, }, 'loggers' : { 'compiler.parser' : { 'level' : 'INFO', }, }, } #As config1 but with a filter added config10 = { 'version': 1, 'formatters': { 'form1' : { 'format' : '%(levelname)s ++ %(message)s', }, }, 'filters' : { 'filt1' : { 'name' : 'compiler.parser', }, }, 'handlers' : { 'hand1' : { '()': testHandler, 'formatter': 'form1', 'filters' : ['filt1'], } }, 'loggers' : { 'compiler.parser' : { 'level' : 'DEBUG', 'filters' : ['filt1'], }, }, 'root' : { 'level' : 'WARNING', 'handlers' : ['hand1'], }, } # As config10, but declaring a handler in a module using # absolute imports config11 = { 'version': 1, 'formatters': { 'form1' : { 'format' : '%(levelname)s ++ %(message)s', }, }, 'filters' : { 'filt1' : { 'name' : 'compiler.parser', }, }, 'handlers' : { 'hand1' : { '()': 'mytest.MyTestHandler', 'formatter': 'form1', 'filters' : ['filt1'], } }, 'loggers' : { 'compiler.parser' : { 'level' : 'DEBUG', 'filters' : ['filt1'], }, }, 'root' : { 'level' : 'WARNING', 'handlers' : ['hand1'], }, } def apply_config(self, conf): dictConfig(conf) def test_config0_ok(self): # A simple config which overrides the default settings. self.apply_config(self.config0) logger = logging.getLogger() # Won't output anything logger.info(self.next_message()) # Outputs a message logger.error(self.next_message()) h = logger.handlers[0] self.assertEqual(1, h.count) self.assertTrue(h.matchall([ dict(levelname='ERROR', message='2') ])) def test_config1_ok(self, config=config1): # A config defining a sub-parser as well. self.apply_config(config) logger = logging.getLogger("compiler.parser") # Both will output a message logger.info(self.next_message()) logger.error(self.next_message()) h = logger.handlers[0] self.assertTrue(h.matchall([ dict(levelname='INFO', message='1'), dict(levelname='ERROR', message='2'), ])) def test_config2_failure(self): # A simple config which overrides the default settings. self.assertRaises(StandardError, self.apply_config, self.config2) def test_config2a_failure(self): # A simple config which overrides the default settings. self.assertRaises(StandardError, self.apply_config, self.config2a) def test_config2b_failure(self): # A simple config which overrides the default settings. self.assertRaises(StandardError, self.apply_config, self.config2b) def test_config3_failure(self): # A simple config which overrides the default settings. self.assertRaises(StandardError, self.apply_config, self.config3) def test_config4_ok(self): # A config specifying a custom formatter class. self.apply_config(self.config4) logger = logging.getLogger() h = logger.handlers[0] try: raise RuntimeError() except RuntimeError: logging.exception("just testing") self.assertEquals(h.formatted[0], "ERROR:root:just testing\nGot a [RuntimeError]") def test_config4a_ok(self): # A config specifying a custom formatter class. self.apply_config(self.config4a) logger = logging.getLogger() h = logger.handlers[0] try: raise RuntimeError() except RuntimeError: logging.exception("just testing") self.assertEquals(h.formatted[0], "ERROR:root:just testing\nGot a [RuntimeError]") def test_config5_ok(self): self.test_config1_ok(config=self.config5) def test_config6_failure(self): self.assertRaises(StandardError, self.apply_config, self.config6) def test_config7_ok(self): self.apply_config(self.config1) logger = logging.getLogger("compiler.parser") # Both will output a message logger.info(self.next_message()) logger.error(self.next_message()) h = logger.handlers[0] self.assertTrue(h.matchall([ dict(levelname='INFO', message='1'), dict(levelname='ERROR', message='2'), ])) self.apply_config(self.config7) logger = logging.getLogger("compiler.parser") self.assertTrue(logger.disabled) logger = logging.getLogger("compiler.lexer") # Both will output a message h = logger.handlers[0] logger.info(self.next_message()) logger.error(self.next_message()) self.assertTrue(h.matchall([ dict(levelname='INFO', message='3'), dict(levelname='ERROR', message='4'), ])) #Same as test_config_7_ok but don't disable old loggers. def test_config_8_ok(self): self.apply_config(self.config1) logger = logging.getLogger("compiler.parser") # Both will output a message logger.info(self.next_message()) logger.error(self.next_message()) h = logger.handlers[0] self.assertTrue(h.matchall([ dict(levelname='INFO', message='1'), dict(levelname='ERROR', message='2'), ])) self.apply_config(self.config8) logger = logging.getLogger("compiler.parser") self.assertFalse(logger.disabled) toplogger = logging.getLogger("compiler") # Both will output a message logger.info(self.next_message()) logger.error(self.next_message()) logger = logging.getLogger("compiler.lexer") # Both will output a message logger.info(self.next_message()) logger.error(self.next_message()) h = toplogger.handlers[0] self.assertTrue(h.matchall([ dict(levelname='INFO', message='3'), dict(levelname='ERROR', message='4'), dict(levelname='INFO', message='5'), dict(levelname='ERROR', message='6'), ])) def test_config_9_ok(self): self.apply_config(self.config9) logger = logging.getLogger("compiler.parser") #Nothing will be output since both handler and logger are set to WARNING logger.info(self.next_message()) h = logger.handlers[0] self.assertEqual(0, h.count) self.apply_config(self.config9a) #Nothing will be output since both handler is still set to WARNING logger.info(self.next_message()) h = logger.handlers[0] nhs = named_handlers_supported() if nhs: self.assertEqual(0, h.count) else: self.assertEqual(1, h.count) self.apply_config(self.config9b) #Message should now be output logger.info(self.next_message()) if nhs: h = logger.handlers[0] self.assertTrue(h.matchall([ dict(levelname='INFO', message='3'), ])) else: self.assertEqual(2, h.count) def test_config_10_ok(self): self.apply_config(self.config10) logger = logging.getLogger("compiler.parser") logger.warning(self.next_message()) logger = logging.getLogger('compiler') #Not output, because filtered logger.warning(self.next_message()) logger = logging.getLogger('compiler.lexer') #Not output, because filtered logger.warning(self.next_message()) logger = logging.getLogger("compiler.parser.codegen") #Output, as not filtered logger.error(self.next_message()) h = logging.getLogger().handlers[0] self.assertTrue(h.matchall([ dict(levelname='WARNING', message='1'), dict(levelname='ERROR', message='4'), ])) def test_config_11_ok(self): self.apply_config(self.config11) h = logging.getLogger().handlers[0] self.assertEqual(h.__module__, 'mytest') self.assertEqual(h.__class__.__name__, 'MyTestHandler') logutils-0.3.3/LICENSE.txt0000644000175000017500000000267412100225263014242 0ustar vinayvinayCopyright (c) 2008-2013 by Vinay Sajip. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The name(s) of the copyright holder(s) may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. logutils-0.3.3/doc/0000755000175000017500000000000012100227273013156 5ustar vinayvinaylogutils-0.3.3/doc/whatsnew.rst0000644000175000017500000000003211517256474015563 0ustar vinayvinay.. include:: ../NEWS.txt logutils-0.3.3/doc/libraries.rst0000644000175000017500000000124011451413756015674 0ustar vinayvinayConfiguring Libraries ===================== When developing libraries, you'll probably need to use the :class:`~logutils.NullHandler` class. **N.B.** This is part of the standard library since Python 2.7 / 3.1, so the version here is for use with earlier Python versions. Typical usage:: import logging try: from logging import NullHandler except ImportError: from logutils import NullHandler # use this in all your library's subpackages/submodules logger = logging.getLogger(__name__) # use this just in your library's top-level package logger.addHandler(NullHandler()) .. autoclass:: logutils.NullHandler :members: logutils-0.3.3/doc/queue.rst0000644000175000017500000000012711662477224015053 0ustar vinayvinayWorking with queues =================== .. automodule:: logutils.queue :members: logutils-0.3.3/doc/dictconfig.rst0000644000175000017500000000064311451415757016042 0ustar vinayvinayDictionary-based Configuration ============================== This module implements dictionary-based configuration according to PEP 391. **N.B.** This is part of the standard library since Python 2.7 / 3.2, so the version here is for use with earlier Python versions. .. automodule:: logutils.dictconfig .. autoclass:: logutils.dictconfig.DictConfigurator :members: configure .. autofunction:: dictConfig logutils-0.3.3/doc/Makefile0000644000175000017500000000447711451373074014643 0ustar vinayvinay# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html web pickle htmlhelp latex changes linkcheck help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview over all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" clean: -rm -rf _build/* html: mkdir -p _build/html _build/doctrees $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html @echo @echo "Build finished. The HTML pages are in _build/html." pickle: mkdir -p _build/pickle _build/doctrees $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle @echo @echo "Build finished; now you can process the pickle files." web: pickle json: mkdir -p _build/json _build/doctrees $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: mkdir -p _build/htmlhelp _build/doctrees $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in _build/htmlhelp." latex: mkdir -p _build/latex _build/doctrees $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex @echo @echo "Build finished; the LaTeX files are in _build/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: mkdir -p _build/changes _build/doctrees $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes @echo @echo "The overview file is in _build/changes." linkcheck: mkdir -p _build/linkcheck _build/doctrees $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in _build/linkcheck/output.txt." logutils-0.3.3/doc/adapter.rst0000644000175000017500000000104011451425601015326 0ustar vinayvinayWorking with Logger adapters ============================ **N.B.** This is part of the standard library since Python 2.6 / 3.1, so the version here is for use with earlier Python versions. The class was enhanced for Python 3.2, so you may wish to use this version with earlier Python versions. However, note that the :class:`~logutils.adapter.LoggerAdapter` class will **not** work with Python 2.4 or earlier, as it uses the `extra` keyword argument which was added in later Python versions. .. automodule:: logutils.adapter :members: logutils-0.3.3/doc/index.rst0000644000175000017500000000121511662477246015041 0ustar vinayvinay.. Logutils documentation master file, created by sphinx-quickstart on Fri Oct 1 15:54:52 2010. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Logutils documentation ====================== .. automodule:: logutils For recent changes, see :ref:`whats-new`. There are a number of subcomponents to this package, relating to particular tasks you may want to perform: .. toctree:: :maxdepth: 2 libraries queue redis testing dictconfig adapter http colorize Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` logutils-0.3.3/doc/redis.rst0000644000175000017500000000050111662500341015014 0ustar vinayvinayWorking with Redis queues ========================= :class:`~logutils.queue.QueueHandler` and :class:`~logutils.queue.QueueListener` classes are provided to facilitate interfacing with Redis. .. autoclass:: logutils.redis.RedisQueueHandler :members: .. autoclass:: logutils.redis.RedisQueueListener :members: logutils-0.3.3/doc/colorize.rst0000644000175000017500000000047611517254541015556 0ustar vinayvinayColorizing Console Streams ========================== ``ColorizingStreamHandler`` is a handler which allows colorizing of console streams, described here_ in more detail. .. _here: http://plumberjack.blogspot.com/2010/12/colorizing-logging-output-in-terminals.html .. automodule:: logutils.colorize :members: logutils-0.3.3/doc/conf.py0000644000175000017500000001417412100225543014462 0ustar vinayvinay# -*- coding: utf-8 -*- # # Logutils documentation build configuration file, created by # sphinx-quickstart on Fri Oct 1 15:54:52 2010. # # This file is execfile()d with the current directory set to its containing dir. # # The contents of this file are pickled, so don't put values in the namespace # that aren't pickleable (module imports are okay, they're removed automatically). # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If your extensions (or modules documented by autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.append(os.path.abspath('..')) # General configuration # --------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Logutils' copyright = u'2010-2013, Vinay Sajip' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.3' # The full version, including alpha/beta/rc tags. release = '0.3.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # Options for HTML output # ----------------------- # The style sheet to use for HTML and HTML Help pages. A file of that name # must exist either in Sphinx' static/ path, or in one of the custom paths # given in html_static_path. html_style = 'default.css' # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, the reST sources are included in the HTML build as _sources/. #html_copy_source = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'Logutilsdoc' # Options for LaTeX output # ------------------------ # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, document class [howto/manual]). latex_documents = [ ('index', 'Logutils.tex', ur'Logutils Documentation', ur'Vinay Sajip', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { 'http://docs.python.org/dev': None, } logutils-0.3.3/doc/testing.rst0000644000175000017500000000504711451414234015376 0ustar vinayvinayUnit testing ============ When developing unit tests, you may find the :class:`~logutils.testing.TestHandler` and :class:`~logutils.testing.Matcher` classes useful. Typical usage:: import logging from logutils.testing import TestHandler, Matcher import unittest class LoggingTest(unittest.TestCase): def setUp(self): self.handler = h = TestHandler(Matcher()) self.logger = l = logging.getLogger() l.addHandler(h) def tearDown(self): self.logger.removeHandler(self.handler) self.handler.close() def test_simple(self): "Simple test of logging test harness." # Just as a demo, let's log some messages. # Only one should show up in the log. self.logger.debug("This won't show up.") self.logger.info("Neither will this.") self.logger.warning("But this will.") h = self.handler self.assertTrue(h.matches(levelno=logging.WARNING)) self.assertFalse(h.matches(levelno=logging.DEBUG)) self.assertFalse(h.matches(levelno=logging.INFO)) def test_partial(self): "Test of partial matching in logging test harness." # Just as a demo, let's log some messages. # Only one should show up in the log. self.logger.debug("This won't show up.") self.logger.info("Neither will this.") self.logger.warning("But this will.") h = self.handler self.assertTrue(h.matches(msg="ut th")) # from "But this will" self.assertTrue(h.matches(message="ut th")) # from "But this will" self.assertFalse(h.matches(message="either")) self.assertFalse(h.matches(message="won't")) def test_multiple(self): "Test of matching multiple values in logging test harness." # Just as a demo, let's log some messages. # Only one should show up in the log. self.logger.debug("This won't show up.") self.logger.info("Neither will this.") self.logger.warning("But this will.") self.logger.error("And so will this.") h = self.handler self.assertTrue(h.matches(levelno=logging.WARNING, message='ut thi')) self.assertTrue(h.matches(levelno=logging.ERROR, message='nd so wi')) self.assertFalse(h.matches(levelno=logging.INFO)) .. automodule:: logutils.testing :members: logutils-0.3.3/doc/http.rst0000644000175000017500000000056711451426014014701 0ustar vinayvinayWorking with web sites ====================== **N.B.** The :class:`~logutils.http.HTTPHandler` class has been present in the :mod:`logging` package since the first release, but was enhanced for Python 3.2 to add options for secure connections and user credentials. You may wish to use this version with earlier Python releases. .. automodule:: logutils.http :members: logutils-0.3.3/NEWS.txt0000644000175000017500000000222112100225401013712 0ustar vinayvinay:orphan: .. _whats-new: What's New in logutils ====================== Version 0.3.3 ------------- - Added encoding support to ColorizingStreamHandler. Version 0.3.2 ------------- - Improvements in QueueListener implementation. - Added redis module with RedisQueueHandler and RedisQueueListener. - Added unit test for a handler in a module where absolute imports are used. Version 0.3.1 ------------- - Improvements in setup.py and documentation. Version 0.3 ----------- - Added caches for BraceMessage/DollarMessage. - Added ColorizingStreamHandler. Version 0.2 ----------- - Updated docstrings for improved documentation. - Added hasHanders() function. - Changed LoggerAdapter.hasHandlers() to use logutils.hasHandlers(). - Documentation improvements. - NullHandler moved to logutils package (from queue package). - Formatter added to logutils package. Adds support for {}- and $-formatting in format strings, as well as %-formatting. - BraceMessage and DollarMessage classes added to facilitate {}- and $- formatting in logging calls (as opposed to Formatter formats). - Added some more unit tests. Version 0.1 ----------- First public release. logutils-0.3.3/README.txt0000644000175000017500000000151212100225242014100 0ustar vinayvinaylogutils 0.3.3 ============== The logutils package provides a set of handlers for the Python standard library's logging package. Some of these handlers are out-of-scope for the standard library, and so they are packaged here. Others are updated versions which have appeared in recent Python releases, but are usable with older versions of Python and so are packaged here. Requirements & Installation --------------------------- The logutils package requires Python 2.5 or greater, and can be installed with the standard Python installation procedure: python setup.py install There is a set of unit tests which you can invoke with python setup.py test before running the installation. Availability & Documentation ---------------------------- The latest version of logutils can be found at: http://code.google.com/p/logutils/ logutils-0.3.3/logutils/0000755000175000017500000000000012100227273014253 5ustar vinayvinaylogutils-0.3.3/logutils/queue.py0000644000175000017500000001657312100226763015770 0ustar vinayvinay# # Copyright (C) 2010-2013 Vinay Sajip. See LICENSE.txt for details. # """ This module contains classes which help you work with queues. A typical application is when you want to log from performance-critical threads, but where the handlers you want to use are slow (for example, :class:`~logging.handlers.SMTPHandler`). In that case, you can create a queue, pass it to a :class:`QueueHandler` instance and use that instance with your loggers. Elsewhere, you can instantiate a :class:`QueueListener` with the same queue and some slow handlers, and call :meth:`~QueueListener.start` on it. This will start monitoring the queue on a separate thread and call all the configured handlers *on that thread*, so that your logging thread is not held up by the slow handlers. Note that as well as in-process queues, you can use these classes with queues from the :mod:`multiprocessing` module. **N.B.** This is part of the standard library since Python 3.2, so the version here is for use with earlier Python versions. """ import logging try: import Queue as queue except ImportError: import queue import threading class QueueHandler(logging.Handler): """ This handler sends events to a queue. Typically, it would be used together with a multiprocessing Queue to centralise logging to file in one process (in a multi-process application), so as to avoid file write contention between processes. :param queue: The queue to send `LogRecords` to. """ def __init__(self, queue): """ Initialise an instance, using the passed queue. """ logging.Handler.__init__(self) self.queue = queue def enqueue(self, record): """ Enqueue a record. The base implementation uses :meth:`~queue.Queue.put_nowait`. You may want to override this method if you want to use blocking, timeouts or custom queue implementations. :param record: The record to enqueue. """ self.queue.put_nowait(record) def prepare(self, record): """ Prepares a record for queuing. The object returned by this method is enqueued. The base implementation formats the record to merge the message and arguments, and removes unpickleable items from the record in-place. You might want to override this method if you want to convert the record to a dict or JSON string, or send a modified copy of the record while leaving the original intact. :param record: The record to prepare. """ # The format operation gets traceback text into record.exc_text # (if there's exception data), and also puts the message into # record.message. We can then use this to replace the original # msg + args, as these might be unpickleable. We also zap the # exc_info attribute, as it's no longer needed and, if not None, # will typically not be pickleable. self.format(record) record.msg = record.message record.args = None record.exc_info = None return record def emit(self, record): """ Emit a record. Writes the LogRecord to the queue, preparing it for pickling first. :param record: The record to emit. """ try: self.enqueue(self.prepare(record)) except (KeyboardInterrupt, SystemExit): raise except: self.handleError(record) class QueueListener(object): """ This class implements an internal threaded listener which watches for LogRecords being added to a queue, removes them and passes them to a list of handlers for processing. :param record: The queue to listen to. :param handlers: The handlers to invoke on everything received from the queue. """ _sentinel = None def __init__(self, queue, *handlers): """ Initialise an instance with the specified queue and handlers. """ self.queue = queue self.handlers = handlers self._stop = threading.Event() self._thread = None def dequeue(self, block): """ Dequeue a record and return it, optionally blocking. The base implementation uses :meth:`~queue.Queue.get`. You may want to override this method if you want to use timeouts or work with custom queue implementations. :param block: Whether to block if the queue is empty. If `False` and the queue is empty, an :class:`~queue.Empty` exception will be thrown. """ return self.queue.get(block) def start(self): """ Start the listener. This starts up a background thread to monitor the queue for LogRecords to process. """ self._thread = t = threading.Thread(target=self._monitor) t.setDaemon(True) t.start() def prepare(self , record): """ Prepare a record for handling. This method just returns the passed-in record. You may want to override this method if you need to do any custom marshalling or manipulation of the record before passing it to the handlers. :param record: The record to prepare. """ return record def handle(self, record): """ Handle a record. This just loops through the handlers offering them the record to handle. :param record: The record to handle. """ record = self.prepare(record) for handler in self.handlers: handler.handle(record) def _monitor(self): """ Monitor the queue for records, and ask the handler to deal with them. This method runs on a separate, internal thread. The thread will terminate if it sees a sentinel object in the queue. """ q = self.queue has_task_done = hasattr(q, 'task_done') while not self._stop.isSet(): try: record = self.dequeue(True) if record is self._sentinel: break self.handle(record) if has_task_done: q.task_done() except queue.Empty: pass # There might still be records in the queue. while True: try: record = self.dequeue(False) if record is self._sentinel: break self.handle(record) if has_task_done: q.task_done() except queue.Empty: break def enqueue_sentinel(self): """ Writes a sentinel to the queue to tell the listener to quit. This implementation uses ``put_nowait()``. You may want to override this method if you want to use timeouts or work with custom queue implementations. """ self.queue.put_nowait(self._sentinel) def stop(self): """ Stop the listener. This asks the thread to terminate, and then waits for it to do so. Note that if you don't call this before your application exits, there may be some records still left on the queue, which won't be processed. """ self._stop.set() self.enqueue_sentinel() self._thread.join() self._thread = None logutils-0.3.3/logutils/redis.py0000644000175000017500000000436412100226735015744 0ustar vinayvinay# # Copyright (C) 2011-2013 Vinay Sajip. See LICENSE.txt for details. # """ This module contains classes which help you work with Redis queues. """ from logutils.queue import QueueHandler, QueueListener try: import cPickle as pickle except ImportError: import pickle class RedisQueueHandler(QueueHandler): """ A QueueHandler implementation which pushes pickled records to a Redis queue using a specified key. :param key: The key to use for the queue. Defaults to "python.logging". :param redis: If specified, this instance is used to communicate with a Redis instance. :param limit: If specified, the queue is restricted to have only this many elements. """ def __init__(self, key='python.logging', redis=None, limit=0): if redis is None: from redis import Redis redis = Redis() self.key = key assert limit >= 0 self.limit = limit QueueHandler.__init__(self, redis) def enqueue(self, record): s = pickle.dumps(vars(record)) self.queue.rpush(self.key, s) if self.limit: self.queue.ltrim(self.key, -self.limit, -1) class RedisQueueListener(QueueListener): """ A QueueListener implementation which fetches pickled records from a Redis queue using a specified key. :param key: The key to use for the queue. Defaults to "python.logging". :param redis: If specified, this instance is used to communicate with a Redis instance. """ def __init__(self, *handlers, **kwargs): redis = kwargs.get('redis') if redis is None: from redis import Redis redis = Redis() self.key = kwargs.get('key', 'python.logging') QueueListener.__init__(self, redis, *handlers) def dequeue(self, block): """ Dequeue and return a record. """ if block: s = self.queue.blpop(self.key)[1] else: s = self.queue.lpop(self.key) if not s: record = None else: record = pickle.loads(s) return record def enqueue_sentinel(self): self.queue.rpush(self.key, '') logutils-0.3.3/logutils/adapter.py0000644000175000017500000000667512100226623016261 0ustar vinayvinay# # Copyright (C) 2010-2013 Vinay Sajip. See LICENSE.txt for details. # import logging import logutils class LoggerAdapter(object): """ An adapter for loggers which makes it easier to specify contextual information in logging output. """ def __init__(self, logger, extra): """ Initialize the adapter with a logger and a dict-like object which provides contextual information. This constructor signature allows easy stacking of LoggerAdapters, if so desired. You can effectively pass keyword arguments as shown in the following example: adapter = LoggerAdapter(someLogger, dict(p1=v1, p2="v2")) """ self.logger = logger self.extra = extra def process(self, msg, kwargs): """ Process the logging message and keyword arguments passed in to a logging call to insert contextual information. You can either manipulate the message itself, the keyword args or both. Return the message and kwargs modified (or not) to suit your needs. Normally, you'll only need to override this one method in a LoggerAdapter subclass for your specific needs. """ kwargs["extra"] = self.extra return msg, kwargs # # Boilerplate convenience methods # def debug(self, msg, *args, **kwargs): """ Delegate a debug call to the underlying logger. """ self.log(logging.DEBUG, msg, *args, **kwargs) def info(self, msg, *args, **kwargs): """ Delegate an info call to the underlying logger. """ self.log(logging.INFO, msg, *args, **kwargs) def warning(self, msg, *args, **kwargs): """ Delegate a warning call to the underlying logger. """ self.log(logging.WARNING, msg, *args, **kwargs) warn = warning def error(self, msg, *args, **kwargs): """ Delegate an error call to the underlying logger. """ self.log(logging.ERROR, msg, *args, **kwargs) def exception(self, msg, *args, **kwargs): """ Delegate an exception call to the underlying logger. """ kwargs["exc_info"] = 1 self.log(logging.ERROR, msg, *args, **kwargs) def critical(self, msg, *args, **kwargs): """ Delegate a critical call to the underlying logger. """ self.log(logging.CRITICAL, msg, *args, **kwargs) def log(self, level, msg, *args, **kwargs): """ Delegate a log call to the underlying logger, after adding contextual information from this adapter instance. """ if self.isEnabledFor(level): msg, kwargs = self.process(msg, kwargs) self.logger._log(level, msg, args, **kwargs) def isEnabledFor(self, level): """ Is this logger enabled for level 'level'? """ if self.logger.manager.disable >= level: return False return level >= self.getEffectiveLevel() def setLevel(self, level): """ Set the specified level on the underlying logger. """ self.logger.setLevel(level) def getEffectiveLevel(self): """ Get the effective level for the underlying logger. """ return self.logger.getEffectiveLevel() def hasHandlers(self): """ See if the underlying logger has any handlers. """ return logutils.hasHandlers(self.logger) logutils-0.3.3/logutils/testing.py0000644000175000017500000001213012100226712016274 0ustar vinayvinay# # Copyright (C) 2010-2013 Vinay Sajip. See LICENSE.txt for details. # import logging from logging.handlers import BufferingHandler class TestHandler(BufferingHandler): """ This handler collects records in a buffer for later inspection by your unit test code. :param matcher: The :class:`~logutils.testing.Matcher` instance to use for matching. """ def __init__(self, matcher): # BufferingHandler takes a "capacity" argument # so as to know when to flush. As we're overriding # shouldFlush anyway, we can set a capacity of zero. # You can call flush() manually to clear out the # buffer. BufferingHandler.__init__(self, 0) self.formatted = [] self.matcher = matcher def shouldFlush(self): """ Should the buffer be flushed? This returns `False` - you'll need to flush manually, usually after your unit test code checks the buffer contents against your expectations. """ return False def emit(self, record): """ Saves the `__dict__` of the record in the `buffer` attribute, and the formatted records in the `formatted` attribute. :param record: The record to emit. """ self.formatted.append(self.format(record)) self.buffer.append(record.__dict__) def flush(self): """ Clears out the `buffer` and `formatted` attributes. """ BufferingHandler.flush(self) self.formatted = [] def matches(self, **kwargs): """ Look for a saved dict whose keys/values match the supplied arguments. Return `True` if found, else `False`. :param kwargs: A set of keyword arguments whose names are LogRecord attributes and whose values are what you want to match in a stored LogRecord. """ result = False for d in self.buffer: if self.matcher.matches(d, **kwargs): result = True break #if not result: # print('*** matcher failed completely on %d records' % len(self.buffer)) return result def matchall(self, kwarglist): """ Accept a list of keyword argument values and ensure that the handler's buffer of stored records matches the list one-for-one. Return `True` if exactly matched, else `False`. :param kwarglist: A list of keyword-argument dictionaries, each of which will be passed to :meth:`matches` with the corresponding record from the buffer. """ if self.count != len(kwarglist): result = False else: result = True for d, kwargs in zip(self.buffer, kwarglist): if not self.matcher.matches(d, **kwargs): result = False break return result @property def count(self): """ The number of records in the buffer. """ return len(self.buffer) class Matcher(object): """ This utility class matches a stored dictionary of :class:`logging.LogRecord` attributes with keyword arguments passed to its :meth:`~logutils.testing.Matcher.matches` method. """ _partial_matches = ('msg', 'message') """ A list of :class:`logging.LogRecord` attribute names which will be checked for partial matches. If not in this list, an exact match will be attempted. """ def matches(self, d, **kwargs): """ Try to match a single dict with the supplied arguments. Keys whose values are strings and which are in self._partial_matches will be checked for partial (i.e. substring) matches. You can extend this scheme to (for example) do regular expression matching, etc. Return `True` if found, else `False`. :param kwargs: A set of keyword arguments whose names are LogRecord attributes and whose values are what you want to match in a stored LogRecord. """ result = True for k in kwargs: v = kwargs[k] dv = d.get(k) if not self.match_value(k, dv, v): #print('*** matcher failed: %s, %r, %r' % (k, dv, v)) result = False break return result def match_value(self, k, dv, v): """ Try to match a single stored value (dv) with a supplied value (v). Return `True` if found, else `False`. :param k: The key value (LogRecord attribute name). :param dv: The stored value to match against. :param v: The value to compare with the stored value. """ if type(v) != type(dv): result = False elif type(dv) is not str or k not in self._partial_matches: result = (v == dv) else: result = dv.find(v) >= 0 #if not result: # print('*** matcher failed on %s: %r vs. %r' % (k, dv, v)) return result logutils-0.3.3/logutils/dictconfig.py0000644000175000017500000005457312100227054016751 0ustar vinayvinay# # Copyright (C) 2009-2013 Vinay Sajip. See LICENSE.txt for details. # import logging.handlers import re import sys import types try: basestring except NameError: basestring = str try: StandardError except NameError: StandardError = Exception IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I) def valid_ident(s): m = IDENTIFIER.match(s) if not m: raise ValueError('Not a valid Python identifier: %r' % s) return True # # This function is defined in logging only in recent versions of Python # try: from logging import _checkLevel except ImportError: def _checkLevel(level): if isinstance(level, int): rv = level elif str(level) == level: if level not in logging._levelNames: raise ValueError('Unknown level: %r' % level) rv = logging._levelNames[level] else: raise TypeError('Level not an integer or a ' 'valid string: %r' % level) return rv # The ConvertingXXX classes are wrappers around standard Python containers, # and they serve to convert any suitable values in the container. The # conversion converts base dicts, lists and tuples to their wrapped # equivalents, whereas strings which match a conversion format are converted # appropriately. # # Each wrapper should have a configurator attribute holding the actual # configurator to use for conversion. class ConvertingDict(dict): """A converting dictionary wrapper.""" def __getitem__(self, key): value = dict.__getitem__(self, key) result = self.configurator.convert(value) #If the converted value is different, save for next time if value is not result: self[key] = result if type(result) in (ConvertingDict, ConvertingList, ConvertingTuple): result.parent = self result.key = key return result def get(self, key, default=None): value = dict.get(self, key, default) result = self.configurator.convert(value) #If the converted value is different, save for next time if value is not result: self[key] = result if type(result) in (ConvertingDict, ConvertingList, ConvertingTuple): result.parent = self result.key = key return result def pop(self, key, default=None): value = dict.pop(self, key, default) result = self.configurator.convert(value) if value is not result: if type(result) in (ConvertingDict, ConvertingList, ConvertingTuple): result.parent = self result.key = key return result class ConvertingList(list): """A converting list wrapper.""" def __getitem__(self, key): value = list.__getitem__(self, key) result = self.configurator.convert(value) #If the converted value is different, save for next time if value is not result: self[key] = result if type(result) in (ConvertingDict, ConvertingList, ConvertingTuple): result.parent = self result.key = key return result def pop(self, idx=-1): value = list.pop(self, idx) result = self.configurator.convert(value) if value is not result: if type(result) in (ConvertingDict, ConvertingList, ConvertingTuple): result.parent = self return result class ConvertingTuple(tuple): """A converting tuple wrapper.""" def __getitem__(self, key): value = tuple.__getitem__(self, key) result = self.configurator.convert(value) if value is not result: if type(result) in (ConvertingDict, ConvertingList, ConvertingTuple): result.parent = self result.key = key return result class BaseConfigurator(object): """ The configurator base class which defines some useful defaults. """ CONVERT_PATTERN = re.compile(r'^(?P[a-z]+)://(?P.*)$') WORD_PATTERN = re.compile(r'^\s*(\w+)\s*') DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*') INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*') DIGIT_PATTERN = re.compile(r'^\d+$') value_converters = { 'ext' : 'ext_convert', 'cfg' : 'cfg_convert', } # We might want to use a different one, e.g. importlib importer = __import__ "Allows the importer to be redefined." def __init__(self, config): """ Initialise an instance with the specified configuration dictionary. """ self.config = ConvertingDict(config) self.config.configurator = self def resolve(self, s): """ Resolve strings to objects using standard import and attribute syntax. """ name = s.split('.') used = name.pop(0) try: found = self.importer(used) for frag in name: used += '.' + frag try: found = getattr(found, frag) except AttributeError: self.importer(used) found = getattr(found, frag) return found except ImportError: e, tb = sys.exc_info()[1:] v = ValueError('Cannot resolve %r: %s' % (s, e)) v.__cause__, v.__traceback__ = e, tb raise v def ext_convert(self, value): """Default converter for the ext:// protocol.""" return self.resolve(value) def cfg_convert(self, value): """Default converter for the cfg:// protocol.""" rest = value m = self.WORD_PATTERN.match(rest) if m is None: raise ValueError("Unable to convert %r" % value) else: rest = rest[m.end():] d = self.config[m.groups()[0]] #print d, rest while rest: m = self.DOT_PATTERN.match(rest) if m: d = d[m.groups()[0]] else: m = self.INDEX_PATTERN.match(rest) if m: idx = m.groups()[0] if not self.DIGIT_PATTERN.match(idx): d = d[idx] else: try: n = int(idx) # try as number first (most likely) d = d[n] except TypeError: d = d[idx] if m: rest = rest[m.end():] else: raise ValueError('Unable to convert ' '%r at %r' % (value, rest)) #rest should be empty return d def convert(self, value): """ Convert values to an appropriate type. dicts, lists and tuples are replaced by their converting alternatives. Strings are checked to see if they have a conversion format and are converted if they do. """ if not isinstance(value, ConvertingDict) and isinstance(value, dict): value = ConvertingDict(value) value.configurator = self elif not isinstance(value, ConvertingList) and isinstance(value, list): value = ConvertingList(value) value.configurator = self elif not isinstance(value, ConvertingTuple) and\ isinstance(value, tuple): value = ConvertingTuple(value) value.configurator = self elif isinstance(value, basestring): m = self.CONVERT_PATTERN.match(value) if m: d = m.groupdict() prefix = d['prefix'] converter = self.value_converters.get(prefix, None) if converter: suffix = d['suffix'] converter = getattr(self, converter) value = converter(suffix) return value def configure_custom(self, config): """Configure an object with a user-supplied factory.""" c = config.pop('()') if isinstance(c, basestring): c = self.resolve(c) props = config.pop('.', None) # Check for valid identifiers kwargs = dict([(k, config[k]) for k in config if valid_ident(k)]) result = c(**kwargs) if props: for name, value in props.items(): setattr(result, name, value) return result def as_tuple(self, value): """Utility function which converts lists to tuples.""" if isinstance(value, list): value = tuple(value) return value def named_handlers_supported(): major, minor = sys.version_info[:2] if major == 2: result = minor >= 7 elif major == 3: result = minor >= 2 else: result = (major > 3) return result class DictConfigurator(BaseConfigurator): """ Configure logging using a dictionary-like object to describe the configuration. """ def configure(self): """Do the configuration.""" config = self.config if 'version' not in config: raise ValueError("dictionary doesn't specify a version") if config['version'] != 1: raise ValueError("Unsupported version: %s" % config['version']) incremental = config.pop('incremental', False) EMPTY_DICT = {} logging._acquireLock() try: if incremental: handlers = config.get('handlers', EMPTY_DICT) # incremental handler config only if handler name # ties in to logging._handlers (Python 2.7, 3.2+) if named_handlers_supported(): for name in handlers: if name not in logging._handlers: raise ValueError('No handler found with ' 'name %r' % name) else: try: handler = logging._handlers[name] handler_config = handlers[name] level = handler_config.get('level', None) if level: handler.setLevel(_checkLevel(level)) except StandardError: e = sys.exc_info()[1] raise ValueError('Unable to configure handler ' '%r: %s' % (name, e)) loggers = config.get('loggers', EMPTY_DICT) for name in loggers: try: self.configure_logger(name, loggers[name], True) except StandardError: e = sys.exc_info()[1] raise ValueError('Unable to configure logger ' '%r: %s' % (name, e)) root = config.get('root', None) if root: try: self.configure_root(root, True) except StandardError: e = sys.exc_info()[1] raise ValueError('Unable to configure root ' 'logger: %s' % e) else: disable_existing = config.pop('disable_existing_loggers', True) logging._handlers.clear() del logging._handlerList[:] # Do formatters first - they don't refer to anything else formatters = config.get('formatters', EMPTY_DICT) for name in formatters: try: formatters[name] = self.configure_formatter( formatters[name]) except StandardError: e = sys.exc_info()[1] raise ValueError('Unable to configure ' 'formatter %r: %s' % (name, e)) # Next, do filters - they don't refer to anything else, either filters = config.get('filters', EMPTY_DICT) for name in filters: try: filters[name] = self.configure_filter(filters[name]) except StandardError: e = sys.exc_info()[1] raise ValueError('Unable to configure ' 'filter %r: %s' % (name, e)) # Next, do handlers - they refer to formatters and filters # As handlers can refer to other handlers, sort the keys # to allow a deterministic order of configuration handlers = config.get('handlers', EMPTY_DICT) for name in sorted(handlers): try: handler = self.configure_handler(handlers[name]) handler.name = name handlers[name] = handler except StandardError: e = sys.exc_info()[1] raise ValueError('Unable to configure handler ' '%r: %s' % (name, e)) # Next, do loggers - they refer to handlers and filters #we don't want to lose the existing loggers, #since other threads may have pointers to them. #existing is set to contain all existing loggers, #and as we go through the new configuration we #remove any which are configured. At the end, #what's left in existing is the set of loggers #which were in the previous configuration but #which are not in the new configuration. root = logging.root existing = sorted(root.manager.loggerDict.keys()) #The list needs to be sorted so that we can #avoid disabling child loggers of explicitly #named loggers. With a sorted list it is easier #to find the child loggers. #We'll keep the list of existing loggers #which are children of named loggers here... child_loggers = [] #now set up the new ones... loggers = config.get('loggers', EMPTY_DICT) for name in loggers: if name in existing: i = existing.index(name) prefixed = name + "." pflen = len(prefixed) num_existing = len(existing) i = i + 1 # look at the entry after name while (i < num_existing) and\ (existing[i][:pflen] == prefixed): child_loggers.append(existing[i]) i = i + 1 existing.remove(name) try: self.configure_logger(name, loggers[name]) except StandardError: e = sys.exc_info()[1] raise ValueError('Unable to configure logger ' '%r: %s' % (name, e)) #Disable any old loggers. There's no point deleting #them as other threads may continue to hold references #and by disabling them, you stop them doing any logging. #However, don't disable children of named loggers, as that's #probably not what was intended by the user. for log in existing: logger = root.manager.loggerDict[log] if log in child_loggers: logger.level = logging.NOTSET logger.handlers = [] logger.propagate = True elif disable_existing: logger.disabled = True # And finally, do the root logger root = config.get('root', None) if root: try: self.configure_root(root) except StandardError: e = sys.exc_info()[1] raise ValueError('Unable to configure root ' 'logger: %s' % e) finally: logging._releaseLock() def configure_formatter(self, config): """Configure a formatter from a dictionary.""" if '()' in config: factory = config['()'] # for use in exception handler try: result = self.configure_custom(config) except TypeError: te = sys.exc_info()[1] if "'format'" not in str(te): raise #Name of parameter changed from fmt to format. #Retry with old name. #This is so that code can be used with older Python versions #(e.g. by Django) config['fmt'] = config.pop('format') config['()'] = factory result = self.configure_custom(config) else: fmt = config.get('format', None) dfmt = config.get('datefmt', None) result = logging.Formatter(fmt, dfmt) return result def configure_filter(self, config): """Configure a filter from a dictionary.""" if '()' in config: result = self.configure_custom(config) else: name = config.get('name', '') result = logging.Filter(name) return result def add_filters(self, filterer, filters): """Add filters to a filterer from a list of names.""" for f in filters: try: filterer.addFilter(self.config['filters'][f]) except StandardError: e = sys.exc_info()[1] raise ValueError('Unable to add filter %r: %s' % (f, e)) def configure_handler(self, config): """Configure a handler from a dictionary.""" formatter = config.pop('formatter', None) if formatter: try: formatter = self.config['formatters'][formatter] except StandardError: e = sys.exc_info()[1] raise ValueError('Unable to set formatter ' '%r: %s' % (formatter, e)) level = config.pop('level', None) filters = config.pop('filters', None) if '()' in config: c = config.pop('()') if isinstance(c, basestring): c = self.resolve(c) factory = c else: klass = self.resolve(config.pop('class')) #Special case for handler which refers to another handler if issubclass(klass, logging.handlers.MemoryHandler) and\ 'target' in config: try: config['target'] = self.config['handlers'][config['target']] except StandardError: e = sys.exc_info()[1] raise ValueError('Unable to set target handler ' '%r: %s' % (config['target'], e)) elif issubclass(klass, logging.handlers.SMTPHandler) and\ 'mailhost' in config: config['mailhost'] = self.as_tuple(config['mailhost']) elif issubclass(klass, logging.handlers.SysLogHandler) and\ 'address' in config: config['address'] = self.as_tuple(config['address']) factory = klass kwargs = dict([(k, config[k]) for k in config if valid_ident(k)]) try: result = factory(**kwargs) except TypeError: te = sys.exc_info()[1] if "'stream'" not in str(te): raise #The argument name changed from strm to stream #Retry with old name. #This is so that code can be used with older Python versions #(e.g. by Django) kwargs['strm'] = kwargs.pop('stream') result = factory(**kwargs) if formatter: result.setFormatter(formatter) if level is not None: result.setLevel(_checkLevel(level)) if filters: self.add_filters(result, filters) return result def add_handlers(self, logger, handlers): """Add handlers to a logger from a list of names.""" for h in handlers: try: logger.addHandler(self.config['handlers'][h]) except StandardError: e = sys.exc_info()[1] raise ValueError('Unable to add handler %r: %s' % (h, e)) def common_logger_config(self, logger, config, incremental=False): """ Perform configuration which is common to root and non-root loggers. """ level = config.get('level', None) if level is not None: logger.setLevel(_checkLevel(level)) if not incremental: #Remove any existing handlers for h in logger.handlers[:]: logger.removeHandler(h) handlers = config.get('handlers', None) if handlers: self.add_handlers(logger, handlers) filters = config.get('filters', None) if filters: self.add_filters(logger, filters) def configure_logger(self, name, config, incremental=False): """Configure a non-root logger from a dictionary.""" logger = logging.getLogger(name) self.common_logger_config(logger, config, incremental) propagate = config.get('propagate', None) if propagate is not None: logger.propagate = propagate def configure_root(self, config, incremental=False): """Configure a root logger from a dictionary.""" root = logging.getLogger() self.common_logger_config(root, config, incremental) dictConfigClass = DictConfigurator def dictConfig(config): """Configure logging using a dictionary.""" dictConfigClass(config).configure() logutils-0.3.3/logutils/colorize.py0000644000175000017500000001507012100225663016457 0ustar vinayvinay# # Copyright (C) 2010-2013 Vinay Sajip. All rights reserved. # import ctypes import logging import os try: unicode except NameError: unicode = None class ColorizingStreamHandler(logging.StreamHandler): """ A stream handler which supports colorizing of console streams under Windows, Linux and Mac OS X. :param strm: The stream to colorize - typically ``sys.stdout`` or ``sys.stderr``. """ # color names to indices color_map = { 'black': 0, 'red': 1, 'green': 2, 'yellow': 3, 'blue': 4, 'magenta': 5, 'cyan': 6, 'white': 7, } #levels to (background, foreground, bold/intense) if os.name == 'nt': level_map = { logging.DEBUG: (None, 'blue', True), logging.INFO: (None, 'white', False), logging.WARNING: (None, 'yellow', True), logging.ERROR: (None, 'red', True), logging.CRITICAL: ('red', 'white', True), } else: "Maps levels to colour/intensity settings." level_map = { logging.DEBUG: (None, 'blue', False), logging.INFO: (None, 'black', False), logging.WARNING: (None, 'yellow', False), logging.ERROR: (None, 'red', False), logging.CRITICAL: ('red', 'white', True), } csi = '\x1b[' reset = '\x1b[0m' @property def is_tty(self): "Returns true if the handler's stream is a terminal." isatty = getattr(self.stream, 'isatty', None) return isatty and isatty() def emit(self, record): try: message = self.format(record) stream = self.stream if unicode and isinstance(message, unicode): enc = getattr(stream, 'encoding', 'utf-8') message = message.encode(enc, 'replace') if not self.is_tty: stream.write(message) else: self.output_colorized(message) stream.write(getattr(self, 'terminator', '\n')) self.flush() except (KeyboardInterrupt, SystemExit): raise except: self.handleError(record) if os.name != 'nt': def output_colorized(self, message): """ Output a colorized message. On Linux and Mac OS X, this method just writes the already-colorized message to the stream, since on these platforms console streams accept ANSI escape sequences for colorization. On Windows, this handler implements a subset of ANSI escape sequence handling by parsing the message, extracting the sequences and making Win32 API calls to colorize the output. :param message: The message to colorize and output. """ self.stream.write(message) else: import re ansi_esc = re.compile(r'\x1b\[((?:\d+)(?:;(?:\d+))*)m') nt_color_map = { 0: 0x00, # black 1: 0x04, # red 2: 0x02, # green 3: 0x06, # yellow 4: 0x01, # blue 5: 0x05, # magenta 6: 0x03, # cyan 7: 0x07, # white } def output_colorized(self, message): """ Output a colorized message. On Linux and Mac OS X, this method just writes the already-colorized message to the stream, since on these platforms console streams accept ANSI escape sequences for colorization. On Windows, this handler implements a subset of ANSI escape sequence handling by parsing the message, extracting the sequences and making Win32 API calls to colorize the output. :param message: The message to colorize and output. """ parts = self.ansi_esc.split(message) write = self.stream.write h = None fd = getattr(self.stream, 'fileno', None) if fd is not None: fd = fd() if fd in (1, 2): # stdout or stderr h = ctypes.windll.kernel32.GetStdHandle(-10 - fd) while parts: text = parts.pop(0) if text: write(text) if parts: params = parts.pop(0) if h is not None: params = [int(p) for p in params.split(';')] color = 0 for p in params: if 40 <= p <= 47: color |= self.nt_color_map[p - 40] << 4 elif 30 <= p <= 37: color |= self.nt_color_map[p - 30] elif p == 1: color |= 0x08 # foreground intensity on elif p == 0: # reset to default color color = 0x07 else: pass # error condition ignored ctypes.windll.kernel32.SetConsoleTextAttribute(h, color) def colorize(self, message, record): """ Colorize a message for a logging event. This implementation uses the ``level_map`` class attribute to map the LogRecord's level to a colour/intensity setting, which is then applied to the whole message. :param message: The message to colorize. :param record: The ``LogRecord`` for the message. """ if record.levelno in self.level_map: bg, fg, bold = self.level_map[record.levelno] params = [] if bg in self.color_map: params.append(str(self.color_map[bg] + 40)) if fg in self.color_map: params.append(str(self.color_map[fg] + 30)) if bold: params.append('1') if params: message = ''.join((self.csi, ';'.join(params), 'm', message, self.reset)) return message def format(self, record): """ Formats a record for output. This implementation colorizes the message line, but leaves any traceback unolorized. """ message = logging.StreamHandler.format(self, record) if self.is_tty: # Don't colorize any traceback parts = message.split('\n', 1) parts[0] = self.colorize(parts[0], record) message = '\n'.join(parts) return message logutils-0.3.3/logutils/__init__.py0000644000175000017500000001410512100227007016360 0ustar vinayvinay# # Copyright (C) 2010-2013 Vinay Sajip. See LICENSE.txt for details. # """ The logutils package provides a set of handlers for the Python standard library's logging package. Some of these handlers are out-of-scope for the standard library, and so they are packaged here. Others are updated versions which have appeared in recent Python releases, but are usable with older versions of Python, and so are packaged here. """ import logging from string import Template __version__ = '0.3.3' class NullHandler(logging.Handler): """ This handler does nothing. It's intended to be used to avoid the "No handlers could be found for logger XXX" one-off warning. This is important for library code, which may contain code to log events. If a user of the library does not configure logging, the one-off warning might be produced; to avoid this, the library developer simply needs to instantiate a NullHandler and add it to the top-level logger of the library module or package. """ def handle(self, record): """ Handle a record. Does nothing in this class, but in other handlers it typically filters and then emits the record in a thread-safe way. """ pass def emit(self, record): """ Emit a record. This does nothing and shouldn't be called during normal processing, unless you redefine :meth:`~logutils.NullHandler.handle`. """ pass def createLock(self): """ Since this handler does nothing, it has no underlying I/O to protect against multi-threaded access, so this method returns `None`. """ self.lock = None class PercentStyle(object): default_format = '%(message)s' asctime_format = '%(asctime)s' def __init__(self, fmt): self._fmt = fmt or self.default_format def usesTime(self): return self._fmt.find(self.asctime_format) >= 0 def format(self, record): return self._fmt % record.__dict__ class StrFormatStyle(PercentStyle): default_format = '{message}' asctime_format = '{asctime}' def format(self, record): return self._fmt.format(**record.__dict__) class StringTemplateStyle(PercentStyle): default_format = '${message}' asctime_format = '${asctime}' def __init__(self, fmt): self._fmt = fmt or self.default_format self._tpl = Template(self._fmt) def usesTime(self): fmt = self._fmt return fmt.find('$asctime') >= 0 or fmt.find(self.asctime_format) >= 0 def format(self, record): return self._tpl.substitute(**record.__dict__) _STYLES = { '%': PercentStyle, '{': StrFormatStyle, '$': StringTemplateStyle } class Formatter(logging.Formatter): """ Subclasses Formatter in Pythons earlier than 3.2 in order to give 3.2 Formatter behaviour with respect to allowing %-, {} or $- formatting. """ def __init__(self, fmt=None, datefmt=None, style='%'): """ Initialize the formatter with specified format strings. Initialize the formatter either with the specified format string, or a default as described above. Allow for specialized date formatting with the optional datefmt argument (if omitted, you get the ISO8601 format). Use a style parameter of '%', '{' or '$' to specify that you want to use one of %-formatting, :meth:`str.format` (``{}``) formatting or :class:`string.Template` formatting in your format string. """ if style not in _STYLES: raise ValueError('Style must be one of: %s' % ','.join( _STYLES.keys())) self._style = _STYLES[style](fmt) self._fmt = self._style._fmt self.datefmt = datefmt def usesTime(self): """ Check if the format uses the creation time of the record. """ return self._style.usesTime() def formatMessage(self, record): return self._style.format(record) def format(self, record): """ Format the specified record as text. The record's attribute dictionary is used as the operand to a string formatting operation which yields the returned string. Before formatting the dictionary, a couple of preparatory steps are carried out. The message attribute of the record is computed using LogRecord.getMessage(). If the formatting string uses the time (as determined by a call to usesTime(), formatTime() is called to format the event time. If there is exception information, it is formatted using formatException() and appended to the message. """ record.message = record.getMessage() if self.usesTime(): record.asctime = self.formatTime(record, self.datefmt) s = self.formatMessage(record) if record.exc_info: # Cache the traceback text to avoid converting it multiple times # (it's constant anyway) if not record.exc_text: record.exc_text = self.formatException(record.exc_info) if record.exc_text: if s[-1:] != "\n": s = s + "\n" s = s + record.exc_text return s class BraceMessage(object): def __init__(self, fmt, *args, **kwargs): self.fmt = fmt self.args = args self.kwargs = kwargs self.str = None def __str__(self): if self.str is None: self.str = self.fmt.format(*self.args, **self.kwargs) return self.str class DollarMessage(object): def __init__(self, fmt, **kwargs): self.fmt = fmt self.kwargs = kwargs self.str = None def __str__(self): if self.str is None: self.str = Template(self.fmt).substitute(**self.kwargs) return self.str def hasHandlers(logger): """ See if a logger has any handlers. """ rv = False while logger: if logger.handlers: rv = True break elif not logger.propagate: break else: logger = logger.parent return rv logutils-0.3.3/logutils/http.py0000644000175000017500000000634512100227030015603 0ustar vinayvinay# # Copyright (C) 2010-2013 Vinay Sajip. See LICENSE.txt for details. # import logging class HTTPHandler(logging.Handler): """ A class which sends records to a Web server, using either GET or POST semantics. :param host: The Web server to connect to. :param url: The URL to use for the connection. :param method: The HTTP method to use. GET and POST are supported. :param secure: set to True if HTTPS is to be used. :param credentials: Set to a username/password tuple if desired. If set, a Basic authentication header is sent. WARNING: if using credentials, make sure `secure` is `True` to avoid sending usernames and passwords in cleartext over the wire. """ def __init__(self, host, url, method="GET", secure=False, credentials=None): """ Initialize an instance. """ logging.Handler.__init__(self) method = method.upper() if method not in ["GET", "POST"]: raise ValueError("method must be GET or POST") self.host = host self.url = url self.method = method self.secure = secure self.credentials = credentials def mapLogRecord(self, record): """ Default implementation of mapping the log record into a dict that is sent as the CGI data. Overwrite in your class. Contributed by Franz Glasner. :param record: The record to be mapped. """ return record.__dict__ def emit(self, record): """ Emit a record. Send the record to the Web server as a percent-encoded dictionary :param record: The record to be emitted. """ try: import http.client, urllib.parse host = self.host if self.secure: h = http.client.HTTPSConnection(host) else: h = http.client.HTTPConnection(host) url = self.url data = urllib.parse.urlencode(self.mapLogRecord(record)) if self.method == "GET": if (url.find('?') >= 0): sep = '&' else: sep = '?' url = url + "%c%s" % (sep, data) h.putrequest(self.method, url) # support multiple hosts on one IP address... # need to strip optional :port from host, if present i = host.find(":") if i >= 0: host = host[:i] h.putheader("Host", host) if self.method == "POST": h.putheader("Content-type", "application/x-www-form-urlencoded") h.putheader("Content-length", str(len(data))) if self.credentials: import base64 s = ('u%s:%s' % self.credentials).encode('utf-8') s = 'Basic ' + base64.b64encode(s).strip() h.putheader('Authorization', s) h.endheaders(data if self.method == "POST" else None) h.getresponse() #can't do anything with the result except (KeyboardInterrupt, SystemExit): raise except: self.handleError(record)