logging_tree-1.4/0000750000175000017500000000000012263312000015073 5ustar brandonbrandon00000000000000logging_tree-1.4/COPYRIGHT0000640000175000017500000000243112263311137016401 0ustar brandonbrandon00000000000000Copyright © 2012, Brandon Rhodes All rights reserved. (The BSD License) 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. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 OR CONTRIBUTORS 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. logging_tree-1.4/PKG-INFO0000640000175000017500000001255612263312000016202 0ustar brandonbrandon00000000000000Metadata-Version: 1.1 Name: logging_tree Version: 1.4 Summary: Introspect and display the logger tree inside "logging" Home-page: https://github.com/brandon-rhodes/logging_tree Author: Brandon Rhodes Author-email: brandon@rhodesmill.org License: UNKNOWN Description: Introspection for the ``logging`` logger tree in the Standard Library. While you can write programs that call this package's ``tree()`` function and examine the hierarchy of logger objects that it finds inside of the Standard Library ``logging`` module, the simplest use of this package for debugging is to call ``printout()`` to print the loggers, filters, and handlers that your application has configured:: >>> logging.getLogger('a') >>> logging.getLogger('a.b').setLevel(logging.DEBUG) >>> logging.getLogger('x.c') >>> from logging_tree import printout >>> printout() <--"" Level WARNING | o<--"a" | Level NOTSET so inherits level WARNING | | | o<--"a.b" | Level DEBUG | o<--[x] | o<--"x.c" Level NOTSET so inherits level WARNING The logger tree should always print successfully, no matter how complicated. A node whose ``[name]`` is in square brackets is a "place holder" that has never actually been named in a ``getLogger()`` call, but was created automatically to serve as the parent of loggers further down the tree. There are several interfaces that ``logging_tree`` supports, depending on how much detail you need. ``logging_tree.printout(node=None)`` Prints the current logger tree, or the tree based at the given `node`, to the standard output. ``logging_tree.format.build_description(node=None)`` Builds and returns the multi-line description of the current logger tree, or the tree based at the given ``node``, as a single string with newlines inside and a newline at the end. ``logging_tree.format.describe(node)`` A generator that yields a series of lines that describe the tree based at the given ``node``. Note that the lines are returned without newline terminators attached. ``logging_tree.tree()`` Fetch the current tree of loggers from the ``logging`` module. Returns a node, that is simply a tuple with three fields: | ``[0]`` the logger name (``""`` for the root logger). | ``[1]`` the ``logging.Logger`` object itself. | ``[2]`` a list of zero or more child nodes. I owe great thanks to `Rover Apps `_ for letting me release this general-purpose tool, whose core logic I developed while working on one of their projects. They care about the Python community! I welcome contributions and ideas as this package matures. You can find the bug tracker at the `repository page on github `_. Developers can run this package's tests with:: $ python -m unittest discover logging_tree On older versions of Python you will instead have to install ``unittest2`` and use its ``unit2`` command line tool to run the tests. Changelog --------- **Version 1.4** - 2014 January 8 Thanks to a contribution from Dave Brondsema, disabled loggers are now actually marked as "Disabled" to make it less of a surprise that they fail to log anything. **Version 1.3** - 2013 October 29 Be explicit and display the logger level ``NOTSET`` along with the effective level inherited from the logger's ancestors; and display the list of ``.filters`` of a custom logging handler even though it might contain custom code that ignores them. **Version 1.2** - 2013 January 19 Compatible with Python 3.3 thanks to @ralphbean. **Version 1.1** - 2012 February 17 Now compatible with 2.3 <= Python <= 3.2. **Version 1.0** - 2012 February 13 Can display the handler inside a MemoryHandler; entire public interface documented; 100% test coverage. **Version 0.6** - 2012 February 10 Added a display format for every ``logging.handlers`` class. **Version 0.5** - 2012 February 8 Initial release. Platform: UNKNOWN Classifier: Development Status :: 6 - Mature Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python :: 2.3 Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Topic :: System :: Logging logging_tree-1.4/logging_tree/0000750000175000017500000000000012263312000017540 5ustar brandonbrandon00000000000000logging_tree-1.4/logging_tree/format.py0000640000175000017500000001124112263311137021414 0ustar brandonbrandon00000000000000"""Routines that pretty-print a hierarchy of logging `Node` objects.""" import logging.handlers import sys if sys.version_info < (2, 6): next = lambda generator: generator.next() # supply a missing builtin def printout(node=None): """Print a tree of loggers, given a `Node` from `logging_tree.nodes`. If no `node` argument is provided, then the entire tree of currently active `logging` loggers is printed out. """ print(build_description(node)[:-1]) def build_description(node=None): """Return a multi-line string describing a `logging_tree.nodes.Node`. If no `node` argument is provided, then the entire tree of currently active `logging` loggers is printed out. """ if node is None: from logging_tree.nodes import tree node = tree() return '\n'.join([ line.rstrip() for line in describe(node) ]) + '\n' def describe(node): """Generate lines describing the given `node` tuple. The `node` should be a tuple returned by `logging_tree.nodes.tree()`. """ logger = node[1] is_placeholder = isinstance(logger, logging.PlaceHolder) if is_placeholder or logger.propagate: arrow = '<--' else: arrow = ' ' if is_placeholder: name = '[%s]' % node[0] else: name = '"%s"' % node[0] yield arrow + name if not is_placeholder: if logger.level == logging.NOTSET: yield ' Level NOTSET so inherits level ' + logging.getLevelName( logger.getEffectiveLevel()) else: yield ' Level ' + logging.getLevelName(logger.level) if not logger.propagate: yield ' Propagate OFF' if logger.disabled: yield ' Disabled' # In case someone has defined a custom logger that lacks a # `filters` or `handlers` attribute, we call getattr() and # provide an empty sequence as a fallback. for f in getattr(logger, 'filters', ()): yield ' Filter %s' % describe_filter(f) for h in getattr(logger, 'handlers', ()): g = describe_handler(h) yield ' Handler %s' % next(g) for line in g: yield ' ' + line children = node[2] if children: last_child = children[-1] for child in children: g = describe(child) yield ' |' yield ' o' + next(g) if child is last_child: prefix = ' ' else: prefix = ' |' for line in g: yield prefix + line # The functions below must avoid `isinstance()`, since a Filter or # Handler subclass might implement behavior that renders our tidy # description quite useless. def describe_filter(f): """Return text describing the logging filter `f`.""" if f.__class__ is logging.Filter: # using type() breaks in Python <= 2.6 return 'name=%r' % f.name return repr(f) handler_formats = { # Someday we will switch to .format() when Py2.6 is gone. logging.StreamHandler: 'Stream %(stream)r', logging.FileHandler: 'File %(baseFilename)r', logging.handlers.RotatingFileHandler: 'RotatingFile %(baseFilename)r' ' maxBytes=%(maxBytes)r backupCount=%(backupCount)r', logging.handlers.SocketHandler: 'Socket %(host)s %(port)r', logging.handlers.DatagramHandler: 'Datagram %(host)s %(port)r', logging.handlers.SysLogHandler: 'SysLog %(address)r facility=%(facility)r', logging.handlers.SMTPHandler: 'SMTP via %(mailhost)s to %(toaddrs)s', logging.handlers.HTTPHandler: 'HTTP %(method)s to http://%(host)s/%(url)s', logging.handlers.BufferingHandler: 'Buffering capacity=%(capacity)r', logging.handlers.MemoryHandler: 'Memory capacity=%(capacity)r dumping to:', } if sys.version_info >= (2, 5): handler_formats.update({ logging.handlers.TimedRotatingFileHandler: 'TimedRotatingFile %(baseFilename)r when=%(when)r' ' interval=%(interval)r backupCount=%(backupCount)r', }) if sys.version_info >= (2, 6): handler_formats.update({ logging.handlers.WatchedFileHandler: 'WatchedFile %(baseFilename)r', }) def describe_handler(h): """Yield one or more lines describing the logging handler `h`.""" t = h.__class__ # using type() breaks in Python <= 2.6 format = handler_formats.get(t) if format is not None: yield format % h.__dict__ else: yield repr(h) for f in getattr(h, 'filters', ()): yield ' Filter %s' % describe_filter(f) if t is logging.handlers.MemoryHandler and h.target is not None: g = describe_handler(h.target) yield ' Handler ' + next(g) for line in g: yield ' ' + line logging_tree-1.4/logging_tree/tests/0000750000175000017500000000000012263312000020702 5ustar brandonbrandon00000000000000logging_tree-1.4/logging_tree/tests/__init__.py0000640000175000017500000000000012263311137023014 0ustar brandonbrandon00000000000000logging_tree-1.4/logging_tree/tests/test_format.py0000640000175000017500000001311712263311137023621 0ustar brandonbrandon00000000000000"""Tests for the `logging_tree.format` module.""" import logging import logging.handlers import unittest import sys from logging_tree.format import build_description, printout from logging_tree.tests.case import LoggingTestCase if sys.version_info >= (3,): from io import StringIO else: from StringIO import StringIO class FakeFile(StringIO): def __init__(self, filename, mode, encoding=None): self.filename = filename StringIO.__init__(self) def __repr__(self): return '' % self.filename def fileno(self): return 0 class FormatTests(LoggingTestCase): def setUp(self): # Prevent logging file handlers from trying to open real files. # (The keyword delay=1, which defers any actual attempt to open # a file, did not appear until Python 2.6.) logging.open = FakeFile super(FormatTests, self).setUp() def tearDown(self): del logging.open super(FormatTests, self).tearDown() def test_printout(self): stdout, sys.stdout = sys.stdout, StringIO() printout() self.assertEqual(sys.stdout.getvalue(), '<--""\n Level WARNING\n') sys.stdout = stdout def test_simple_tree(self): logging.getLogger('a') logging.getLogger('a.b').setLevel(logging.DEBUG) logging.getLogger('x.c') self.assertEqual(build_description(), '''\ <--"" Level WARNING | o<--"a" | Level NOTSET so inherits level WARNING | | | o<--"a.b" | Level DEBUG | o<--[x] | o<--"x.c" Level NOTSET so inherits level WARNING ''') def test_fancy_tree(self): logging.getLogger('').setLevel(logging.DEBUG) log = logging.getLogger('db') log.setLevel(logging.INFO) log.propagate = False log.disabled = 1 log.addFilter(MyFilter()) handler = logging.StreamHandler() log.addHandler(handler) handler.addFilter(logging.Filter('db.errors')) logging.getLogger('db.errors') logging.getLogger('db.stats') log = logging.getLogger('www.status') log.setLevel(logging.DEBUG) log.addHandler(logging.FileHandler('/foo/log.txt')) log.addHandler(MyHandler()) self.assertEqual(build_description(), '''\ <--"" Level DEBUG | o "db" | Level INFO | Propagate OFF | Disabled | Filter | Handler Stream %r | Filter name='db.errors' | | | o<--"db.errors" | | Level NOTSET so inherits level INFO | | | o<--"db.stats" | Level NOTSET so inherits level INFO | o<--[www] | o<--"www.status" Level DEBUG Handler File '/foo/log.txt' Handler ''' % (sys.stderr,)) def test_most_handlers(self): ah = logging.getLogger('').addHandler ah(logging.handlers.RotatingFileHandler( '/bar/one.txt', maxBytes=10000, backupCount=3)) ah(logging.handlers.SocketHandler('server.example.com', 514)) ah(logging.handlers.DatagramHandler('server.example.com', 1958)) ah(logging.handlers.SysLogHandler()) ah(logging.handlers.SMTPHandler( 'mail.example.com', 'Server', 'Sysadmin', 'Logs!')) # ah(logging.handlers.NTEventLogHandler()) ah(logging.handlers.HTTPHandler('api.example.com', '/logs', 'POST')) ah(logging.handlers.BufferingHandler(20000)) sh = logging.StreamHandler() ah(logging.handlers.MemoryHandler(30000, target=sh)) self.assertEqual(build_description(), '''\ <--"" Level WARNING Handler RotatingFile '/bar/one.txt' maxBytes=10000 backupCount=3 Handler Socket server.example.com 514 Handler Datagram server.example.com 1958 Handler SysLog ('localhost', 514) facility=1 Handler SMTP via mail.example.com to ['Sysadmin'] Handler HTTP POST to http://api.example.com//logs Handler Buffering capacity=20000 Handler Memory capacity=30000 dumping to: Handler Stream %r ''' % (sh.stream,)) logging.getLogger('').handlers[3].socket.close() # or Python 3 warning def test_2_dot_5_handlers(self): if sys.version_info < (2, 5): return ah = logging.getLogger('').addHandler ah(logging.handlers.TimedRotatingFileHandler('/bar/two.txt')) self.assertEqual(build_description(), '''\ <--"" Level WARNING Handler TimedRotatingFile '/bar/two.txt' when='H' interval=3600 backupCount=0 ''') def test_2_dot_6_handlers(self): if sys.version_info < (2, 6): return ah = logging.getLogger('').addHandler ah(logging.handlers.WatchedFileHandler('/bar/three.txt')) self.assertEqual(build_description(), '''\ <--"" Level WARNING Handler WatchedFile '/bar/three.txt' ''') def test_nested_handlers(self): h1 = logging.StreamHandler() h2 = logging.handlers.MemoryHandler(30000, target=h1) h2.addFilter(logging.Filter('worse')) h3 = logging.handlers.MemoryHandler(30000, target=h2) h3.addFilter(logging.Filter('bad')) logging.getLogger('').addHandler(h3) self.assertEqual(build_description(), '''\ <--"" Level WARNING Handler Memory capacity=30000 dumping to: Filter name='bad' Handler Memory capacity=30000 dumping to: Filter name='worse' Handler Stream %r ''' % (h1.stream,)) class MyFilter(object): def __repr__(self): return '' class MyHandler(object): def __repr__(self): return '' if __name__ == '__main__': # for Python <= 2.4 unittest.main() logging_tree-1.4/logging_tree/tests/case.py0000640000175000017500000000070212263311137022201 0ustar brandonbrandon00000000000000"""Common test class for `logging` tests.""" import logging.handlers import unittest class LoggingTestCase(unittest.TestCase): """Test case that knows the secret: how to reset the logging module.""" def tearDown(self): super(LoggingTestCase, self).tearDown() logging.root = logging.RootLogger(logging.WARNING) logging.Logger.root = logging.root logging.Logger.manager = logging.Manager(logging.Logger.root) logging_tree-1.4/logging_tree/tests/test_node.py0000640000175000017500000000260012263311137023251 0ustar brandonbrandon00000000000000"""Tests for the `logging_tree.node` module.""" import logging.handlers import unittest from logging_tree.nodes import tree from logging_tree.tests.case import LoggingTestCase class AnyPlaceHolder(object): def __eq__(self, other): return isinstance(other, logging.PlaceHolder) any_placeholder = AnyPlaceHolder() class NodeTests(LoggingTestCase): def test_default_tree(self): self.assertEqual(tree(), ('', logging.root, [])) def test_one_level_tree(self): a = logging.getLogger('a') b = logging.getLogger('b') self.assertEqual(tree(), ( '', logging.root, [ ('a', a, []), ('b', b, []), ])) def test_two_level_tree(self): a = logging.getLogger('a') b = logging.getLogger('a.b') self.assertEqual(tree(), ( '', logging.root, [ ('a', a, [ ('a.b', b, []), ]), ])) def test_two_level_tree_with_placeholder(self): b = logging.getLogger('a.b') self.assertEqual(tree(), ( '', logging.root, [ ('a', any_placeholder, [ ('a.b', b, []), ]), ])) if __name__ == '__main__': # for Python <= 2.4 unittest.main() logging_tree-1.4/logging_tree/__init__.py0000640000175000017500000000756712263311613021702 0ustar brandonbrandon00000000000000"""Introspection for the ``logging`` logger tree in the Standard Library. While you can write programs that call this package's ``tree()`` function and examine the hierarchy of logger objects that it finds inside of the Standard Library ``logging`` module, the simplest use of this package for debugging is to call ``printout()`` to print the loggers, filters, and handlers that your application has configured:: >>> logging.getLogger('a') >>> logging.getLogger('a.b').setLevel(logging.DEBUG) >>> logging.getLogger('x.c') >>> from logging_tree import printout >>> printout() <--"" Level WARNING | o<--"a" | Level NOTSET so inherits level WARNING | | | o<--"a.b" | Level DEBUG | o<--[x] | o<--"x.c" Level NOTSET so inherits level WARNING The logger tree should always print successfully, no matter how complicated. A node whose ``[name]`` is in square brackets is a "place holder" that has never actually been named in a ``getLogger()`` call, but was created automatically to serve as the parent of loggers further down the tree. There are several interfaces that ``logging_tree`` supports, depending on how much detail you need. ``logging_tree.printout(node=None)`` Prints the current logger tree, or the tree based at the given `node`, to the standard output. ``logging_tree.format.build_description(node=None)`` Builds and returns the multi-line description of the current logger tree, or the tree based at the given ``node``, as a single string with newlines inside and a newline at the end. ``logging_tree.format.describe(node)`` A generator that yields a series of lines that describe the tree based at the given ``node``. Note that the lines are returned without newline terminators attached. ``logging_tree.tree()`` Fetch the current tree of loggers from the ``logging`` module. Returns a node, that is simply a tuple with three fields: | ``[0]`` the logger name (``""`` for the root logger). | ``[1]`` the ``logging.Logger`` object itself. | ``[2]`` a list of zero or more child nodes. I owe great thanks to `Rover Apps `_ for letting me release this general-purpose tool, whose core logic I developed while working on one of their projects. They care about the Python community! I welcome contributions and ideas as this package matures. You can find the bug tracker at the `repository page on github `_. Developers can run this package's tests with:: $ python -m unittest discover logging_tree On older versions of Python you will instead have to install ``unittest2`` and use its ``unit2`` command line tool to run the tests. Changelog --------- **Version 1.4** - 2014 January 8 Thanks to a contribution from Dave Brondsema, disabled loggers are now actually marked as "Disabled" to make it less of a surprise that they fail to log anything. **Version 1.3** - 2013 October 29 Be explicit and display the logger level ``NOTSET`` along with the effective level inherited from the logger's ancestors; and display the list of ``.filters`` of a custom logging handler even though it might contain custom code that ignores them. **Version 1.2** - 2013 January 19 Compatible with Python 3.3 thanks to @ralphbean. **Version 1.1** - 2012 February 17 Now compatible with 2.3 <= Python <= 3.2. **Version 1.0** - 2012 February 13 Can display the handler inside a MemoryHandler; entire public interface documented; 100% test coverage. **Version 0.6** - 2012 February 10 Added a display format for every ``logging.handlers`` class. **Version 0.5** - 2012 February 8 Initial release. """ __version__ = '1.4' __all__ = ('tree', 'printout') from logging_tree.nodes import tree from logging_tree.format import printout logging_tree-1.4/logging_tree/nodes.py0000640000175000017500000000142712263311137021241 0ustar brandonbrandon00000000000000"""Routine that explores the `logging` hierarchy and builds a `Node` tree.""" import logging def tree(): """Return a tree of tuples representing the logger layout. Each tuple looks like ``('logger-name', , [...])`` where the third element is a list of zero or more child tuples that share the same layout. """ root = ('', logging.root, []) nodes = {} items = list(logging.root.manager.loggerDict.items()) # for Python 2 and 3 items.sort() for name, logger in items: nodes[name] = node = (name, logger, []) i = name.rfind('.', 0, len(name) - 1) # same formula used in `logging` if i == -1: parent = root else: parent = nodes[name[:i]] parent[2].append(node) return root logging_tree-1.4/setup.py0000640000175000017500000000173712263311137016630 0ustar brandonbrandon00000000000000from distutils.core import setup import logging_tree setup(name='logging_tree', version=logging_tree.__version__, description='Introspect and display the logger tree inside "logging"', long_description=logging_tree.__doc__, author='Brandon Rhodes', author_email='brandon@rhodesmill.org', url='https://github.com/brandon-rhodes/logging_tree', classifiers=[ 'Development Status :: 6 - Mature', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python :: 2.3', 'Programming Language :: Python :: 2.4', 'Programming Language :: Python :: 2.5', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Topic :: System :: Logging', ], packages=['logging_tree', 'logging_tree.tests'], )