/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: Martin Pool
  • Date: 2006-06-04 21:22:51 UTC
  • mto: This revision was merged to the branch mainline in revision 1797.
  • Revision ID: mbp@sourcefrog.net-20060604212251-8f5dc15da9189eac
When an unhandled exception occurs, write the traceback to stderr.

Also encourage the user to send this to the list.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 by Canonical Ltd
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
16
 
 
17
"""Messages and logging for bazaar-ng.
 
18
 
 
19
Messages are supplied by callers as a string-formatting template, plus values
 
20
to be inserted into it.  The actual %-formatting is deferred to the log
 
21
library so that it doesn't need to be done for messages that won't be emitted.
 
22
 
 
23
Messages are classified by severity levels: critical, error, warning, info,
 
24
and debug.
 
25
 
 
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
 
 
30
~/.bzr.log gets all messages, and full tracebacks for uncaught exceptions.
 
31
This trace file is always in UTF-8, regardless of the user's default encoding,
 
32
so that we can always rely on writing any message.
 
33
 
 
34
Output to stderr depends on the mode chosen by the user.  By default, messages
 
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 quiet mode, only warnings and
 
37
above are shown.  In debug mode, stderr gets debug messages too.
 
38
 
 
39
Errors that terminate an operation are generally passed back as exceptions;
 
40
others may be just emitted as messages.
 
41
 
 
42
Exceptions are reported in a brief form to stderr so as not to look scary.
 
43
BzrErrors are required to be able to format themselves into a properly
 
44
explanatory message.  This is not true for builtin excexceptions such as
 
45
KeyError, which typically just str to "0".  They're printed in a different
 
46
form.
 
47
"""
 
48
 
 
49
# FIXME: Unfortunately it turns out that python's logging module
 
50
# is quite expensive, even when the message is not printed by any handlers.
 
51
# We should perhaps change back to just simply doing it here.
 
52
 
 
53
 
 
54
import sys
 
55
import os
 
56
import logging
 
57
 
 
58
import bzrlib
 
59
from bzrlib.errors import BzrError, BzrNewError
 
60
 
 
61
 
 
62
_file_handler = None
 
63
_stderr_handler = None
 
64
_stderr_quiet = False
 
65
_trace_file = None
 
66
_trace_depth = 0
 
67
_bzr_log_file = None
 
68
 
 
69
 
 
70
class QuietFormatter(logging.Formatter):
 
71
    """Formatter that supresses the details of errors.
 
72
 
 
73
    This is used by default on stderr so as not to scare the user.
 
74
    """
 
75
    # At first I tried overriding formatException to suppress the
 
76
    # exception details, but that has global effect: no loggers
 
77
    # can get the exception details is we suppress them here.
 
78
 
 
79
    def format(self, record):
 
80
        if record.levelno >= logging.WARNING:
 
81
            s = 'bzr: ' + record.levelname + ': '
 
82
        else:
 
83
            s = ''
 
84
        s += record.getMessage()
 
85
        if record.exc_info:
 
86
            s += '\n' + format_exception_short(record.exc_info)
 
87
        return s
 
88
        
 
89
# configure convenient aliases for output routines
 
90
 
 
91
_bzr_logger = logging.getLogger('bzr')
 
92
 
 
93
def note(*args, **kwargs):
 
94
    import bzrlib.ui
 
95
    bzrlib.ui.ui_factory.clear_term()
 
96
    _bzr_logger.info(*args, **kwargs)
 
97
 
 
98
def warning(*args, **kwargs):
 
99
    import bzrlib.ui
 
100
    bzrlib.ui.ui_factory.clear_term()
 
101
    _bzr_logger.warning(*args, **kwargs)
 
102
 
 
103
info = note
 
104
log_error = _bzr_logger.error
 
105
error =     _bzr_logger.error
 
106
 
 
107
 
 
108
def mutter(fmt, *args):
 
109
    if _trace_file is None:
 
110
        return
 
111
    if hasattr(_trace_file, 'closed') and _trace_file.closed:
 
112
        return
 
113
    if len(args) > 0:
 
114
        out = fmt % args
 
115
    else:
 
116
        out = fmt
 
117
    out += '\n'
 
118
    _trace_file.write(out)
 
119
debug = mutter
 
120
 
 
121
 
 
122
def _rollover_trace_maybe(trace_fname):
 
123
    import stat
 
124
    try:
 
125
        size = os.stat(trace_fname)[stat.ST_SIZE]
 
126
        if size <= 4 << 20:
 
127
            return
 
128
        old_fname = trace_fname + '.old'
 
129
        from osutils import rename
 
130
        rename(trace_fname, old_fname)
 
131
    except OSError:
 
132
        return
 
133
 
 
134
 
 
135
def open_tracefile(tracefilename='~/.bzr.log'):
 
136
    # Messages are always written to here, so that we have some
 
137
    # information if something goes wrong.  In a future version this
 
138
    # file will be removed on successful completion.
 
139
    global _file_handler, _bzr_log_file
 
140
    import stat, codecs
 
141
 
 
142
    trace_fname = os.path.join(os.path.expanduser(tracefilename))
 
143
    _rollover_trace_maybe(trace_fname)
 
144
    try:
 
145
        LINE_BUFFERED = 1
 
146
        tf = codecs.open(trace_fname, 'at', 'utf8', buffering=LINE_BUFFERED)
 
147
        _bzr_log_file = tf
 
148
        if tf.tell() == 0:
 
149
            tf.write("\nthis is a debug log for diagnosing/reporting problems in bzr\n")
 
150
            tf.write("you can delete or truncate this file, or include sections in\n")
 
151
            tf.write("bug reports to bazaar-ng@lists.canonical.com\n\n")
 
152
        _file_handler = logging.StreamHandler(tf)
 
153
        fmt = r'[%(process)5d] %(asctime)s.%(msecs)03d %(levelname)s: %(message)s'
 
154
        datefmt = r'%a %H:%M:%S'
 
155
        _file_handler.setFormatter(logging.Formatter(fmt, datefmt))
 
156
        _file_handler.setLevel(logging.DEBUG)
 
157
        logging.getLogger('').addHandler(_file_handler)
 
158
    except IOError, e:
 
159
        warning("failed to open trace file: %s" % (e))
 
160
 
 
161
 
 
162
def log_startup(argv):
 
163
    debug('\n\nbzr %s invoked on python %s (%s)',
 
164
          bzrlib.__version__,
 
165
          '.'.join(map(str, sys.version_info)),
 
166
          sys.platform)
 
167
    debug('  arguments: %r', argv)
 
168
    debug('  working dir: %r', os.getcwdu())
 
169
 
 
170
 
 
171
def log_exception_quietly():
 
172
    """Log the last exception to the trace file only.
 
173
 
 
174
    Used for exceptions that occur internally and that may be 
 
175
    interesting to developers but not to users.  For example, 
 
176
    errors loading plugins.
 
177
    """
 
178
    import traceback
 
179
    debug(traceback.format_exc())
 
180
 
 
181
 
 
182
def enable_default_logging():
 
183
    """Configure default logging to stderr and .bzr.log"""
 
184
    # FIXME: if this is run twice, things get confused
 
185
    global _stderr_handler, _file_handler, _trace_file, _bzr_log_file
 
186
    _stderr_handler = logging.StreamHandler()
 
187
    _stderr_handler.setFormatter(QuietFormatter())
 
188
    logging.getLogger('').addHandler(_stderr_handler)
 
189
    _stderr_handler.setLevel(logging.INFO)
 
190
    if not _file_handler:
 
191
        open_tracefile()
 
192
    _trace_file = _bzr_log_file
 
193
    if _file_handler:
 
194
        _file_handler.setLevel(logging.DEBUG)
 
195
    _bzr_logger.setLevel(logging.DEBUG) 
 
196
 
 
197
 
 
198
 
 
199
def be_quiet(quiet=True):
 
200
    global _stderr_handler, _stderr_quiet
 
201
    
 
202
    _stderr_quiet = quiet
 
203
    if quiet:
 
204
        _stderr_handler.setLevel(logging.WARNING)
 
205
    else:
 
206
        _stderr_handler.setLevel(logging.INFO)
 
207
 
 
208
 
 
209
def is_quiet():
 
210
    global _stderr_quiet
 
211
    return _stderr_quiet
 
212
 
 
213
 
 
214
def disable_default_logging():
 
215
    """Turn off default log handlers.
 
216
 
 
217
    This is intended to be used by the test framework, which doesn't
 
218
    want leakage from the code-under-test into the main logs.
 
219
    """
 
220
 
 
221
    l = logging.getLogger('')
 
222
    l.removeHandler(_stderr_handler)
 
223
    if _file_handler:
 
224
        l.removeHandler(_file_handler)
 
225
    _trace_file = None
 
226
 
 
227
 
 
228
def enable_test_log(to_file):
 
229
    """Redirect logging to a temporary file for a test
 
230
    
 
231
    returns an opaque reference that should be passed to disable_test_log
 
232
    after the test completes.
 
233
    """
 
234
    disable_default_logging()
 
235
    global _trace_file
 
236
    global _trace_depth
 
237
    hdlr = logging.StreamHandler(to_file)
 
238
    hdlr.setLevel(logging.DEBUG)
 
239
    hdlr.setFormatter(logging.Formatter('%(levelname)8s  %(message)s'))
 
240
    _bzr_logger.addHandler(hdlr)
 
241
    _bzr_logger.setLevel(logging.DEBUG)
 
242
    result = hdlr, _trace_file, _trace_depth
 
243
    _trace_file = to_file
 
244
    _trace_depth += 1
 
245
    return result
 
246
 
 
247
 
 
248
def disable_test_log((test_log_hdlr, old_trace_file, old_trace_depth)):
 
249
    _bzr_logger.removeHandler(test_log_hdlr)
 
250
    test_log_hdlr.close()
 
251
    global _trace_file
 
252
    global _trace_depth
 
253
    _trace_file = old_trace_file
 
254
    _trace_depth = old_trace_depth
 
255
    if not _trace_depth:
 
256
        enable_default_logging()
 
257
 
 
258
 
 
259
def report_unhandled_exception(exc_info, err_file):
 
260
    """Report to stderr than an exception hit the top level.
 
261
 
 
262
    This is used only for exceptions that indicate a bug of some kind in bzr.
 
263
    """
 
264
    if isinstance(exc_info[1], (BzrError, BzrNewError)):
 
265
        report_user_error(exc_info, err_file)
 
266
    else:
 
267
        report_bug(exc_info, err_file)
 
268
 
 
269
 
 
270
# TODO: Should these be specially encoding the output?
 
271
def report_user_error(exc_info, err_file):
 
272
    exc_type, exc_object, exc_tb = exc_info
 
273
    print >>err_file, "bzr:",
 
274
    try:
 
275
        print >>err_file, str(exc_object)
 
276
    except Exception, formatting_exc:
 
277
        # XXX: is this really better than just letting it run up?
 
278
        print >>err_file, \
 
279
                '(error formatting exception of type %s: %s)' \
 
280
                % (exc_type, formatting_exc)
 
281
 
 
282
 
 
283
def report_bug(exc_info, err_file):
 
284
    import traceback
 
285
    exc_type, exc_object, exc_tb = exc_info
 
286
    print >>err_file, "bzr: unhandled error: %s: %s" % (exc_type, exc_object)
 
287
    print >>err_file
 
288
    traceback.print_exception(*exc_info)
 
289
    print >>err_file
 
290
    print >>err_file, "** please send this report to bazaar-ng@lists.ubuntu.com"
 
291
 
 
292
 
 
293
# TODO: Is this still used?
 
294
def format_exception_short(exc_info):
 
295
    """Make a short string form of an exception.
 
296
 
 
297
    This is used for display to stderr.  It specially handles exception
 
298
    classes without useful string methods.
 
299
 
 
300
    The result has no trailing newline, but does span a few lines and includes
 
301
    the function and line.
 
302
 
 
303
    :param exc_info: typically an exception from sys.exc_info()
 
304
    """
 
305
    exc_type, exc_object, exc_tb = exc_info
 
306
    try:
 
307
        if exc_type is None:
 
308
            return '(no exception)'
 
309
        if isinstance(exc_object, (BzrError, BzrNewError)):
 
310
            return str(exc_object)
 
311
        else:
 
312
            import traceback
 
313
            tb = traceback.extract_tb(exc_tb)
 
314
            msg = '%s: %s' % (exc_type, exc_object)
 
315
            if msg[-1] == '\n':
 
316
                msg = msg[:-1]
 
317
            if tb:
 
318
                msg += '\n  at %s line %d\n  in %s' % (tb[-1][:3])
 
319
            return msg
 
320
    except Exception, formatting_exc:
 
321
        # XXX: is this really better than just letting it run up?
 
322
        return '(error formatting exception of type %s: %s)' \
 
323
                % (exc_type, formatting_exc)