~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/services/log/logger.py

[r=sinzui][bug=855670] Add additional checks to the private team
        launchpad.LimitedView security adaptor so more users in defined
        roles can see the team.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2010-2011 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
"""Loggers."""
 
5
 
 
6
__metaclass__ = type
 
7
__all__ = [
 
8
    'BufferLogger',
 
9
    'DevNullLogger',
 
10
    'FakeLogger',
 
11
    'LaunchpadLogger',
 
12
    'NullHandler',
 
13
    'PrefixFilter',
 
14
    ]
 
15
 
 
16
import logging
 
17
from StringIO import StringIO
 
18
import sys
 
19
import traceback
 
20
 
 
21
from testtools.content import (
 
22
    Content,
 
23
    UTF8_TEXT,
 
24
    )
 
25
 
 
26
from lp.services.log import loglevels
 
27
 
 
28
 
 
29
LEVEL_PREFIXES = dict(
 
30
    (debug_level, "DEBUG%d" % (1 + debug_level - loglevels.DEBUG))
 
31
    for debug_level in xrange(loglevels.DEBUG9, loglevels.DEBUG))
 
32
 
 
33
LEVEL_PREFIXES.update({
 
34
    loglevels.DEBUG: 'DEBUG',
 
35
    loglevels.INFO: 'INFO',
 
36
    loglevels.WARNING: 'WARNING',
 
37
    loglevels.ERROR: 'ERROR',
 
38
    loglevels.CRITICAL: 'CRITICAL',
 
39
})
 
40
 
 
41
 
 
42
class LaunchpadLogger(logging.Logger):
 
43
    """Logger that support our custom levels."""
 
44
 
 
45
    def debug1(self, msg, *args, **kwargs):
 
46
        if self.isEnabledFor(loglevels.DEBUG1):
 
47
            self._log(loglevels.DEBUG1, msg, args, **kwargs)
 
48
 
 
49
    def debug2(self, msg, *args, **kwargs):
 
50
        if self.isEnabledFor(loglevels.DEBUG2):
 
51
            self._log(loglevels.DEBUG2, msg, args, **kwargs)
 
52
 
 
53
    def debug3(self, msg, *args, **kwargs):
 
54
        if self.isEnabledFor(loglevels.DEBUG3):
 
55
            self._log(loglevels.DEBUG3, msg, args, **kwargs)
 
56
 
 
57
    def debug4(self, msg, *args, **kwargs):
 
58
        if self.isEnabledFor(loglevels.DEBUG4):
 
59
            self._log(loglevels.DEBUG4, msg, args, **kwargs)
 
60
 
 
61
    def debug5(self, msg, *args, **kwargs):
 
62
        if self.isEnabledFor(loglevels.DEBUG5):
 
63
            self._log(loglevels.DEBUG5, msg, args, **kwargs)
 
64
 
 
65
    def debug6(self, msg, *args, **kwargs):
 
66
        if self.isEnabledFor(loglevels.DEBUG6):
 
67
            self._log(loglevels.DEBUG6, msg, args, **kwargs)
 
68
 
 
69
    def debug7(self, msg, *args, **kwargs):
 
70
        if self.isEnabledFor(loglevels.DEBUG7):
 
71
            self._log(loglevels.DEBUG7, msg, args, **kwargs)
 
72
 
 
73
    def debug8(self, msg, *args, **kwargs):
 
74
        if self.isEnabledFor(loglevels.DEBUG8):
 
75
            self._log(loglevels.DEBUG8, msg, args, **kwargs)
 
76
 
 
77
    def debug9(self, msg, *args, **kwargs):
 
78
        if self.isEnabledFor(loglevels.DEBUG9):
 
79
            self._log(loglevels.DEBUG9, msg, args, **kwargs)
 
80
 
 
81
 
 
82
class PrefixFilter:
 
83
    """A logging Filter that inserts a prefix into messages.
 
84
 
 
85
    If no static prefix is provided, the Logger's name is used.
 
86
    """
 
87
 
 
88
    def __init__(self, prefix=None):
 
89
        self.prefix = prefix
 
90
 
 
91
    def filter(self, record):
 
92
        prefix = self.prefix or record.name
 
93
        record.msg = '[%s] %s' % (prefix, record.msg)
 
94
        return True
 
95
 
 
96
 
 
97
class NullHandler(logging.Handler):
 
98
    """A do-nothing Handler used to silence 'No handlers for logger' warnings.
 
99
    """
 
100
 
 
101
    def emit(self, record):
 
102
        pass
 
103
 
 
104
 
 
105
class FakeLogger:
 
106
    """Emulates a proper logger, just printing everything out the given file.
 
107
    """
 
108
    # XXX: GavinPanella 2011-11-04 bug=886053: This is a test fixture not a
 
109
    # service.
 
110
 
 
111
    loglevel = loglevels.DEBUG
 
112
 
 
113
    def __init__(self, output_file=None):
 
114
        """The default output_file is sys.stdout."""
 
115
        self.output_file = output_file
 
116
 
 
117
    def setLevel(self, loglevel):
 
118
        self.loglevel = loglevel
 
119
 
 
120
    def getEffectiveLevel(self):
 
121
        return self.loglevel
 
122
 
 
123
    def _format_message(self, msg, *args):
 
124
        if not isinstance(msg, basestring):
 
125
            msg = str(msg)
 
126
        # To avoid type errors when the msg has % values and args is empty,
 
127
        # don't expand the string with empty args.
 
128
        if len(args) > 0:
 
129
            msg %= args
 
130
        return msg
 
131
 
 
132
    def message(self, level, msg, *stuff, **kw):
 
133
        if level < self.loglevel:
 
134
            return
 
135
 
 
136
        # We handle the default output file here because sys.stdout
 
137
        # might have been reassigned. Between now and when this object
 
138
        # was instantiated.
 
139
        if self.output_file is None:
 
140
            output_file = sys.stdout
 
141
        else:
 
142
            output_file = self.output_file
 
143
        prefix = LEVEL_PREFIXES.get(level, "%d>" % level)
 
144
        print >> output_file, prefix, self._format_message(msg, *stuff)
 
145
 
 
146
        if 'exc_info' in kw:
 
147
            traceback.print_exc(file=output_file)
 
148
 
 
149
    def log(self, level, *stuff, **kw):
 
150
        self.message(level, *stuff, **kw)
 
151
 
 
152
    def warning(self, *stuff, **kw):
 
153
        self.message(loglevels.WARNING, *stuff, **kw)
 
154
 
 
155
    warn = warning
 
156
 
 
157
    def error(self, *stuff, **kw):
 
158
        self.message(loglevels.ERROR, *stuff, **kw)
 
159
 
 
160
    exception = error
 
161
 
 
162
    def critical(self, *stuff, **kw):
 
163
        self.message(loglevels.CRITICAL, *stuff, **kw)
 
164
 
 
165
    fatal = critical
 
166
 
 
167
    def info(self, *stuff, **kw):
 
168
        self.message(loglevels.INFO, *stuff, **kw)
 
169
 
 
170
    def debug(self, *stuff, **kw):
 
171
        self.message(loglevels.DEBUG, *stuff, **kw)
 
172
 
 
173
    def debug2(self, *stuff, **kw):
 
174
        self.message(loglevels.DEBUG2, *stuff, **kw)
 
175
 
 
176
    def debug3(self, *stuff, **kw):
 
177
        self.message(loglevels.DEBUG3, *stuff, **kw)
 
178
 
 
179
    def debug4(self, *stuff, **kw):
 
180
        self.message(loglevels.DEBUG4, *stuff, **kw)
 
181
 
 
182
    def debug5(self, *stuff, **kw):
 
183
        self.message(loglevels.DEBUG5, *stuff, **kw)
 
184
 
 
185
    def debug6(self, *stuff, **kw):
 
186
        self.message(loglevels.DEBUG6, *stuff, **kw)
 
187
 
 
188
    def debug7(self, *stuff, **kw):
 
189
        self.message(loglevels.DEBUG7, *stuff, **kw)
 
190
 
 
191
    def debug8(self, *stuff, **kw):
 
192
        self.message(loglevels.DEBUG8, *stuff, **kw)
 
193
 
 
194
    def debug9(self, *stuff, **kw):
 
195
        self.message(loglevels.DEBUG9, *stuff, **kw)
 
196
 
 
197
 
 
198
class DevNullLogger(FakeLogger):
 
199
    """A logger that drops all messages."""
 
200
    # XXX: GavinPanella 2011-11-04 bug=886053: This is a test fixture not a
 
201
    # service.
 
202
 
 
203
    def message(self, *args, **kwargs):
 
204
        """Do absolutely nothing."""
 
205
 
 
206
 
 
207
class BufferLogger(FakeLogger):
 
208
    """A logger that logs to a StringIO object."""
 
209
    # XXX: GavinPanella 2011-11-04 bug=886053: This is a test fixture not a
 
210
    # service.
 
211
 
 
212
    def __init__(self):
 
213
        super(BufferLogger, self).__init__(StringIO())
 
214
 
 
215
    def getLogBuffer(self):
 
216
        """Return the existing log messages."""
 
217
        return self.output_file.getvalue()
 
218
 
 
219
    def clearLogBuffer(self):
 
220
        """Clear out the existing log messages."""
 
221
        self.output_file = StringIO()
 
222
 
 
223
    def getLogBufferAndClear(self):
 
224
        """Return the existing log messages and clear the buffer."""
 
225
        messages = self.getLogBuffer()
 
226
        self.clearLogBuffer()
 
227
        return messages
 
228
 
 
229
    @property
 
230
    def content(self):
 
231
        """Return a `testtools.content.Content` for this object's buffer.
 
232
 
 
233
        Use with `testtools.TestCase.addDetail`, `fixtures.Fixture.addDetail`,
 
234
        and anything else that understands details.
 
235
        """
 
236
        get_bytes = lambda: [self.getLogBuffer().encode("utf-8")]
 
237
        return Content(UTF8_TEXT, get_bytes)