logging_tree-1.4/ 0000750 0001750 0001750 00000000000 12263312000 015073 5 ustar brandon brandon 0000000 0000000 logging_tree-1.4/COPYRIGHT 0000640 0001750 0001750 00000002431 12263311137 016401 0 ustar brandon brandon 0000000 0000000 Copyright © 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-INFO 0000640 0001750 0001750 00000012556 12263312000 016202 0 ustar brandon brandon 0000000 0000000 Metadata-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/ 0000750 0001750 0001750 00000000000 12263312000 017540 5 ustar brandon brandon 0000000 0000000 logging_tree-1.4/logging_tree/format.py 0000640 0001750 0001750 00000011241 12263311137 021414 0 ustar brandon brandon 0000000 0000000 """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/ 0000750 0001750 0001750 00000000000 12263312000 020702 5 ustar brandon brandon 0000000 0000000 logging_tree-1.4/logging_tree/tests/__init__.py 0000640 0001750 0001750 00000000000 12263311137 023014 0 ustar brandon brandon 0000000 0000000 logging_tree-1.4/logging_tree/tests/test_format.py 0000640 0001750 0001750 00000013117 12263311137 023621 0 ustar brandon brandon 0000000 0000000 """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.py 0000640 0001750 0001750 00000000702 12263311137 022201 0 ustar brandon brandon 0000000 0000000 """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.py 0000640 0001750 0001750 00000002600 12263311137 023251 0 ustar brandon brandon 0000000 0000000 """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__.py 0000640 0001750 0001750 00000007567 12263311613 021702 0 ustar brandon brandon 0000000 0000000 """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.py 0000640 0001750 0001750 00000001427 12263311137 021241 0 ustar brandon brandon 0000000 0000000 """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.py 0000640 0001750 0001750 00000001737 12263311137 016630 0 ustar brandon brandon 0000000 0000000 from 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'],
)