1. Vinay Sajip
  2. logutils

Commits

Vinay Sajip  committed 44d2a90

More changes for 0.3.

  • Participants
  • Parent commits 3565923
  • Branches default

Comments (0)

Files changed (10)

File doc/adapter.rst

View file
+Working 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:
+

File doc/colorize.rst

View file
+Colorizing 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:
+

File doc/dictconfig.rst

View file
+Dictionary-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
+

File doc/http.rst

View file
+Working 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:
+

File doc/libraries.rst

View file
+Configuring 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:

File doc/queue.rst

View file
+Working with queues
+===================
+
+.. automodule:: logutils.queue
+    :members:
+

File doc/testing.rst

View file
+Unit 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:

File logutils/colorize.py

View file
+#
+# Copyright (C) 2010-2011 Vinay Sajip. All rights reserved.
+#
+import ctypes
+import logging
+import os
+
+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 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
+

File tests/test_formatter.py

View file
+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())
+
+

File tests/test_messages.py

View file
+import 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')
+        
+