~canonical-livepatch-dependencies/canonical-livepatch-service-dependencies/twisted

« back to all changes in this revision

Viewing changes to twisted/python/log.py

  • Committer: Free Ekanayaka
  • Date: 2016-07-01 12:22:33 UTC
  • Revision ID: free.ekanayaka@canonical.com-20160701122233-nh55w514zwzoz1ip
Tags: upstream-16.2.0
ImportĀ upstreamĀ versionĀ 16.2.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.test.test_log -*-
 
2
# Copyright (c) Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
"""
 
6
Logging and metrics infrastructure.
 
7
"""
 
8
 
 
9
from __future__ import division, absolute_import
 
10
 
 
11
import sys
 
12
import time
 
13
import warnings
 
14
 
 
15
from datetime import datetime
 
16
 
 
17
from zope.interface import Interface
 
18
 
 
19
from twisted.python.compat import unicode, _PY3
 
20
from twisted.python import context
 
21
from twisted.python import reflect
 
22
from twisted.python import util
 
23
from twisted.python import failure
 
24
from twisted.python.threadable import synchronize
 
25
from twisted.logger import (
 
26
    Logger as NewLogger, LogLevel as NewLogLevel,
 
27
    STDLibLogObserver as NewSTDLibLogObserver,
 
28
    LegacyLogObserverWrapper, LoggingFile, LogPublisher as NewPublisher,
 
29
    globalLogPublisher as newGlobalLogPublisher,
 
30
    globalLogBeginner as newGlobalLogBeginner,
 
31
)
 
32
 
 
33
from twisted.logger._global import LogBeginner
 
34
from twisted.logger._legacy import publishToNewObserver as _publishNew
 
35
 
 
36
 
 
37
 
 
38
class ILogContext:
 
39
    """
 
40
    Actually, this interface is just a synonym for the dictionary interface,
 
41
    but it serves as a key for the default information in a log.
 
42
 
 
43
    I do not inherit from C{Interface} because the world is a cruel place.
 
44
    """
 
45
 
 
46
 
 
47
 
 
48
class ILogObserver(Interface):
 
49
    """
 
50
    An observer which can do something with log events.
 
51
 
 
52
    Given that most log observers are actually bound methods, it's okay to not
 
53
    explicitly declare provision of this interface.
 
54
    """
 
55
    def __call__(eventDict):
 
56
        """
 
57
        Log an event.
 
58
 
 
59
        @type eventDict: C{dict} with C{str} keys.
 
60
        @param eventDict: A dictionary with arbitrary keys.  However, these
 
61
            keys are often available:
 
62
              - C{message}: A C{tuple} of C{str} containing messages to be
 
63
                logged.
 
64
              - C{system}: A C{str} which indicates the "system" which is
 
65
                generating this event.
 
66
              - C{isError}: A C{bool} indicating whether this event represents
 
67
                an error.
 
68
              - C{failure}: A L{failure.Failure} instance
 
69
              - C{why}: Used as header of the traceback in case of errors.
 
70
              - C{format}: A string format used in place of C{message} to
 
71
                customize the event.  The intent is for the observer to format
 
72
                a message by doing something like C{format % eventDict}.
 
73
        """
 
74
 
 
75
 
 
76
 
 
77
context.setDefault(ILogContext,
 
78
                   {"system": "-"})
 
79
 
 
80
 
 
81
def callWithContext(ctx, func, *args, **kw):
 
82
    newCtx = context.get(ILogContext).copy()
 
83
    newCtx.update(ctx)
 
84
    return context.call({ILogContext: newCtx}, func, *args, **kw)
 
85
 
 
86
 
 
87
 
 
88
def callWithLogger(logger, func, *args, **kw):
 
89
    """
 
90
    Utility method which wraps a function in a try:/except:, logs a failure if
 
91
    one occurs, and uses the system's logPrefix.
 
92
    """
 
93
    try:
 
94
        lp = logger.logPrefix()
 
95
    except KeyboardInterrupt:
 
96
        raise
 
97
    except:
 
98
        lp = '(buggy logPrefix method)'
 
99
        err(system=lp)
 
100
    try:
 
101
        return callWithContext({"system": lp}, func, *args, **kw)
 
102
    except KeyboardInterrupt:
 
103
        raise
 
104
    except:
 
105
        err(system=lp)
 
106
 
 
107
 
 
108
 
 
109
def err(_stuff=None, _why=None, **kw):
 
110
    """
 
111
    Write a failure to the log.
 
112
 
 
113
    The C{_stuff} and C{_why} parameters use an underscore prefix to lessen
 
114
    the chance of colliding with a keyword argument the application wishes
 
115
    to pass.  It is intended that they be supplied with arguments passed
 
116
    positionally, not by keyword.
 
117
 
 
118
    @param _stuff: The failure to log.  If C{_stuff} is C{None} a new
 
119
        L{Failure} will be created from the current exception state.  If
 
120
        C{_stuff} is an C{Exception} instance it will be wrapped in a
 
121
        L{Failure}.
 
122
    @type _stuff: C{NoneType}, C{Exception}, or L{Failure}.
 
123
 
 
124
    @param _why: The source of this failure.  This will be logged along with
 
125
        C{_stuff} and should describe the context in which the failure
 
126
        occurred.
 
127
    @type _why: C{str}
 
128
    """
 
129
    if _stuff is None:
 
130
        _stuff = failure.Failure()
 
131
    if isinstance(_stuff, failure.Failure):
 
132
        msg(failure=_stuff, why=_why, isError=1, **kw)
 
133
    elif isinstance(_stuff, Exception):
 
134
        msg(failure=failure.Failure(_stuff), why=_why, isError=1, **kw)
 
135
    else:
 
136
        msg(repr(_stuff), why=_why, isError=1, **kw)
 
137
 
 
138
deferr = err
 
139
 
 
140
 
 
141
class Logger:
 
142
    """
 
143
    This represents a class which may 'own' a log. Used by subclassing.
 
144
    """
 
145
    def logPrefix(self):
 
146
        """
 
147
        Override this method to insert custom logging behavior.  Its
 
148
        return value will be inserted in front of every line.  It may
 
149
        be called more times than the number of output lines.
 
150
        """
 
151
        return '-'
 
152
 
 
153
 
 
154
 
 
155
class LogPublisher:
 
156
    """
 
157
    Class for singleton log message publishing.
 
158
    """
 
159
 
 
160
    synchronized = ['msg']
 
161
 
 
162
 
 
163
    def __init__(self, observerPublisher=None, publishPublisher=None,
 
164
                 logBeginner=None, warningsModule=warnings):
 
165
        if publishPublisher is None:
 
166
            publishPublisher = NewPublisher()
 
167
            if observerPublisher is None:
 
168
                observerPublisher = publishPublisher
 
169
        if observerPublisher is None:
 
170
            observerPublisher = NewPublisher()
 
171
        self._observerPublisher = observerPublisher
 
172
        self._publishPublisher = publishPublisher
 
173
        self._legacyObservers = []
 
174
        if logBeginner is None:
 
175
            # This default behavior is really only used for testing.
 
176
            beginnerPublisher = NewPublisher()
 
177
            beginnerPublisher.addObserver(observerPublisher)
 
178
            logBeginner = LogBeginner(beginnerPublisher, NullFile(), sys,
 
179
                                      warnings)
 
180
        self._logBeginner = logBeginner
 
181
        self._warningsModule = warningsModule
 
182
        self._oldshowwarning = warningsModule.showwarning
 
183
        self.showwarning = self._logBeginner.showwarning
 
184
 
 
185
 
 
186
    @property
 
187
    def observers(self):
 
188
        """
 
189
        Property returning all observers registered on this L{LogPublisher}.
 
190
 
 
191
        @return: observers previously added with L{LogPublisher.addObserver}
 
192
        @rtype: L{list} of L{callable}
 
193
        """
 
194
        return [x.legacyObserver for x in self._legacyObservers]
 
195
 
 
196
 
 
197
    def _startLogging(self, other, setStdout):
 
198
        """
 
199
        Begin logging to the L{LogBeginner} associated with this
 
200
        L{LogPublisher}.
 
201
 
 
202
        @param other: the observer to log to.
 
203
        @type other: L{LogBeginner}
 
204
 
 
205
        @param setStdout: if true, send standard I/O to the observer as well.
 
206
        @type setStdout: L{bool}
 
207
        """
 
208
        wrapped = LegacyLogObserverWrapper(other)
 
209
        self._legacyObservers.append(wrapped)
 
210
        self._logBeginner.beginLoggingTo([wrapped], True, setStdout)
 
211
 
 
212
 
 
213
    def _stopLogging(self):
 
214
        """
 
215
        Clean-up hook for fixing potentially global state.  Only for testing of
 
216
        this module itself.  If you want less global state, use the new
 
217
        warnings system in L{twisted.logger}.
 
218
        """
 
219
        if self._warningsModule.showwarning == self.showwarning:
 
220
            self._warningsModule.showwarning = self._oldshowwarning
 
221
 
 
222
 
 
223
    def addObserver(self, other):
 
224
        """
 
225
        Add a new observer.
 
226
 
 
227
        @type other: Provider of L{ILogObserver}
 
228
        @param other: A callable object that will be called with each new log
 
229
            message (a dict).
 
230
        """
 
231
        wrapped = LegacyLogObserverWrapper(other)
 
232
        self._legacyObservers.append(wrapped)
 
233
        self._observerPublisher.addObserver(wrapped)
 
234
 
 
235
 
 
236
    def removeObserver(self, other):
 
237
        """
 
238
        Remove an observer.
 
239
        """
 
240
        for observer in self._legacyObservers:
 
241
            if observer.legacyObserver == other:
 
242
                self._legacyObservers.remove(observer)
 
243
                self._observerPublisher.removeObserver(observer)
 
244
                break
 
245
 
 
246
 
 
247
    def msg(self, *message, **kw):
 
248
        """
 
249
        Log a new message.
 
250
 
 
251
        The message should be a native string, i.e. bytes on Python 2 and
 
252
        Unicode on Python 3. For compatibility with both use the native string
 
253
        syntax, for example::
 
254
 
 
255
            >>> log.msg('Hello, world.')
 
256
 
 
257
        You MUST avoid passing in Unicode on Python 2, and the form::
 
258
 
 
259
            >>> log.msg('Hello ', 'world.')
 
260
 
 
261
        This form only works (sometimes) by accident.
 
262
 
 
263
        Keyword arguments will be converted into items in the event
 
264
        dict that is passed to L{ILogObserver} implementations.
 
265
        Each implementation, in turn, can define keys that are used
 
266
        by it specifically, in addition to common keys listed at
 
267
        L{ILogObserver.__call__}.
 
268
 
 
269
        For example, to set the C{system} parameter while logging
 
270
        a message::
 
271
 
 
272
        >>> log.msg('Started', system='Foo')
 
273
 
 
274
        """
 
275
        actualEventDict = (context.get(ILogContext) or {}).copy()
 
276
        actualEventDict.update(kw)
 
277
        actualEventDict['message'] = message
 
278
        actualEventDict['time'] = time.time()
 
279
        if "isError" not in actualEventDict:
 
280
            actualEventDict["isError"] = 0
 
281
 
 
282
        _publishNew(self._publishPublisher, actualEventDict, textFromEventDict)
 
283
 
 
284
 
 
285
synchronize(LogPublisher)
 
286
 
 
287
 
 
288
 
 
289
if 'theLogPublisher' not in globals():
 
290
    def _actually(something):
 
291
        """
 
292
        A decorator that returns its argument rather than the thing it is
 
293
        decorating.
 
294
 
 
295
        This allows the documentation generator to see an alias for a method or
 
296
        constant as an object with a docstring and thereby document it and
 
297
        allow references to it statically.
 
298
 
 
299
        @param something: An object to create an alias for.
 
300
        @type something: L{object}
 
301
 
 
302
        @return: a 1-argument callable that returns C{something}
 
303
        @rtype: L{object}
 
304
        """
 
305
        def decorate(thingWithADocstring):
 
306
            return something
 
307
        return decorate
 
308
 
 
309
    theLogPublisher = LogPublisher(
 
310
        observerPublisher=newGlobalLogPublisher,
 
311
        publishPublisher=newGlobalLogPublisher,
 
312
        logBeginner=newGlobalLogBeginner,
 
313
    )
 
314
 
 
315
 
 
316
    @_actually(theLogPublisher.addObserver)
 
317
    def addObserver(observer):
 
318
        """
 
319
        Add a log observer to the global publisher.
 
320
 
 
321
        @see: L{LogPublisher.addObserver}
 
322
 
 
323
        @param observer: a log observer
 
324
        @type observer: L{callable}
 
325
        """
 
326
 
 
327
 
 
328
    @_actually(theLogPublisher.removeObserver)
 
329
    def removeObserver(observer):
 
330
        """
 
331
        Remove a log observer from the global publisher.
 
332
 
 
333
        @see: L{LogPublisher.removeObserver}
 
334
 
 
335
        @param observer: a log observer previously added with L{addObserver}
 
336
        @type observer: L{callable}
 
337
        """
 
338
 
 
339
 
 
340
    @_actually(theLogPublisher.msg)
 
341
    def msg(*message, **event):
 
342
        """
 
343
        Publish a message to the global log publisher.
 
344
 
 
345
        @see: L{LogPublisher.msg}
 
346
 
 
347
        @param message: the log message
 
348
        @type message: C{tuple} of L{str} (native string)
 
349
 
 
350
        @param event: fields for the log event
 
351
        @type event: L{dict} mapping L{str} (native string) to L{object}
 
352
        """
 
353
 
 
354
 
 
355
    @_actually(theLogPublisher.showwarning)
 
356
    def showwarning():
 
357
        """
 
358
        Publish a Python warning through the global log publisher.
 
359
 
 
360
        @see: L{LogPublisher.showwarning}
 
361
        """
 
362
 
 
363
 
 
364
 
 
365
def _safeFormat(fmtString, fmtDict):
 
366
    """
 
367
    Try to format a string, swallowing all errors to always return a string.
 
368
 
 
369
    @note: For backward-compatibility reasons, this function ensures that it
 
370
        returns a native string, meaning C{bytes} in Python 2 and C{unicode} in
 
371
        Python 3.
 
372
 
 
373
    @param fmtString: a C{%}-format string
 
374
 
 
375
    @param fmtDict: string formatting arguments for C{fmtString}
 
376
 
 
377
    @return: A native string, formatted from C{fmtString} and C{fmtDict}.
 
378
    @rtype: L{str}
 
379
    """
 
380
    # There's a way we could make this if not safer at least more
 
381
    # informative: perhaps some sort of str/repr wrapper objects
 
382
    # could be wrapped around the things inside of C{fmtDict}. That way
 
383
    # if the event dict contains an object with a bad __repr__, we
 
384
    # can only cry about that individual object instead of the
 
385
    # entire event dict.
 
386
    try:
 
387
        text = fmtString % fmtDict
 
388
    except KeyboardInterrupt:
 
389
        raise
 
390
    except:
 
391
        try:
 
392
            text = ('Invalid format string or unformattable object in '
 
393
                    'log message: %r, %s' % (fmtString, fmtDict))
 
394
        except:
 
395
            try:
 
396
                text = ('UNFORMATTABLE OBJECT WRITTEN TO LOG with fmt %r, '
 
397
                        'MESSAGE LOST' % (fmtString,))
 
398
            except:
 
399
                text = ('PATHOLOGICAL ERROR IN BOTH FORMAT STRING AND '
 
400
                        'MESSAGE DETAILS, MESSAGE LOST')
 
401
 
 
402
    # Return a native string
 
403
    if _PY3:
 
404
        if isinstance(text, bytes):
 
405
            text = text.decode("utf-8")
 
406
    else:
 
407
        if isinstance(text, unicode):
 
408
            text = text.encode("utf-8")
 
409
 
 
410
    return text
 
411
 
 
412
 
 
413
 
 
414
def textFromEventDict(eventDict):
 
415
    """
 
416
    Extract text from an event dict passed to a log observer. If it cannot
 
417
    handle the dict, it returns None.
 
418
 
 
419
    The possible keys of eventDict are:
 
420
     - C{message}: by default, it holds the final text. It's required, but can
 
421
       be empty if either C{isError} or C{format} is provided (the first
 
422
       having the priority).
 
423
     - C{isError}: boolean indicating the nature of the event.
 
424
     - C{failure}: L{failure.Failure} instance, required if the event is an
 
425
       error.
 
426
     - C{why}: if defined, used as header of the traceback in case of errors.
 
427
     - C{format}: string format used in place of C{message} to customize
 
428
       the event. It uses all keys present in C{eventDict} to format
 
429
       the text.
 
430
    Other keys will be used when applying the C{format}, or ignored.
 
431
    """
 
432
    edm = eventDict['message']
 
433
    if not edm:
 
434
        if eventDict['isError'] and 'failure' in eventDict:
 
435
            why = eventDict.get('why')
 
436
            if why:
 
437
                why = reflect.safe_str(why)
 
438
            else:
 
439
                why = 'Unhandled Error'
 
440
            try:
 
441
                traceback = eventDict['failure'].getTraceback()
 
442
            except Exception as e:
 
443
                traceback = '(unable to obtain traceback): ' + str(e)
 
444
            text = (why + '\n' + traceback)
 
445
        elif 'format' in eventDict:
 
446
            text = _safeFormat(eventDict['format'], eventDict)
 
447
        else:
 
448
            # We don't know how to log this
 
449
            return None
 
450
    else:
 
451
        text = ' '.join(map(reflect.safe_str, edm))
 
452
    return text
 
453
 
 
454
 
 
455
 
 
456
class _GlobalStartStopMixIn:
 
457
    """
 
458
    Mix-in for global log observers that can start and stop.
 
459
    """
 
460
 
 
461
    def start(self):
 
462
        """
 
463
        Start observing log events.
 
464
        """
 
465
        addObserver(self.emit)
 
466
 
 
467
 
 
468
    def stop(self):
 
469
        """
 
470
        Stop observing log events.
 
471
        """
 
472
        removeObserver(self.emit)
 
473
 
 
474
 
 
475
 
 
476
class FileLogObserver(_GlobalStartStopMixIn):
 
477
    """
 
478
    Log observer that writes to a file-like object.
 
479
 
 
480
    @type timeFormat: C{str} or C{NoneType}
 
481
    @ivar timeFormat: If not C{None}, the format string passed to strftime().
 
482
    """
 
483
 
 
484
    timeFormat = None
 
485
 
 
486
    def __init__(self, f):
 
487
        # Compatibility
 
488
        self.write = f.write
 
489
        self.flush = f.flush
 
490
 
 
491
 
 
492
    def getTimezoneOffset(self, when):
 
493
        """
 
494
        Return the current local timezone offset from UTC.
 
495
 
 
496
        @type when: C{int}
 
497
        @param when: POSIX (ie, UTC) timestamp for which to find the offset.
 
498
 
 
499
        @rtype: C{int}
 
500
        @return: The number of seconds offset from UTC.  West is positive,
 
501
        east is negative.
 
502
        """
 
503
        offset = datetime.utcfromtimestamp(when) - datetime.fromtimestamp(when)
 
504
        return offset.days * (60 * 60 * 24) + offset.seconds
 
505
 
 
506
 
 
507
    def formatTime(self, when):
 
508
        """
 
509
        Format the given UTC value as a string representing that time in the
 
510
        local timezone.
 
511
 
 
512
        By default it's formatted as a ISO8601-like string (ISO8601 date and
 
513
        ISO8601 time separated by a space). It can be customized using the
 
514
        C{timeFormat} attribute, which will be used as input for the underlying
 
515
        L{datetime.datetime.strftime} call.
 
516
 
 
517
        @type when: C{int}
 
518
        @param when: POSIX (ie, UTC) timestamp for which to find the offset.
 
519
 
 
520
        @rtype: C{str}
 
521
        """
 
522
        if self.timeFormat is not None:
 
523
            return datetime.fromtimestamp(when).strftime(self.timeFormat)
 
524
 
 
525
        tzOffset = -self.getTimezoneOffset(when)
 
526
        when = datetime.utcfromtimestamp(when + tzOffset)
 
527
        tzHour = abs(int(tzOffset / 60 / 60))
 
528
        tzMin = abs(int(tzOffset / 60 % 60))
 
529
        if tzOffset < 0:
 
530
            tzSign = '-'
 
531
        else:
 
532
            tzSign = '+'
 
533
        return '%d-%02d-%02d %02d:%02d:%02d%s%02d%02d' % (
 
534
            when.year, when.month, when.day,
 
535
            when.hour, when.minute, when.second,
 
536
            tzSign, tzHour, tzMin)
 
537
 
 
538
 
 
539
    def emit(self, eventDict):
 
540
        """
 
541
        Format the given log event as text and write it to the output file.
 
542
 
 
543
        @param eventDict: a log event
 
544
        @type eventDict: L{dict} mapping L{str} (native string) to L{object}
 
545
        """
 
546
        text = textFromEventDict(eventDict)
 
547
        if text is None:
 
548
            return
 
549
 
 
550
        timeStr = self.formatTime(eventDict["time"])
 
551
        fmtDict = {
 
552
            "system": eventDict["system"],
 
553
            "text": text.replace("\n", "\n\t")
 
554
        }
 
555
        msgStr = _safeFormat("[%(system)s] %(text)s\n", fmtDict)
 
556
 
 
557
        util.untilConcludes(self.write, timeStr + " " + msgStr)
 
558
        util.untilConcludes(self.flush)  # Hoorj!
 
559
 
 
560
 
 
561
 
 
562
class PythonLoggingObserver(_GlobalStartStopMixIn, object):
 
563
    """
 
564
    Output twisted messages to Python standard library L{logging} module.
 
565
 
 
566
    WARNING: specific logging configurations (example: network) can lead to
 
567
    a blocking system. Nothing is done here to prevent that, so be sure to not
 
568
    use this: code within Twisted, such as twisted.web, assumes that logging
 
569
    does not block.
 
570
    """
 
571
 
 
572
    def __init__(self, loggerName="twisted"):
 
573
        """
 
574
        @param loggerName: identifier used for getting logger.
 
575
        @type loggerName: C{str}
 
576
        """
 
577
        self._newObserver = NewSTDLibLogObserver(loggerName)
 
578
 
 
579
 
 
580
    def emit(self, eventDict):
 
581
        """
 
582
        Receive a twisted log entry, format it and bridge it to python.
 
583
 
 
584
        By default the logging level used is info; log.err produces error
 
585
        level, and you can customize the level by using the C{logLevel} key::
 
586
 
 
587
            >>> log.msg('debugging', logLevel=logging.DEBUG)
 
588
        """
 
589
        if 'log_format' in eventDict:
 
590
            _publishNew(self._newObserver, eventDict, textFromEventDict)
 
591
 
 
592
 
 
593
 
 
594
class StdioOnnaStick:
 
595
    """
 
596
    Class that pretends to be stdout/err, and turns writes into log messages.
 
597
 
 
598
    @ivar isError: boolean indicating whether this is stderr, in which cases
 
599
                   log messages will be logged as errors.
 
600
 
 
601
    @ivar encoding: unicode encoding used to encode any unicode strings
 
602
                    written to this object.
 
603
    """
 
604
 
 
605
    closed = 0
 
606
    softspace = 0
 
607
    mode = 'wb'
 
608
    name = '<stdio (log)>'
 
609
 
 
610
    def __init__(self, isError=0, encoding=None):
 
611
        self.isError = isError
 
612
        if encoding is None:
 
613
            encoding = sys.getdefaultencoding()
 
614
        self.encoding = encoding
 
615
        self.buf = ''
 
616
 
 
617
 
 
618
    def close(self):
 
619
        pass
 
620
 
 
621
 
 
622
    def fileno(self):
 
623
        return -1
 
624
 
 
625
 
 
626
    def flush(self):
 
627
        pass
 
628
 
 
629
 
 
630
    def read(self):
 
631
        raise IOError("can't read from the log!")
 
632
 
 
633
    readline = read
 
634
    readlines = read
 
635
    seek = read
 
636
    tell = read
 
637
 
 
638
 
 
639
    def write(self, data):
 
640
        if not _PY3 and isinstance(data, unicode):
 
641
            data = data.encode(self.encoding)
 
642
        d = (self.buf + data).split('\n')
 
643
        self.buf = d[-1]
 
644
        messages = d[0:-1]
 
645
        for message in messages:
 
646
            msg(message, printed=1, isError=self.isError)
 
647
 
 
648
 
 
649
    def writelines(self, lines):
 
650
        for line in lines:
 
651
            if not _PY3 and isinstance(line, unicode):
 
652
                line = line.encode(self.encoding)
 
653
            msg(line, printed=1, isError=self.isError)
 
654
 
 
655
 
 
656
 
 
657
def startLogging(file, *a, **kw):
 
658
    """
 
659
    Initialize logging to a specified file.
 
660
 
 
661
    @return: A L{FileLogObserver} if a new observer is added, None otherwise.
 
662
    """
 
663
    if isinstance(file, LoggingFile):
 
664
        return
 
665
    flo = FileLogObserver(file)
 
666
    startLoggingWithObserver(flo.emit, *a, **kw)
 
667
    return flo
 
668
 
 
669
 
 
670
 
 
671
def startLoggingWithObserver(observer, setStdout=1):
 
672
    """
 
673
    Initialize logging to a specified observer. If setStdout is true
 
674
    (defaults to yes), also redirect sys.stdout and sys.stderr
 
675
    to the specified file.
 
676
    """
 
677
    theLogPublisher._startLogging(observer, setStdout)
 
678
    msg("Log opened.")
 
679
 
 
680
 
 
681
 
 
682
class NullFile:
 
683
    """
 
684
    A file-like object that discards everything.
 
685
    """
 
686
    softspace = 0
 
687
 
 
688
    def read(self):
 
689
        """
 
690
        Do nothing.
 
691
        """
 
692
 
 
693
 
 
694
    def write(self, bytes):
 
695
        """
 
696
        Do nothing.
 
697
 
 
698
        @param bytes: data
 
699
        @type bytes: L{bytes}
 
700
        """
 
701
 
 
702
 
 
703
    def flush(self):
 
704
        """
 
705
        Do nothing.
 
706
        """
 
707
 
 
708
 
 
709
    def close(self):
 
710
        """
 
711
        Do nothing.
 
712
        """
 
713
 
 
714
 
 
715
 
 
716
def discardLogs():
 
717
    """
 
718
    Discard messages logged via the global C{logfile} object.
 
719
    """
 
720
    global logfile
 
721
    logfile = NullFile()
 
722
 
 
723
 
 
724
 
 
725
# Prevent logfile from being erased on reload.  This only works in cpython.
 
726
if 'logfile' not in globals():
 
727
    logfile = LoggingFile(logger=NewLogger(),
 
728
                          level=NewLogLevel.info,
 
729
                          encoding=getattr(sys.stdout, "encoding", None))
 
730
    logerr = LoggingFile(logger=NewLogger(),
 
731
                         level=NewLogLevel.error,
 
732
                         encoding=getattr(sys.stderr, "encoding", None))
 
733
 
 
734
 
 
735
 
 
736
class DefaultObserver(_GlobalStartStopMixIn):
 
737
    """
 
738
    Default observer.
 
739
 
 
740
    Will ignore all non-error messages and send error messages to sys.stderr.
 
741
    Will be removed when startLogging() is called for the first time.
 
742
    """
 
743
    stderr = sys.stderr
 
744
 
 
745
    def emit(self, eventDict):
 
746
        """
 
747
        Emit an event dict.
 
748
 
 
749
        @param eventDict: an event dict
 
750
        @type eventDict: dict
 
751
        """
 
752
        if eventDict["isError"]:
 
753
            text = textFromEventDict(eventDict)
 
754
            self.stderr.write(text)
 
755
            self.stderr.flush()
 
756
 
 
757
 
 
758
 
 
759
if 'defaultObserver' not in globals():
 
760
    defaultObserver = DefaultObserver()