/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/trace.py

  • Committer: John Ferlito
  • Date: 2009-09-02 04:31:45 UTC
  • mto: (4665.7.1 serve-init)
  • mto: This revision was merged to the branch mainline in revision 4913.
  • Revision ID: johnf@inodes.org-20090902043145-gxdsfw03ilcwbyn5
Add a debian init script for bzr --serve

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
"""Messages and logging.
 
17
"""Messages and logging for bazaar-ng.
18
18
 
19
19
Messages are supplied by callers as a string-formatting template, plus values
20
20
to be inserted into it.  The actual %-formatting is deferred to the log
23
23
Messages are classified by severity levels: critical, error, warning, info,
24
24
and debug.
25
25
 
26
 
They can be sent to two places: stderr, and `$XDG_CACHE_HOME/breezy/brz.log`.
27
 
For purposes such as running the test suite, they can also be redirected away
28
 
from both of those two places to another location.
 
26
They can be sent to two places: to stderr, and to ~/.bzr.log.  For purposes
 
27
such as running the test suite, they can also be redirected away from both of
 
28
those two places to another location.
29
29
 
30
 
`brz.log` gets all messages, and full tracebacks for uncaught exceptions.
 
30
~/.bzr.log gets all messages, and full tracebacks for uncaught exceptions.
31
31
This trace file is always in UTF-8, regardless of the user's default encoding,
32
32
so that we can always rely on writing any message.
33
33
 
34
34
Output to stderr depends on the mode chosen by the user.  By default, messages
35
35
of info and above are sent out, which results in progress messages such as the
36
 
list of files processed by add and commit.  In debug mode, stderr gets debug messages too.
 
36
list of files processed by add and commit.  In quiet mode, only warnings and
 
37
above are shown.  In debug mode, stderr gets debug messages too.
37
38
 
38
39
Errors that terminate an operation are generally passed back as exceptions;
39
40
others may be just emitted as messages.
54
55
# increased cost of logging.py is not so bad, and we could standardize on
55
56
# that.
56
57
 
57
 
import errno
58
 
from io import StringIO
 
58
import codecs
59
59
import logging
60
60
import os
61
61
import sys
 
62
import re
62
63
import time
63
64
 
64
 
from .lazy_import import lazy_import
 
65
from bzrlib.lazy_import import lazy_import
65
66
lazy_import(globals(), """
66
 
import tempfile
 
67
from cStringIO import StringIO
 
68
import errno
 
69
import locale
67
70
import traceback
68
71
""")
69
72
 
70
 
import breezy
 
73
import bzrlib
71
74
 
72
75
lazy_import(globals(), """
73
 
from breezy import (
74
 
    bedding,
 
76
from bzrlib import (
75
77
    debug,
 
78
    errors,
76
79
    osutils,
77
 
    ui,
 
80
    plugin,
 
81
    symbol_versioning,
78
82
    )
79
83
""")
80
 
from . import (
81
 
    errors,
82
 
    )
83
 
 
84
 
 
85
 
# global verbosity for breezy; controls the log level for stderr; 0=normal; <0
 
84
 
 
85
 
 
86
# global verbosity for bzrlib; controls the log level for stderr; 0=normal; <0
86
87
# is quiet; >0 is verbose.
87
88
_verbosity_level = 0
88
89
 
92
93
# than push/pop_log_file.
93
94
_trace_file = None
94
95
 
95
 
# Absolute path for brz.log.  Not changed even if the log/trace output is
 
96
# Absolute path for ~/.bzr.log.  Not changed even if the log/trace output is
96
97
# redirected elsewhere.  Used to show the location in --version.
97
 
_brz_log_filename = None
 
98
_bzr_log_filename = None
98
99
 
99
100
# The time the first message was written to the trace file, so that we can
100
101
# show relative times since startup.
101
 
_brz_log_start_time = breezy._start_time
 
102
_bzr_log_start_time = bzrlib._start_time
102
103
 
103
104
 
104
105
# held in a global for quick reference
105
 
_brz_logger = logging.getLogger('brz')
 
106
_bzr_logger = logging.getLogger('bzr')
106
107
 
107
108
 
108
109
def note(*args, **kwargs):
109
 
    """Output a note to the user.
110
 
 
111
 
    Takes the same parameters as logging.info.
112
 
 
113
 
    :return: None
114
 
    """
 
110
    # FIXME note always emits utf-8, regardless of the terminal encoding
 
111
    #
115
112
    # FIXME: clearing the ui and then going through the abstract logging
116
113
    # framework is whack; we should probably have a logging Handler that
117
114
    # deals with terminal output if needed.
118
 
    ui.ui_factory.clear_term()
119
 
    _brz_logger.info(*args, **kwargs)
 
115
    import bzrlib.ui
 
116
    bzrlib.ui.ui_factory.clear_term()
 
117
    _bzr_logger.info(*args, **kwargs)
120
118
 
121
119
 
122
120
def warning(*args, **kwargs):
123
 
    ui.ui_factory.clear_term()
124
 
    _brz_logger.warning(*args, **kwargs)
125
 
 
126
 
 
127
 
def show_error(*args, **kwargs):
128
 
    """Show an error message to the user.
129
 
 
130
 
    Don't use this for exceptions, use report_exception instead.
131
 
    """
132
 
    _brz_logger.error(*args, **kwargs)
133
 
 
134
 
 
135
 
class _Bytes(str):
136
 
    """Compat class for displaying bytes on Python 2."""
137
 
 
138
 
    def __repr__(self):
139
 
        return 'b' + str.__repr__(self)
140
 
 
141
 
    def __unicode__(self):
142
 
        return self.decode('ascii', 'replace')
143
 
 
 
121
    import bzrlib.ui
 
122
    bzrlib.ui.ui_factory.clear_term()
 
123
    _bzr_logger.warning(*args, **kwargs)
 
124
 
 
125
 
 
126
# configure convenient aliases for output routines
 
127
#
 
128
# TODO: deprecate them, have one name for each.
 
129
info = note
 
130
log_error = _bzr_logger.error
 
131
error =     _bzr_logger.error
 
132
 
 
133
 
 
134
_last_mutter_flush_time = None
144
135
 
145
136
def mutter(fmt, *args):
 
137
    global _last_mutter_flush_time
146
138
    if _trace_file is None:
147
139
        return
148
 
    # XXX: Don't check this every time; instead anyone who closes the file
149
 
    # ought to deregister it.  We can tolerate None.
150
140
    if (getattr(_trace_file, 'closed', None) is not None) and _trace_file.closed:
151
141
        return
152
142
 
153
 
    # Let format strings be specified as ascii bytes to help Python 2
154
 
    if isinstance(fmt, bytes):
155
 
        fmt = fmt.decode('ascii', 'replace')
 
143
    if isinstance(fmt, unicode):
 
144
        fmt = fmt.encode('utf8')
156
145
 
157
 
    if args:
158
 
        out = fmt % args
 
146
    if len(args) > 0:
 
147
        # It seems that if we do ascii % (unicode, ascii) we can
 
148
        # get a unicode cannot encode ascii error, so make sure that "fmt"
 
149
        # is a unicode string
 
150
        real_args = []
 
151
        for arg in args:
 
152
            if isinstance(arg, unicode):
 
153
                arg = arg.encode('utf8')
 
154
            real_args.append(arg)
 
155
        out = fmt % tuple(real_args)
159
156
    else:
160
157
        out = fmt
161
158
    now = time.time()
162
 
    out = '%0.3f  %s\n' % (now - _brz_log_start_time, out)
163
 
    _trace_file.write(out.encode('utf-8'))
164
 
    # there's no explicit flushing; the file is typically line buffered.
 
159
    timestamp = '%0.3f  ' % (now - _bzr_log_start_time,)
 
160
    out = timestamp + out + '\n'
 
161
    _trace_file.write(out)
 
162
    # We flush if we haven't flushed for a few seconds. We don't want to flush
 
163
    # on every mutter, but when a command takes a while, it can be nice to see
 
164
    # updates in the debug log.
 
165
    if (_last_mutter_flush_time is None
 
166
        or (now - _last_mutter_flush_time) > 2.0):
 
167
        flush = getattr(_trace_file, 'flush', None)
 
168
        if flush is not None:
 
169
            flush()
 
170
        _last_mutter_flush_time = now
165
171
 
166
172
 
167
173
def mutter_callsite(stacklevel, fmt, *args):
195
201
        return
196
202
 
197
203
 
198
 
def _get_brz_log_filename():
199
 
    """Return the brz log filename.
200
 
 
201
 
    :return: A path to the log file
202
 
    :raise EnvironmentError: If the cache directory could not be created
203
 
    """
204
 
    brz_log = os.environ.get('BRZ_LOG')
205
 
    if brz_log:
206
 
        return brz_log
207
 
    return os.path.join(bedding.cache_dir(), 'brz.log')
208
 
 
209
 
 
210
 
def _open_brz_log():
211
 
    """Open the brz.log trace file.
 
204
def _get_bzr_log_filename():
 
205
    bzr_log = os.environ.get('BZR_LOG')
 
206
    if bzr_log:
 
207
        return bzr_log
 
208
    home = os.environ.get('BZR_HOME')
 
209
    if home is None:
 
210
        if sys.platform == 'win32':
 
211
            from bzrlib import win32utils
 
212
            home = win32utils.get_home_location()
 
213
        else:
 
214
            home = os.path.expanduser('~')
 
215
    return os.path.join(home, '.bzr.log')
 
216
 
 
217
 
 
218
def _open_bzr_log():
 
219
    """Open the .bzr.log trace file.
212
220
 
213
221
    If the log is more than a particular length, the old file is renamed to
214
 
    brz.log.old and a new file is started.  Otherwise, we append to the
 
222
    .bzr.log.old and a new file is started.  Otherwise, we append to the
215
223
    existing file.
216
224
 
217
 
    This sets the global _brz_log_filename.
 
225
    This sets the global _bzr_log_filename.
218
226
    """
219
 
    global _brz_log_filename
220
 
 
221
 
    def _open_or_create_log_file(filename):
222
 
        """Open existing log file, or create with ownership and permissions
223
 
 
224
 
        It inherits the ownership and permissions (masked by umask) from
225
 
        the containing directory to cope better with being run under sudo
226
 
        with $HOME still set to the user's homedir.
227
 
        """
228
 
        flags = os.O_WRONLY | os.O_APPEND | osutils.O_TEXT
229
 
        while True:
230
 
            try:
231
 
                fd = os.open(filename, flags)
232
 
                break
233
 
            except OSError as e:
234
 
                if e.errno != errno.ENOENT:
235
 
                    raise
236
 
            try:
237
 
                fd = os.open(filename, flags | os.O_CREAT | os.O_EXCL, 0o666)
238
 
            except OSError as e:
239
 
                if e.errno != errno.EEXIST:
240
 
                    raise
241
 
            else:
242
 
                osutils.copy_ownership_from_path(filename)
243
 
                break
244
 
        return os.fdopen(fd, 'ab', 0)  # unbuffered
245
 
 
 
227
    global _bzr_log_filename
 
228
    _bzr_log_filename = _get_bzr_log_filename()
 
229
    _rollover_trace_maybe(_bzr_log_filename)
246
230
    try:
247
 
        _brz_log_filename = _get_brz_log_filename()
248
 
        _rollover_trace_maybe(_brz_log_filename)
249
 
 
250
 
        brz_log_file = _open_or_create_log_file(_brz_log_filename)
251
 
        brz_log_file.write(b'\n')
252
 
        if brz_log_file.tell() <= 2:
253
 
            brz_log_file.write(
254
 
                b"this is a debug log for diagnosing/reporting problems in brz\n")
255
 
            brz_log_file.write(
256
 
                b"you can delete or truncate this file, or include sections in\n")
257
 
            brz_log_file.write(
258
 
                b"bug reports to https://bugs.launchpad.net/brz/+filebug\n\n")
259
 
 
260
 
        return brz_log_file
261
 
 
262
 
    except EnvironmentError as e:
263
 
        # If we are failing to open the log, then most likely logging has not
264
 
        # been set up yet. So we just write to stderr rather than using
265
 
        # 'warning()'. If we using warning(), users get the unhelpful 'no
266
 
        # handlers registered for "brz"' when something goes wrong on the
267
 
        # server. (bug #503886)
268
 
        sys.stderr.write("failed to open trace file: %s\n" % (e,))
 
231
        bzr_log_file = open(_bzr_log_filename, 'at', 1) # line buffered
 
232
        # bzr_log_file.tell() on windows always return 0 until some writing done
 
233
        bzr_log_file.write('\n')
 
234
        if bzr_log_file.tell() <= 2:
 
235
            bzr_log_file.write("this is a debug log for diagnosing/reporting problems in bzr\n")
 
236
            bzr_log_file.write("you can delete or truncate this file, or include sections in\n")
 
237
            bzr_log_file.write("bug reports to https://bugs.launchpad.net/bzr/+filebug\n\n")
 
238
        return bzr_log_file
 
239
    except IOError, e:
 
240
        warning("failed to open trace file: %s" % (e))
269
241
    # TODO: What should happen if we fail to open the trace file?  Maybe the
270
242
    # objects should be pointed at /dev/null or the equivalent?  Currently
271
243
    # returns None which will cause failures later.
273
245
 
274
246
 
275
247
def enable_default_logging():
276
 
    """Configure default logging: messages to stderr and debug to brz.log
 
248
    """Configure default logging: messages to stderr and debug to .bzr.log
277
249
 
278
250
    This should only be called once per process.
279
251
 
280
 
    Non-command-line programs embedding breezy do not need to call this.  They
 
252
    Non-command-line programs embedding bzrlib do not need to call this.  They
281
253
    can instead either pass a file to _push_log_file, or act directly on
282
 
    logging.getLogger("brz").
 
254
    logging.getLogger("bzr").
283
255
 
284
256
    Output can be redirected away by calling _push_log_file.
285
 
 
286
 
    :return: A memento from push_log_file for restoring the log state.
287
257
    """
288
 
    start_time = osutils.format_local_date(_brz_log_start_time,
 
258
    # Do this before we open the log file, so we prevent
 
259
    # get_terminal_encoding() from mutter()ing multiple times
 
260
    term_encoding = osutils.get_terminal_encoding()
 
261
    start_time = osutils.format_local_date(_bzr_log_start_time,
289
262
                                           timezone='local')
290
 
    brz_log_file = _open_brz_log()
291
 
    if brz_log_file is not None:
292
 
        brz_log_file.write(start_time.encode('utf-8') + b'\n')
293
 
    memento = push_log_file(
294
 
        brz_log_file,
 
263
    # create encoded wrapper around stderr
 
264
    bzr_log_file = _open_bzr_log()
 
265
    if bzr_log_file is not None:
 
266
        bzr_log_file.write(start_time.encode('utf-8') + '\n')
 
267
    push_log_file(bzr_log_file,
295
268
        r'[%(process)5d] %(asctime)s.%(msecs)03d %(levelname)s: %(message)s',
296
269
        r'%Y-%m-%d %H:%M:%S')
297
 
    # after hooking output into brz_log, we also need to attach a stderr
 
270
    # after hooking output into bzr_log, we also need to attach a stderr
298
271
    # handler, writing only at level info and with encoding
299
 
    stderr_handler = logging.StreamHandler(stream=sys.stderr)
300
 
    logging.getLogger('brz').addHandler(stderr_handler)
301
 
    return memento
 
272
    writer_factory = codecs.getwriter(term_encoding)
 
273
    encoded_stderr = writer_factory(sys.stderr, errors='replace')
 
274
    stderr_handler = logging.StreamHandler(encoded_stderr)
 
275
    stderr_handler.setLevel(logging.INFO)
 
276
    logging.getLogger('bzr').addHandler(stderr_handler)
302
277
 
303
278
 
304
279
def push_log_file(to_file, log_format=None, date_format=None):
307
282
    :param to_file: A file-like object to which messages will be sent.
308
283
 
309
284
    :returns: A memento that should be passed to _pop_log_file to restore the
310
 
        previously active logging.
 
285
    previously active logging.
311
286
    """
312
287
    global _trace_file
313
288
    # make a new handler
314
 
    new_handler = EncodedStreamHandler(to_file, "utf-8", level=logging.DEBUG)
 
289
    new_handler = logging.StreamHandler(to_file)
 
290
    new_handler.setLevel(logging.DEBUG)
315
291
    if log_format is None:
316
292
        log_format = '%(levelname)8s  %(message)s'
317
293
    new_handler.setFormatter(logging.Formatter(log_format, date_format))
318
294
    # save and remove any existing log handlers
319
 
    brz_logger = logging.getLogger('brz')
320
 
    old_handlers = brz_logger.handlers[:]
321
 
    del brz_logger.handlers[:]
 
295
    bzr_logger = logging.getLogger('bzr')
 
296
    old_handlers = bzr_logger.handlers[:]
 
297
    del bzr_logger.handlers[:]
322
298
    # set that as the default logger
323
 
    brz_logger.addHandler(new_handler)
324
 
    brz_logger.setLevel(logging.DEBUG)
 
299
    bzr_logger.addHandler(new_handler)
 
300
    bzr_logger.setLevel(logging.DEBUG)
325
301
    # TODO: check if any changes are needed to the root logger
326
302
    #
327
 
    # TODO: also probably need to save and restore the level on brz_logger.
 
303
    # TODO: also probably need to save and restore the level on bzr_logger.
328
304
    # but maybe we can avoid setting the logger level altogether, and just set
329
305
    # the level on the handler?
330
306
    #
332
308
    old_trace_file = _trace_file
333
309
    # send traces to the new one
334
310
    _trace_file = to_file
 
311
    result = new_handler, _trace_file
335
312
    return ('log_memento', old_handlers, new_handler, old_trace_file, to_file)
336
313
 
337
314
 
338
 
def pop_log_file(entry):
 
315
def pop_log_file((magic, old_handlers, new_handler, old_trace_file, new_trace_file)):
339
316
    """Undo changes to logging/tracing done by _push_log_file.
340
317
 
341
 
    This flushes, but does not close the trace file (so that anything that was
342
 
    in it is output.
 
318
    This flushes, but does not close the trace file.
343
319
 
344
320
    Takes the memento returned from _push_log_file."""
345
 
    (magic, old_handlers, new_handler, old_trace_file, new_trace_file) = entry
346
321
    global _trace_file
347
322
    _trace_file = old_trace_file
348
 
    brz_logger = logging.getLogger('brz')
349
 
    brz_logger.removeHandler(new_handler)
350
 
    # must be closed, otherwise logging will try to close it at exit, and the
 
323
    bzr_logger = logging.getLogger('bzr')
 
324
    bzr_logger.removeHandler(new_handler)
 
325
    # must be closed, otherwise logging will try to close it atexit, and the
351
326
    # file will likely already be closed underneath.
352
327
    new_handler.close()
353
 
    brz_logger.handlers = old_handlers
354
 
    if new_trace_file is not None:
355
 
        new_trace_file.flush()
 
328
    bzr_logger.handlers = old_handlers
 
329
    new_trace_file.flush()
356
330
 
357
331
 
358
332
def log_exception_quietly():
373
347
    global _verbosity_level
374
348
    _verbosity_level = level
375
349
    _update_logging_level(level < 0)
376
 
    ui.ui_factory.be_quiet(level < 0)
377
350
 
378
351
 
379
352
def get_verbosity_level():
385
358
 
386
359
 
387
360
def be_quiet(quiet=True):
 
361
    # Perhaps this could be deprecated now ...
388
362
    if quiet:
389
363
        set_verbosity_level(-1)
390
364
    else:
394
368
def _update_logging_level(quiet=True):
395
369
    """Hide INFO messages if quiet."""
396
370
    if quiet:
397
 
        _brz_logger.setLevel(logging.WARNING)
 
371
        _bzr_logger.setLevel(logging.WARNING)
398
372
    else:
399
 
        _brz_logger.setLevel(logging.INFO)
 
373
        _bzr_logger.setLevel(logging.INFO)
400
374
 
401
375
 
402
376
def is_quiet():
412
386
def debug_memory(message='', short=True):
413
387
    """Write out a memory dump."""
414
388
    if sys.platform == 'win32':
415
 
        from breezy import win32utils
 
389
        from bzrlib import win32utils
416
390
        win32utils.debug_memory_win32api(message=message, short=short)
417
391
    else:
418
392
        _debug_memory_proc(message=message, short=short)
420
394
 
421
395
_short_fields = ('VmPeak', 'VmSize', 'VmRSS')
422
396
 
423
 
 
424
397
def _debug_memory_proc(message='', short=True):
425
398
    try:
426
 
        status_file = open('/proc/%s/status' % os.getpid(), 'rb')
 
399
        status_file = file('/proc/%s/status' % os.getpid(), 'rb')
427
400
    except IOError:
428
401
        return
429
402
    try:
442
415
                    break
443
416
 
444
417
 
445
 
def _dump_memory_usage(err_file):
446
 
    try:
447
 
        try:
448
 
            fd, name = tempfile.mkstemp(prefix="brz_memdump", suffix=".json")
449
 
            dump_file = os.fdopen(fd, 'w')
450
 
            from meliae import scanner
451
 
            scanner.dump_gc_objects(dump_file)
452
 
            err_file.write("Memory dumped to %s\n" % name)
453
 
        except ImportError:
454
 
            err_file.write("Dumping memory requires meliae module.\n")
455
 
            log_exception_quietly()
456
 
        except BaseException:
457
 
            err_file.write("Exception while dumping memory.\n")
458
 
            log_exception_quietly()
459
 
    finally:
460
 
        if dump_file is not None:
461
 
            dump_file.close()
462
 
        elif fd is not None:
463
 
            os.close(fd)
464
 
 
465
 
 
466
 
def _qualified_exception_name(eclass, unqualified_breezy_errors=False):
467
 
    """Give name of error class including module for non-builtin exceptions
468
 
 
469
 
    If `unqualified_breezy_errors` is True, errors specific to breezy will
470
 
    also omit the module prefix.
471
 
    """
472
 
    class_name = eclass.__name__
473
 
    module_name = eclass.__module__
474
 
    if module_name in ("builtins", "exceptions", "__main__") or (
475
 
            unqualified_breezy_errors and module_name == "breezy.errors"):
476
 
        return class_name
477
 
    return "%s.%s" % (module_name, class_name)
478
 
 
479
 
 
480
418
def report_exception(exc_info, err_file):
481
 
    """Report an exception to err_file (typically stderr) and to brz.log.
 
419
    """Report an exception to err_file (typically stderr) and to .bzr.log.
482
420
 
483
421
    This will show either a full traceback or a short message as appropriate.
484
422
 
485
423
    :return: The appropriate exit code for this error.
486
424
    """
487
 
    # Log the full traceback to brz.log
 
425
    exc_type, exc_object, exc_tb = exc_info
 
426
    # Log the full traceback to ~/.bzr.log
488
427
    log_exception_quietly()
489
 
    if 'error' in debug.debug_flags:
490
 
        print_exception(exc_info, err_file)
491
 
        return errors.EXIT_ERROR
492
 
    exc_type, exc_object, exc_tb = exc_info
493
 
    if isinstance(exc_object, KeyboardInterrupt):
494
 
        err_file.write("brz: interrupted\n")
495
 
        return errors.EXIT_ERROR
496
 
    elif isinstance(exc_object, MemoryError):
497
 
        err_file.write("brz: out of memory\n")
498
 
        if 'mem_dump' in debug.debug_flags:
499
 
            _dump_memory_usage(err_file)
500
 
        else:
501
 
            err_file.write("Use -Dmem_dump to dump memory to a file.\n")
 
428
    if (isinstance(exc_object, IOError)
 
429
        and getattr(exc_object, 'errno', None) == errno.EPIPE):
 
430
        err_file.write("bzr: broken pipe\n")
 
431
        return errors.EXIT_ERROR
 
432
    elif isinstance(exc_object, KeyboardInterrupt):
 
433
        err_file.write("bzr: interrupted\n")
502
434
        return errors.EXIT_ERROR
503
435
    elif isinstance(exc_object, ImportError) \
504
 
            and str(exc_object).startswith("No module named "):
505
 
        report_user_error(
506
 
            exc_info, err_file,
 
436
        and str(exc_object).startswith("No module named "):
 
437
        report_user_error(exc_info, err_file,
507
438
            'You may need to install this Python library separately.')
508
439
        return errors.EXIT_ERROR
509
440
    elif not getattr(exc_object, 'internal_error', True):
510
441
        report_user_error(exc_info, err_file)
511
442
        return errors.EXIT_ERROR
512
 
    elif isinstance(exc_object, EnvironmentError):
513
 
        if getattr(exc_object, 'errno', None) == errno.EPIPE:
514
 
            err_file.write("brz: broken pipe\n")
515
 
            return errors.EXIT_ERROR
 
443
    elif isinstance(exc_object, (OSError, IOError)):
516
444
        # Might be nice to catch all of these and show them as something more
517
445
        # specific, but there are too many cases at the moment.
518
446
        report_user_error(exc_info, err_file)
524
452
 
525
453
def print_exception(exc_info, err_file):
526
454
    exc_type, exc_object, exc_tb = exc_info
527
 
    err_file.write("brz: ERROR: %s: %s\n" % (
528
 
        _qualified_exception_name(exc_type), exc_object))
 
455
    err_file.write("bzr: ERROR: %s.%s: %s\n" % (
 
456
        exc_type.__module__, exc_type.__name__, exc_object))
529
457
    err_file.write('\n')
530
458
    traceback.print_exception(exc_type, exc_object, exc_tb, file=err_file)
531
459
 
540
468
    :param advice: Extra advice to the user to be printed following the
541
469
        exception.
542
470
    """
543
 
    err_file.write(("brz: ERROR: %s\n" % (str(exc_info[1]),)))
 
471
    if 'error' in debug.debug_flags:
 
472
        print_exception(exc_info, err_file)
 
473
        return
 
474
    err_file.write("bzr: ERROR: %s\n" % (exc_info[1],))
544
475
    if advice:
545
 
        err_file.write(("%s\n" % advice))
 
476
        err_file.write("%s\n" % (advice,))
546
477
 
547
478
 
548
479
def report_bug(exc_info, err_file):
549
 
    """Report an exception that probably indicates a bug in brz"""
550
 
    from breezy.crash import report_bug
 
480
    """Report an exception that probably indicates a bug in bzr"""
 
481
    from bzrlib.crash import report_bug
551
482
    report_bug(exc_info, err_file)
552
 
 
553
 
 
554
 
def _flush_stdout_stderr():
555
 
    # called from the breezy library finalizer returned by breezy.initialize()
556
 
    try:
557
 
        sys.stdout.flush()
558
 
        sys.stderr.flush()
559
 
    except ValueError:
560
 
        # On Windows, I get ValueError calling stdout.flush() on a closed
561
 
        # handle
562
 
        pass
563
 
    except IOError as e:
564
 
        import errno
565
 
        if e.errno in [errno.EINVAL, errno.EPIPE]:
566
 
            pass
567
 
        else:
568
 
            raise
569
 
 
570
 
 
571
 
def _flush_trace():
572
 
    # called from the breezy library finalizer returned by breezy.initialize()
573
 
    global _trace_file
574
 
    if _trace_file:
575
 
        _trace_file.flush()
576
 
 
577
 
 
578
 
class EncodedStreamHandler(logging.Handler):
579
 
    """Robustly write logging events to a stream using the specified encoding
580
 
 
581
 
    Messages are expected to be formatted to unicode, but UTF-8 byte strings
582
 
    are also accepted. An error during formatting or a str message in another
583
 
    encoding will be quitely noted as an error in the Bazaar log file.
584
 
 
585
 
    The stream is not closed so sys.stdout or sys.stderr may be passed.
586
 
    """
587
 
 
588
 
    def __init__(self, stream, encoding=None, errors='strict', level=0):
589
 
        logging.Handler.__init__(self, level)
590
 
        self.stream = stream
591
 
        if encoding is None:
592
 
            encoding = getattr(stream, "encoding", "ascii")
593
 
        self.encoding = encoding
594
 
        self.errors = errors
595
 
 
596
 
    def flush(self):
597
 
        flush = getattr(self.stream, "flush", None)
598
 
        if flush is not None:
599
 
            flush()
600
 
 
601
 
    def emit(self, record):
602
 
        try:
603
 
            if not isinstance(record.msg, str):
604
 
                msg = record.msg.decode("utf-8")
605
 
                record.msg = msg
606
 
            line = self.format(record)
607
 
            if not isinstance(line, str):
608
 
                line = line.decode("utf-8")
609
 
            self.stream.write(line.encode(self.encoding, self.errors) + b"\n")
610
 
        except Exception:
611
 
            log_exception_quietly()
612
 
            # Try saving the details that would have been logged in some form
613
 
            msg = args = "<Unformattable>"
614
 
            try:
615
 
                msg = repr(record.msg)
616
 
                args = repr(record.args)
617
 
            except Exception:
618
 
                pass
619
 
            # Using mutter() bypasses the logging module and writes directly
620
 
            # to the file so there's no danger of getting into a loop here.
621
 
            mutter("Logging record unformattable: %s %% %s", msg, args)
622
 
 
623
 
 
624
 
class Config(object):
625
 
    """Configuration of message tracing in breezy.
626
 
 
627
 
    This implements the context manager protocol and should manage any global
628
 
    variables still used. The default config used is DefaultConfig, but
629
 
    embedded uses of breezy may wish to use a custom manager.
630
 
    """
631
 
 
632
 
    def __enter__(self):
633
 
        return self  # This is bound to the 'as' clause in a with statement.
634
 
 
635
 
    def __exit__(self, exc_type, exc_val, exc_tb):
636
 
        return False  # propogate exceptions.
637
 
 
638
 
 
639
 
class DefaultConfig(Config):
640
 
    """A default configuration for tracing of messages in breezy.
641
 
 
642
 
    This implements the context manager protocol.
643
 
    """
644
 
 
645
 
    def __enter__(self):
646
 
        self._original_filename = _brz_log_filename
647
 
        self._original_state = enable_default_logging()
648
 
        return self  # This is bound to the 'as' clause in a with statement.
649
 
 
650
 
    def __exit__(self, exc_type, exc_val, exc_tb):
651
 
        pop_log_file(self._original_state)
652
 
        global _brz_log_filename
653
 
        _brz_log_filename = self._original_filename
654
 
        return False  # propogate exceptions.