~ubuntu-branches/ubuntu/utopic/python-logutils/utopic

« back to all changes in this revision

Viewing changes to logutils/colorize.py

  • Committer: Package Import Robot
  • Author(s): Thomas Goirand
  • Date: 2014-01-09 14:31:16 UTC
  • Revision ID: package-import@ubuntu.com-20140109143116-khh927gczb4ewojh
Tags: upstream-0.3.3
ImportĀ upstreamĀ versionĀ 0.3.3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# Copyright (C) 2010-2013 Vinay Sajip. All rights reserved.
 
3
#
 
4
import ctypes
 
5
import logging
 
6
import os
 
7
 
 
8
try:
 
9
    unicode
 
10
except NameError:
 
11
    unicode = None
 
12
 
 
13
class ColorizingStreamHandler(logging.StreamHandler):
 
14
    """
 
15
    A stream handler which supports colorizing of console streams
 
16
    under Windows, Linux and Mac OS X.
 
17
 
 
18
    :param strm: The stream to colorize - typically ``sys.stdout``
 
19
                 or ``sys.stderr``.
 
20
    """
 
21
    
 
22
    # color names to indices
 
23
    color_map = {
 
24
        'black': 0,
 
25
        'red': 1,
 
26
        'green': 2,
 
27
        'yellow': 3,
 
28
        'blue': 4,
 
29
        'magenta': 5,
 
30
        'cyan': 6,
 
31
        'white': 7,
 
32
    }
 
33
 
 
34
    #levels to (background, foreground, bold/intense)
 
35
    if os.name == 'nt':
 
36
        level_map = {
 
37
            logging.DEBUG: (None, 'blue', True),
 
38
            logging.INFO: (None, 'white', False),
 
39
            logging.WARNING: (None, 'yellow', True),
 
40
            logging.ERROR: (None, 'red', True),
 
41
            logging.CRITICAL: ('red', 'white', True),
 
42
        }
 
43
    else:
 
44
        "Maps levels to colour/intensity settings."
 
45
        level_map = {
 
46
            logging.DEBUG: (None, 'blue', False),
 
47
            logging.INFO: (None, 'black', False),
 
48
            logging.WARNING: (None, 'yellow', False),
 
49
            logging.ERROR: (None, 'red', False),
 
50
            logging.CRITICAL: ('red', 'white', True),
 
51
        }
 
52
 
 
53
    csi = '\x1b['
 
54
    reset = '\x1b[0m'
 
55
 
 
56
    @property
 
57
    def is_tty(self):
 
58
        "Returns true if the handler's stream is a terminal."
 
59
        isatty = getattr(self.stream, 'isatty', None)
 
60
        return isatty and isatty()
 
61
 
 
62
    def emit(self, record):
 
63
        try:
 
64
            message = self.format(record)
 
65
            stream = self.stream
 
66
            if unicode and isinstance(message, unicode):
 
67
                enc = getattr(stream, 'encoding', 'utf-8')
 
68
                message = message.encode(enc, 'replace')
 
69
            if not self.is_tty:
 
70
                stream.write(message)
 
71
            else:
 
72
                self.output_colorized(message)
 
73
            stream.write(getattr(self, 'terminator', '\n'))
 
74
            self.flush()
 
75
        except (KeyboardInterrupt, SystemExit):
 
76
            raise
 
77
        except:
 
78
            self.handleError(record)
 
79
 
 
80
    if os.name != 'nt':
 
81
        def output_colorized(self, message):
 
82
            """
 
83
            Output a colorized message.
 
84
 
 
85
            On Linux and Mac OS X, this method just writes the
 
86
            already-colorized message to the stream, since on these
 
87
            platforms console streams accept ANSI escape sequences
 
88
            for colorization. On Windows, this handler implements a
 
89
            subset of ANSI escape sequence handling by parsing the
 
90
            message, extracting the sequences and making Win32 API
 
91
            calls to colorize the output.
 
92
 
 
93
            :param message: The message to colorize and output.
 
94
            """
 
95
            self.stream.write(message)
 
96
    else:
 
97
        import re
 
98
        ansi_esc = re.compile(r'\x1b\[((?:\d+)(?:;(?:\d+))*)m')
 
99
 
 
100
        nt_color_map = {
 
101
            0: 0x00,    # black
 
102
            1: 0x04,    # red
 
103
            2: 0x02,    # green
 
104
            3: 0x06,    # yellow
 
105
            4: 0x01,    # blue
 
106
            5: 0x05,    # magenta
 
107
            6: 0x03,    # cyan
 
108
            7: 0x07,    # white
 
109
        }
 
110
 
 
111
        def output_colorized(self, message):
 
112
            """
 
113
            Output a colorized message.
 
114
 
 
115
            On Linux and Mac OS X, this method just writes the
 
116
            already-colorized message to the stream, since on these
 
117
            platforms console streams accept ANSI escape sequences
 
118
            for colorization. On Windows, this handler implements a
 
119
            subset of ANSI escape sequence handling by parsing the
 
120
            message, extracting the sequences and making Win32 API
 
121
            calls to colorize the output.
 
122
 
 
123
            :param message: The message to colorize and output.
 
124
            """
 
125
            parts = self.ansi_esc.split(message)
 
126
            write = self.stream.write
 
127
            h = None
 
128
            fd = getattr(self.stream, 'fileno', None)
 
129
            if fd is not None:
 
130
                fd = fd()
 
131
                if fd in (1, 2): # stdout or stderr
 
132
                    h = ctypes.windll.kernel32.GetStdHandle(-10 - fd)
 
133
            while parts:
 
134
                text = parts.pop(0)
 
135
                if text:
 
136
                    write(text)
 
137
                if parts:
 
138
                    params = parts.pop(0)
 
139
                    if h is not None:
 
140
                        params = [int(p) for p in params.split(';')]
 
141
                        color = 0
 
142
                        for p in params:
 
143
                            if 40 <= p <= 47:
 
144
                                color |= self.nt_color_map[p - 40] << 4
 
145
                            elif 30 <= p <= 37:
 
146
                                color |= self.nt_color_map[p - 30]
 
147
                            elif p == 1:
 
148
                                color |= 0x08 # foreground intensity on
 
149
                            elif p == 0: # reset to default color
 
150
                                color = 0x07
 
151
                            else:
 
152
                                pass # error condition ignored
 
153
                        ctypes.windll.kernel32.SetConsoleTextAttribute(h, color)
 
154
 
 
155
    def colorize(self, message, record):
 
156
        """
 
157
        Colorize a message for a logging event.
 
158
 
 
159
        This implementation uses the ``level_map`` class attribute to
 
160
        map the LogRecord's level to a colour/intensity setting, which is
 
161
        then applied to the whole message.
 
162
 
 
163
        :param message: The message to colorize.
 
164
        :param record: The ``LogRecord`` for the message.
 
165
        """
 
166
        if record.levelno in self.level_map:
 
167
            bg, fg, bold = self.level_map[record.levelno]
 
168
            params = []
 
169
            if bg in self.color_map:
 
170
                params.append(str(self.color_map[bg] + 40))
 
171
            if fg in self.color_map:
 
172
                params.append(str(self.color_map[fg] + 30))
 
173
            if bold:
 
174
                params.append('1')
 
175
            if params:
 
176
                message = ''.join((self.csi, ';'.join(params),
 
177
                                   'm', message, self.reset))
 
178
        return message
 
179
 
 
180
    def format(self, record):
 
181
        """
 
182
        Formats a record for output.
 
183
 
 
184
        This implementation colorizes the message line, but leaves
 
185
        any traceback unolorized.
 
186
        """
 
187
        message = logging.StreamHandler.format(self, record)
 
188
        if self.is_tty:
 
189
            # Don't colorize any traceback
 
190
            parts = message.split('\n', 1)
 
191
            parts[0] = self.colorize(parts[0], record)
 
192
            message = '\n'.join(parts)
 
193
        return message
 
194