1
# Copyright (C) 2005, Canonical Ltd
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
3
17
"""Messages and logging for bazaar-ng.
28
42
Exceptions are reported in a brief form to stderr so as not to look scary.
29
43
BzrErrors are required to be able to format themselves into a properly
30
explanatory message. This is not true for builtin excexceptions such as
44
explanatory message. This is not true for builtin exceptions such as
31
45
KeyError, which typically just str to "0". They're printed in a different
35
# TODO: in debug mode, stderr should get full tracebacks and also
36
# debug messages. (Is this really needed?)
38
49
# FIXME: Unfortunately it turns out that python's logging module
39
50
# is quite expensive, even when the message is not printed by any handlers.
40
51
# We should perhaps change back to just simply doing it here.
59
from bzrlib.lazy_import import lazy_import
60
lazy_import(globals(), """
61
from cStringIO import StringIO
48
from bzrlib.errors import BzrError, BzrNewError
69
lazy_import(globals(), """
51
78
_file_handler = None
52
79
_stderr_handler = None
55
83
_bzr_log_file = None
58
class QuietFormatter(logging.Formatter):
59
"""Formatter that supresses the details of errors.
61
This is used by default on stderr so as not to scare the user.
63
# At first I tried overriding formatException to suppress the
64
# exception details, but that has global effect: no loggers
65
# can get the exception details is we suppress them here.
67
def format(self, record):
68
if record.levelno >= logging.WARNING:
69
s = 'bzr: ' + record.levelname + ': '
72
s += record.getMessage()
74
s += '\n' + format_exception_short(record.exc_info)
84
_bzr_log_filename = None
77
87
# configure convenient aliases for output routines
79
89
_bzr_logger = logging.getLogger('bzr')
81
info = note = _bzr_logger.info
82
warning = _bzr_logger.warning
92
def note(*args, **kwargs):
93
# FIXME note always emits utf-8, regardless of the terminal encoding
95
bzrlib.ui.ui_factory.clear_term()
96
_bzr_logger.info(*args, **kwargs)
98
def warning(*args, **kwargs):
100
bzrlib.ui.ui_factory.clear_term()
101
_bzr_logger.warning(*args, **kwargs)
83
104
log_error = _bzr_logger.error
84
105
error = _bzr_logger.error
87
108
def mutter(fmt, *args):
88
109
if _trace_file is None:
90
if hasattr(_trace_file, 'closed') and _trace_file.closed:
111
if (getattr(_trace_file, 'closed', None) is not None) and _trace_file.closed:
114
if isinstance(fmt, unicode):
115
fmt = fmt.encode('utf8')
118
# It seems that if we do ascii % (unicode, ascii) we can
119
# get a unicode cannot encode ascii error, so make sure that "fmt"
120
# is a unicode string
123
if isinstance(arg, unicode):
124
arg = arg.encode('utf8')
125
real_args.append(arg)
126
out = fmt % tuple(real_args)
97
130
_trace_file.write(out)
131
# TODO: jam 20051227 Consider flushing the trace file to help debugging
135
def mutter_callsite(stacklevel, fmt, *args):
136
"""Perform a mutter of fmt and args, logging the call trace.
138
:param stacklevel: The number of frames to show. None will show all
140
:param fmt: The format string to pass to mutter.
141
:param args: A list of substitution variables.
144
traceback.print_stack(limit=stacklevel + 1, file=outf)
145
formatted_lines = outf.getvalue().splitlines()
146
formatted_stack = '\n'.join(formatted_lines[:-2])
147
mutter(fmt + "\nCalled from:\n%s", *(args + (formatted_stack,)))
101
150
def _rollover_trace_maybe(trace_fname):
105
154
if size <= 4 << 20:
107
156
old_fname = trace_fname + '.old'
108
from osutils import rename
109
rename(trace_fname, old_fname)
157
osutils.rename(trace_fname, old_fname)
114
def open_tracefile(tracefilename='~/.bzr.log'):
162
def open_tracefile(tracefilename=None):
115
163
# Messages are always written to here, so that we have some
116
164
# information if something goes wrong. In a future version this
117
165
# file will be removed on successful completion.
118
global _file_handler, _bzr_log_file
121
trace_fname = os.path.join(os.path.expanduser(tracefilename))
122
_rollover_trace_maybe(trace_fname)
166
global _file_handler, _bzr_log_file, _bzr_log_filename
169
if tracefilename is None:
170
if sys.platform == 'win32':
171
from bzrlib import win32utils
172
home = win32utils.get_home_location()
174
home = os.path.expanduser('~')
175
_bzr_log_filename = os.path.join(home, '.bzr.log')
177
_bzr_log_filename = tracefilename
179
_bzr_log_filename = os.path.expanduser(_bzr_log_filename)
180
_rollover_trace_maybe(_bzr_log_filename)
124
182
LINE_BUFFERED = 1
125
tf = codecs.open(trace_fname, 'at', 'utf8', buffering=LINE_BUFFERED)
183
#tf = codecs.open(trace_fname, 'at', 'utf8', buffering=LINE_BUFFERED)
184
tf = open(_bzr_log_filename, 'at', LINE_BUFFERED)
126
185
_bzr_log_file = tf
128
tf.write("\nthis is a debug log for diagnosing/reporting problems in bzr\n")
186
# tf.tell() on windows always return 0 until some writing done
189
tf.write("this is a debug log for diagnosing/reporting problems in bzr\n")
129
190
tf.write("you can delete or truncate this file, or include sections in\n")
130
tf.write("bug reports to bazaar-ng@lists.canonical.com\n\n")
191
tf.write("bug reports to bazaar@lists.canonical.com\n\n")
131
192
_file_handler = logging.StreamHandler(tf)
132
193
fmt = r'[%(process)5d] %(asctime)s.%(msecs)03d %(levelname)s: %(message)s'
133
194
datefmt = r'%a %H:%M:%S'
185
225
_trace_file = _bzr_log_file
186
226
if _file_handler:
187
227
_file_handler.setLevel(logging.DEBUG)
188
_bzr_logger.setLevel(logging.DEBUG)
228
_bzr_logger.setLevel(logging.DEBUG)
231
def set_verbosity_level(level):
232
"""Set the verbosity level.
234
:param level: -ve for quiet, 0 for normal, +ve for verbose
236
global _verbosity_level
237
_verbosity_level = level
238
_update_logging_level(level < 0)
241
def get_verbosity_level():
242
"""Get the verbosity level.
244
See set_verbosity_level() for values.
246
return _verbosity_level
192
249
def be_quiet(quiet=True):
193
global _stderr_handler, _stderr_quiet
195
_stderr_quiet = quiet
250
# Perhaps this could be deprecated now ...
252
set_verbosity_level(-1)
254
set_verbosity_level(0)
257
def _update_logging_level(quiet=True):
258
"""Hide INFO messages if quiet."""
197
260
_stderr_handler.setLevel(logging.WARNING)
221
289
def enable_test_log(to_file):
222
"""Redirect logging to a temporary file for a test"""
290
"""Redirect logging to a temporary file for a test
292
returns an opaque reference that should be passed to disable_test_log
293
after the test completes.
223
295
disable_default_logging()
224
global _test_log_hdlr, _trace_file
225
298
hdlr = logging.StreamHandler(to_file)
226
299
hdlr.setLevel(logging.DEBUG)
227
300
hdlr.setFormatter(logging.Formatter('%(levelname)8s %(message)s'))
228
301
_bzr_logger.addHandler(hdlr)
229
302
_bzr_logger.setLevel(logging.DEBUG)
230
_test_log_hdlr = hdlr
303
result = hdlr, _trace_file, _trace_depth
231
304
_trace_file = to_file
234
def disable_test_log():
235
_bzr_logger.removeHandler(_test_log_hdlr)
237
enable_default_logging()
240
def format_exception_short(exc_info):
241
"""Make a short string form of an exception.
243
This is used for display to stderr. It specially handles exception
244
classes without useful string methods.
246
The result has no trailing newline.
248
exc_info - typically an exception from sys.exc_info()
250
exc_type, exc_object, exc_tb = exc_info
253
return '(no exception)'
254
if isinstance(exc_object, (BzrError, BzrNewError)):
255
return str(exc_object)
258
tb = traceback.extract_tb(exc_tb)
259
msg = '%s: %s' % (exc_type, exc_object)
263
msg += '\n at %s line %d\n in %s' % (tb[-1][:3])
265
except Exception, formatting_exc:
266
# XXX: is this really better than just letting it run up?
267
return '(error formatting exception of type %s: %s)' \
268
% (exc_type, formatting_exc)
309
def disable_test_log((test_log_hdlr, old_trace_file, old_trace_depth)):
310
_bzr_logger.removeHandler(test_log_hdlr)
311
test_log_hdlr.close()
314
_trace_file = old_trace_file
315
_trace_depth = old_trace_depth
317
enable_default_logging()
320
def report_exception(exc_info, err_file):
321
"""Report an exception to err_file (typically stderr) and to .bzr.log.
323
This will show either a full traceback or a short message as appropriate.
325
:return: The appropriate exit code for this error.
327
exc_type, exc_object, exc_tb = exc_info
328
# Log the full traceback to ~/.bzr.log
329
log_exception_quietly()
330
if (isinstance(exc_object, IOError)
331
and getattr(exc_object, 'errno', None) == errno.EPIPE):
332
err_file.write("bzr: broken pipe\n")
333
return errors.EXIT_ERROR
334
elif isinstance(exc_object, KeyboardInterrupt):
335
err_file.write("bzr: interrupted\n")
336
return errors.EXIT_ERROR
337
elif not getattr(exc_object, 'internal_error', True):
338
report_user_error(exc_info, err_file)
339
return errors.EXIT_ERROR
340
elif isinstance(exc_object, (OSError, IOError)):
341
# Might be nice to catch all of these and show them as something more
342
# specific, but there are too many cases at the moment.
343
report_user_error(exc_info, err_file)
344
return errors.EXIT_ERROR
346
report_bug(exc_info, err_file)
347
return errors.EXIT_INTERNAL_ERROR
350
# TODO: Should these be specially encoding the output?
351
def report_user_error(exc_info, err_file):
352
"""Report to err_file an error that's not an internal error.
354
These don't get a traceback unless -Derror was given.
356
if 'error' in debug.debug_flags:
357
report_bug(exc_info, err_file)
359
err_file.write("bzr: ERROR: %s\n" % (exc_info[1],))
362
def report_bug(exc_info, err_file):
363
"""Report an exception that probably indicates a bug in bzr"""
365
exc_type, exc_object, exc_tb = exc_info
366
err_file.write("bzr: ERROR: %s.%s: %s\n" % (
367
exc_type.__module__, exc_type.__name__, exc_object))
369
traceback.print_exception(exc_type, exc_object, exc_tb, file=err_file)
371
err_file.write('bzr %s on python %s (%s)\n' % \
373
'.'.join(map(str, sys.version_info)),
375
err_file.write('arguments: %r\n' % sys.argv)
377
'encoding: %r, fsenc: %r, lang: %r\n' % (
378
osutils.get_user_encoding(), sys.getfilesystemencoding(),
379
os.environ.get('LANG')))
380
err_file.write("plugins:\n")
381
for name, a_plugin in sorted(plugin.plugins().items()):
382
err_file.write(" %-20s %s [%s]\n" %
383
(name, a_plugin.path(), a_plugin.__version__))
386
"** Please send this report to bazaar@lists.ubuntu.com\n"
387
" with a description of what you were doing when the\n"