~landscape/zope3/ztk-1.1.3

« back to all changes in this revision

Viewing changes to src/twisted/python/log.py

  • Committer: Andreas Hasenack
  • Date: 2009-07-20 17:49:16 UTC
  • Revision ID: andreas@canonical.com-20090720174916-g2tn6qmietz2hn0u
Revert twisted removal, it breaks several dozen tests [trivial]

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.test.test_log -*-
 
2
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
"""Logging and metrics infrastructure.
 
6
"""
 
7
 
 
8
from __future__ import division
 
9
 
 
10
# System Imports
 
11
import sys
 
12
import time
 
13
import warnings
 
14
import datetime
 
15
 
 
16
# Sibling Imports
 
17
from twisted.python import util, context, reflect
 
18
 
 
19
class ILogContext:
 
20
    """Actually, this interface is just a synoym for the dictionary interface,
 
21
    but it serves as a key for the default information in a log.
 
22
 
 
23
    I do not inherit from Interface because the world is a cruel place.
 
24
    """
 
25
 
 
26
context.setDefault(ILogContext,
 
27
                   {"isError": 0,
 
28
                    "system": "-"})
 
29
 
 
30
def callWithContext(ctx, func, *args, **kw):
 
31
    newCtx = context.get(ILogContext).copy()
 
32
    newCtx.update(ctx)
 
33
    return context.call({ILogContext: newCtx}, func, *args, **kw)
 
34
 
 
35
def callWithLogger(logger, func, *args, **kw):
 
36
    """
 
37
    Utility method which wraps a function in a try:/except:, logs a failure if
 
38
    one occurrs, and uses the system's logPrefix.
 
39
    """
 
40
    try:
 
41
        lp = logger.logPrefix()
 
42
    except KeyboardInterrupt:
 
43
        raise
 
44
    except:
 
45
        lp = '(buggy logPrefix method)'
 
46
        err(system=lp)
 
47
    try:
 
48
        return callWithContext({"system": lp}, func, *args, **kw)
 
49
    except KeyboardInterrupt:
 
50
        raise
 
51
    except:
 
52
        err(system=lp)
 
53
 
 
54
def showwarning(message, category, filename, lineno, file=None):
 
55
    if file is None:
 
56
        msg(warning=message, category=reflect.qual(category), filename=filename, lineno=lineno,
 
57
            format="%(filename)s:%(lineno)s: %(category)s: %(warning)s")
 
58
    else:
 
59
        _oldshowwarning(message, category, filename, lineno, file)
 
60
 
 
61
_keepErrors = 0
 
62
_keptErrors = []
 
63
_ignoreErrors = []
 
64
 
 
65
def startKeepingErrors():
 
66
    """
 
67
    DEPRECATED in Twisted 2.5.
 
68
    
 
69
    Support function for testing frameworks.
 
70
 
 
71
    Start keeping errors in a buffer which can be retrieved (and emptied) with
 
72
    flushErrors.
 
73
    """
 
74
    warnings.warn("log.startKeepingErrors is deprecated since Twisted 2.5",
 
75
                  category=DeprecationWarning, stacklevel=2)
 
76
    global _keepErrors
 
77
    _keepErrors = 1
 
78
 
 
79
 
 
80
def flushErrors(*errorTypes):
 
81
    """
 
82
    DEPRECATED in Twisted 2.5.  See L{TestCase.flushLoggedErrors}.
 
83
 
 
84
    Support function for testing frameworks.
 
85
 
 
86
    Return a list of errors that occurred since the last call to flushErrors().
 
87
    (This will return None unless startKeepingErrors has been called.)
 
88
    """
 
89
 
 
90
    warnings.warn("log.flushErrors is deprecated since Twisted 2.5. "
 
91
                  "If you need to flush errors from within a unittest, "
 
92
                  "use TestCase.flushLoggedErrors instead.",
 
93
                  category=DeprecationWarning, stacklevel=2)
 
94
    return _flushErrors(*errorTypes)
 
95
 
 
96
 
 
97
def _flushErrors(*errorTypes):
 
98
    """
 
99
    PRIVATE. DEPRECATED. DON'T USE.
 
100
    """
 
101
    global _keptErrors
 
102
    k = _keptErrors
 
103
    _keptErrors = []
 
104
    if errorTypes:
 
105
        for erk in k:
 
106
            shouldReLog = 1
 
107
            for errT in errorTypes:
 
108
                if erk.check(errT):
 
109
                    shouldReLog = 0
 
110
            if shouldReLog:
 
111
                err(erk)
 
112
    return k
 
113
 
 
114
def ignoreErrors(*types):
 
115
    """DEPRECATED"""
 
116
    warnings.warn("log.ignoreErrors is deprecated since Twisted 2.5",
 
117
                  category=DeprecationWarning, stacklevel=2)
 
118
    _ignore(*types)
 
119
 
 
120
def _ignore(*types):
 
121
    """
 
122
    PRIVATE. DEPRECATED. DON'T USE.
 
123
    """
 
124
    for type in types:
 
125
        _ignoreErrors.append(type)
 
126
 
 
127
def clearIgnores():
 
128
    """DEPRECATED"""
 
129
    warnings.warn("log.clearIgnores is deprecated since Twisted 2.5",
 
130
                  category=DeprecationWarning, stacklevel=2)
 
131
    _clearIgnores()
 
132
 
 
133
def _clearIgnores():
 
134
    """
 
135
    PRIVATE. DEPRECATED. DON'T USE.
 
136
    """
 
137
    global _ignoreErrors
 
138
    _ignoreErrors = []
 
139
 
 
140
 
 
141
def err(_stuff=None, _why=None, **kw):
 
142
    """
 
143
    Write a failure to the log.
 
144
    """
 
145
    if _stuff is None:
 
146
        _stuff = failure.Failure()
 
147
    if isinstance(_stuff, failure.Failure):
 
148
        if _keepErrors:
 
149
            if _ignoreErrors:
 
150
                keep = 0
 
151
                for err in _ignoreErrors:
 
152
                    r = _stuff.check(err)
 
153
                    if r:
 
154
                        keep = 0
 
155
                        break
 
156
                    else:
 
157
                        keep = 1
 
158
                if keep:
 
159
                    _keptErrors.append(_stuff)
 
160
            else:
 
161
                _keptErrors.append(_stuff)
 
162
        msg(failure=_stuff, why=_why, isError=1, **kw)
 
163
    elif isinstance(_stuff, Exception):
 
164
        msg(failure=failure.Failure(_stuff), why=_why, isError=1, **kw)
 
165
    else:
 
166
        msg(repr(_stuff), why=_why, isError=1, **kw)
 
167
 
 
168
deferr = err
 
169
 
 
170
class Logger:
 
171
    """
 
172
    This represents a class which may 'own' a log. Used by subclassing.
 
173
    """
 
174
    def logPrefix(self):
 
175
        """
 
176
        Override this method to insert custom logging behavior.  Its
 
177
        return value will be inserted in front of every line.  It may
 
178
        be called more times than the number of output lines.
 
179
        """
 
180
        return '-'
 
181
 
 
182
class LogPublisher:
 
183
    """Class for singleton log message publishing."""
 
184
 
 
185
    synchronized = ['msg']
 
186
 
 
187
    def __init__(self):
 
188
        self.observers = []
 
189
 
 
190
    def addObserver(self, other):
 
191
        """Add a new observer.
 
192
 
 
193
        Observers are callable objects that will be called with each new log
 
194
        message (a dict).
 
195
        """
 
196
        assert callable(other)
 
197
        self.observers.append(other)
 
198
 
 
199
    def removeObserver(self, other):
 
200
        """Remove an observer."""
 
201
        self.observers.remove(other)
 
202
 
 
203
    def msg(self, *message, **kw):
 
204
        """Log a new message.
 
205
 
 
206
        For example::
 
207
 
 
208
        | log.msg('Hello, world.')
 
209
 
 
210
        In particular, you MUST avoid the forms::
 
211
 
 
212
        | log.msg(u'Hello, world.')
 
213
        | log.msg('Hello ', 'world.')
 
214
 
 
215
        These forms work (sometimes) by accident and will be disabled
 
216
        entirely in the future.
 
217
        """
 
218
        actualEventDict = (context.get(ILogContext) or {}).copy()
 
219
        actualEventDict.update(kw)
 
220
        actualEventDict['message'] = message
 
221
        actualEventDict['time'] = time.time()
 
222
        for i in xrange(len(self.observers) - 1, -1, -1):
 
223
            try:
 
224
                self.observers[i](actualEventDict)
 
225
            except KeyboardInterrupt:
 
226
                # Don't swallow keyboard interrupt!
 
227
                raise
 
228
            except UnicodeEncodeError:
 
229
                raise
 
230
            except:
 
231
                o = self.observers.pop(i)
 
232
                err(failure.Failure(),
 
233
                    "Log observer %s failed, removing from observer list." % (o,))
 
234
 
 
235
 
 
236
try:
 
237
    theLogPublisher
 
238
except NameError:
 
239
    theLogPublisher = LogPublisher()
 
240
    addObserver = theLogPublisher.addObserver
 
241
    removeObserver = theLogPublisher.removeObserver
 
242
    msg = theLogPublisher.msg
 
243
 
 
244
 
 
245
class FileLogObserver:
 
246
    """
 
247
    Log observer that writes to a file-like object.
 
248
 
 
249
    @type timeFormat: C{str} or C{NoneType}
 
250
    @ivar timeFormat: If not C{None}, the format string passed to strftime().
 
251
    """
 
252
    timeFormat = None
 
253
 
 
254
    def __init__(self, f):
 
255
        self.write = f.write
 
256
        self.flush = f.flush
 
257
 
 
258
    def _safeFormat(self, fmtString, crap):
 
259
        #There's a way we could make this if not safer at least more
 
260
        #informative: perhaps some sort of str/repr wrapper objects
 
261
        #could be wrapped around the things inside of 'crap'. That way
 
262
        #if the event dict contains an object with a bad __repr__, we
 
263
        #can only cry about that individual object instead of the
 
264
        #entire event dict.
 
265
        try:
 
266
            text = fmtString % crap
 
267
        except KeyboardInterrupt:
 
268
            raise
 
269
        except:
 
270
            try:
 
271
                text = ('Invalid format string or unformattable object in log message: %r, %s' % (fmtString, crap))
 
272
            except:
 
273
                try:
 
274
                    text = 'UNFORMATTABLE OBJECT WRITTEN TO LOG with fmt %r, MESSAGE LOST' % (fmtString,)
 
275
                except:
 
276
                    text = 'PATHOLOGICAL ERROR IN BOTH FORMAT STRING AND MESSAGE DETAILS, MESSAGE LOST'
 
277
        return text
 
278
 
 
279
 
 
280
    def getTimezoneOffset(self):
 
281
        """
 
282
        Return the current local timezone offset from UTC.
 
283
 
 
284
        @rtype: C{int}
 
285
        @return: The number of seconds offset from UTC.  West is positive,
 
286
        east is negative.
 
287
        """
 
288
        if time.daylight:
 
289
            return time.altzone
 
290
        return time.timezone
 
291
 
 
292
 
 
293
    def formatTime(self, when):
 
294
        """
 
295
        Return the given UTC value formatted as a human-readable string
 
296
        representing that time in the local timezone.
 
297
 
 
298
        @type when: C{int}
 
299
        @param when: POSIX timestamp to convert to a human-readable string.
 
300
 
 
301
        @rtype: C{str}
 
302
        """
 
303
        if self.timeFormat is not None:
 
304
            return time.strftime(self.timeFormat, time.localtime(when))
 
305
 
 
306
        tzOffset = -self.getTimezoneOffset()
 
307
        when = datetime.datetime.utcfromtimestamp(when + tzOffset)
 
308
        tzHour = int(tzOffset / 60 / 60)
 
309
        tzMin = int(tzOffset / 60 % 60)
 
310
        return '%d/%02d/%02d %02d:%02d %+03d%02d' % (
 
311
            when.year, when.month, when.day,
 
312
            when.hour, when.minute,
 
313
            tzHour, tzMin)
 
314
 
 
315
 
 
316
    def emit(self, eventDict):
 
317
        edm = eventDict['message']
 
318
        if not edm:
 
319
            if eventDict['isError'] and eventDict.has_key('failure'):
 
320
                text = ((eventDict.get('why') or 'Unhandled Error')
 
321
                        + '\n' + eventDict['failure'].getTraceback())
 
322
            elif eventDict.has_key('format'):
 
323
                text = self._safeFormat(eventDict['format'], eventDict)
 
324
            else:
 
325
                # we don't know how to log this
 
326
                return
 
327
        else:
 
328
            text = ' '.join(map(reflect.safe_str, edm))
 
329
 
 
330
        timeStr = self.formatTime(eventDict['time'])
 
331
        fmtDict = {'system': eventDict['system'], 'text': text.replace("\n", "\n\t")}
 
332
        msgStr = self._safeFormat("[%(system)s] %(text)s\n", fmtDict)
 
333
 
 
334
        util.untilConcludes(self.write, timeStr + " " + msgStr)
 
335
        util.untilConcludes(self.flush)  # Hoorj!
 
336
 
 
337
    def start(self):
 
338
        """Start observing log events."""
 
339
        addObserver(self.emit)
 
340
 
 
341
    def stop(self):
 
342
        """Stop observing log events."""
 
343
        removeObserver(self.emit)
 
344
 
 
345
 
 
346
class StdioOnnaStick:
 
347
    """Class that pretends to be stout/err."""
 
348
 
 
349
    closed = 0
 
350
    softspace = 0
 
351
    mode = 'wb'
 
352
    name = '<stdio (log)>'
 
353
 
 
354
    def __init__(self, isError=0):
 
355
        self.isError = isError
 
356
        self.buf = ''
 
357
 
 
358
    def close(self):
 
359
        pass
 
360
 
 
361
    def fileno(self):
 
362
        return -1
 
363
 
 
364
    def flush(self):
 
365
        pass
 
366
 
 
367
    def read(self):
 
368
        raise IOError("can't read from the log!")
 
369
 
 
370
    readline = read
 
371
    readlines = read
 
372
    seek = read
 
373
    tell = read
 
374
 
 
375
    def write(self, data):
 
376
        d = (self.buf + data).split('\n')
 
377
        self.buf = d[-1]
 
378
        messages = d[0:-1]
 
379
        for message in messages:
 
380
            msg(message, printed=1, isError=self.isError)
 
381
 
 
382
    def writelines(self, lines):
 
383
        for line in lines:
 
384
            msg(line, printed=1, isError=self.isError)
 
385
 
 
386
 
 
387
try:
 
388
    _oldshowwarning
 
389
except NameError:
 
390
    _oldshowwarning = None
 
391
 
 
392
 
 
393
def startLogging(file, *a, **kw):
 
394
    """Initialize logging to a specified file.
 
395
    """
 
396
    flo = FileLogObserver(file)
 
397
    startLoggingWithObserver(flo.emit, *a, **kw)
 
398
 
 
399
def startLoggingWithObserver(observer, setStdout=1):
 
400
    """Initialize logging to a specified observer. If setStdout is true
 
401
       (defaults to yes), also redirect sys.stdout and sys.stderr
 
402
       to the specified file.
 
403
    """
 
404
    global defaultObserver, _oldshowwarning
 
405
    if not _oldshowwarning:
 
406
        _oldshowwarning = warnings.showwarning
 
407
        warnings.showwarning = showwarning
 
408
    if defaultObserver:
 
409
        defaultObserver.stop()
 
410
        defaultObserver = None
 
411
    addObserver(observer)
 
412
    msg("Log opened.")
 
413
    if setStdout:
 
414
        sys.stdout = logfile
 
415
        sys.stderr = logerr
 
416
 
 
417
 
 
418
class NullFile:
 
419
    softspace = 0
 
420
    def read(self): pass
 
421
    def write(self, bytes): pass
 
422
    def flush(self): pass
 
423
    def close(self): pass
 
424
 
 
425
 
 
426
def discardLogs():
 
427
    """Throw away all logs.
 
428
    """
 
429
    global logfile
 
430
    logfile = NullFile()
 
431
 
 
432
 
 
433
# Prevent logfile from being erased on reload.  This only works in cpython.
 
434
try:
 
435
    logfile
 
436
except NameError:
 
437
    logfile = StdioOnnaStick(0)
 
438
    logerr = StdioOnnaStick(1)
 
439
 
 
440
 
 
441
class DefaultObserver:
 
442
    """Default observer.
 
443
 
 
444
    Will ignore all non-error messages and send error messages to sys.stderr.
 
445
    Will be removed when startLogging() is called for the first time.
 
446
    """
 
447
 
 
448
    def _emit(self, eventDict):
 
449
        if eventDict["isError"]:
 
450
            if eventDict.has_key('failure'):
 
451
                text = eventDict['failure'].getTraceback()
 
452
            else:
 
453
                text = " ".join([str(m) for m in eventDict["message"]]) + "\n"
 
454
            sys.stderr.write(text)
 
455
            sys.stderr.flush()
 
456
 
 
457
    def start(self):
 
458
        addObserver(self._emit)
 
459
 
 
460
    def stop(self):
 
461
        removeObserver(self._emit)
 
462
 
 
463
 
 
464
# Some more sibling imports, at the bottom and unqualified to avoid
 
465
# unresolvable circularity
 
466
import threadable, failure
 
467
threadable.synchronize(LogPublisher)
 
468
 
 
469
 
 
470
try:
 
471
    defaultObserver
 
472
except NameError:
 
473
    defaultObserver = DefaultObserver()
 
474
    defaultObserver.start()
 
475