~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/trace.py

  • Committer: Aaron Bentley
  • Date: 2008-03-03 16:52:41 UTC
  • mfrom: (3144.3.11 fix-conflict-handling)
  • mto: This revision was merged to the branch mainline in revision 3250.
  • Revision ID: aaron@aaronbentley.com-20080303165241-0k2c7ggs6kr9q6hf
Merge with fix-conflict-handling

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 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
49
49
# FIXME: Unfortunately it turns out that python's logging module
50
50
# is quite expensive, even when the message is not printed by any handlers.
51
51
# We should perhaps change back to just simply doing it here.
 
52
#
 
53
# On the other hand, as of 1.2 we generally only call the mutter() statement
 
54
# if (according to debug_flags) we actually intend to write it.  So the
 
55
# increased cost of logging.py is not so bad, and we could standardize on
 
56
# that.
52
57
 
53
58
import codecs
54
59
import logging
73
78
    errors,
74
79
    osutils,
75
80
    plugin,
 
81
    symbol_versioning,
76
82
    )
77
83
""")
78
84
 
79
 
_file_handler = None
80
 
_stderr_handler = None
 
85
 
 
86
# global verbosity for bzrlib; controls the log level for stderr; 0=normal; <0
 
87
# is quiet; >0 is verbose.
81
88
_verbosity_level = 0
 
89
 
 
90
# File-like object where mutter/debug output is currently sent.  Can be
 
91
# changed by _push_log_file etc.  This is directly manipulated by some
 
92
# external code; maybe there should be functions to do that more precisely
 
93
# than push/pop_log_file.
82
94
_trace_file = None
83
 
_trace_depth = 0
84
 
_bzr_log_file = None
 
95
 
 
96
# Absolute path for ~/.bzr.log.  Not changed even if the log/trace output is
 
97
# redirected elsewhere.  Used to show the location in --version.
85
98
_bzr_log_filename = None
86
 
_start_time = bzrlib._start_time
87
 
 
88
 
 
89
 
# configure convenient aliases for output routines
90
 
 
 
99
 
 
100
# The time the first message was written to the trace file, so that we can
 
101
# show relative times since startup.
 
102
_bzr_log_start_time = bzrlib._start_time
 
103
 
 
104
 
 
105
# held in a global for quick reference
91
106
_bzr_logger = logging.getLogger('bzr')
92
107
 
93
108
 
94
109
def note(*args, **kwargs):
95
110
    # FIXME note always emits utf-8, regardless of the terminal encoding
 
111
    #
 
112
    # FIXME: clearing the ui and then going through the abstract logging
 
113
    # framework is whack; we should probably have a logging Handler that
 
114
    # deals with terminal output if needed.
96
115
    import bzrlib.ui
97
116
    bzrlib.ui.ui_factory.clear_term()
98
117
    _bzr_logger.info(*args, **kwargs)
99
118
 
 
119
 
100
120
def warning(*args, **kwargs):
101
121
    import bzrlib.ui
102
122
    bzrlib.ui.ui_factory.clear_term()
103
123
    _bzr_logger.warning(*args, **kwargs)
104
124
 
 
125
 
 
126
# configure convenient aliases for output routines
 
127
#
 
128
# TODO: deprecate them, have one name for each.
105
129
info = note
106
130
log_error = _bzr_logger.error
107
131
error =     _bzr_logger.error
128
152
        out = fmt % tuple(real_args)
129
153
    else:
130
154
        out = fmt
131
 
    timestamp = '%0.3f  ' % (time.time() - _start_time,)
 
155
    timestamp = '%0.3f  ' % (time.time() - _bzr_log_start_time,)
132
156
    out = timestamp + out + '\n'
133
157
    _trace_file.write(out)
134
 
    # TODO: jam 20051227 Consider flushing the trace file to help debugging
135
 
    #_trace_file.flush()
 
158
    # no need to flush here, the trace file is now linebuffered when it's
 
159
    # opened.
136
160
 
137
161
 
138
162
def mutter_callsite(stacklevel, fmt, *args):
162
186
        return
163
187
 
164
188
 
165
 
def open_tracefile(tracefilename=None):
166
 
    # Messages are always written to here, so that we have some
167
 
    # information if something goes wrong.  In a future version this
168
 
    # file will be removed on successful completion.
169
 
    global _file_handler, _bzr_log_file, _bzr_log_filename
170
 
    import codecs
171
 
 
172
 
    if tracefilename is None:
 
189
def _get_bzr_log_filename():
 
190
    bzr_log = os.environ.get('BZR_LOG')
 
191
    if bzr_log:
 
192
        return bzr_log
 
193
    home = os.environ.get('BZR_HOME')
 
194
    if home is None:
173
195
        if sys.platform == 'win32':
174
196
            from bzrlib import win32utils
175
197
            home = win32utils.get_home_location()
176
198
        else:
177
199
            home = os.path.expanduser('~')
178
 
        _bzr_log_filename = os.path.join(home, '.bzr.log')
179
 
    else:
180
 
        _bzr_log_filename = tracefilename
181
 
 
182
 
    _bzr_log_filename = os.path.expanduser(_bzr_log_filename)
 
200
    return os.path.join(home, '.bzr.log')
 
201
 
 
202
 
 
203
def _open_bzr_log():
 
204
    """Open the .bzr.log trace file.  
 
205
 
 
206
    If the log is more than a particular length, the old file is renamed to
 
207
    .bzr.log.old and a new file is started.  Otherwise, we append to the
 
208
    existing file.
 
209
 
 
210
    This sets the global _bzr_log_filename.
 
211
    """
 
212
    global _bzr_log_filename
 
213
    _bzr_log_filename = _get_bzr_log_filename()
183
214
    _rollover_trace_maybe(_bzr_log_filename)
184
215
    try:
185
 
        LINE_BUFFERED = 1
186
 
        #tf = codecs.open(trace_fname, 'at', 'utf8', buffering=LINE_BUFFERED)
187
 
        tf = open(_bzr_log_filename, 'at', LINE_BUFFERED)
188
 
        _bzr_log_file = tf
189
 
        # tf.tell() on windows always return 0 until some writing done
190
 
        tf.write('\n')
191
 
        if tf.tell() <= 2:
192
 
            tf.write("this is a debug log for diagnosing/reporting problems in bzr\n")
193
 
            tf.write("you can delete or truncate this file, or include sections in\n")
194
 
            tf.write("bug reports to https://bugs.launchpad.net/bzr/+filebug\n\n")
195
 
        _file_handler = logging.StreamHandler(tf)
196
 
        fmt = r'[%(process)5d] %(asctime)s.%(msecs)03d %(levelname)s: %(message)s'
197
 
        datefmt = r'%Y-%m-%d %H:%M:%S'
198
 
        _file_handler.setFormatter(logging.Formatter(fmt, datefmt))
199
 
        _file_handler.setLevel(logging.DEBUG)
200
 
        logging.getLogger('').addHandler(_file_handler)
 
216
        bzr_log_file = open(_bzr_log_filename, 'at', 1) # line buffered
 
217
        # bzr_log_file.tell() on windows always return 0 until some writing done
 
218
        bzr_log_file.write('\n')
 
219
        if bzr_log_file.tell() <= 2:
 
220
            bzr_log_file.write("this is a debug log for diagnosing/reporting problems in bzr\n")
 
221
            bzr_log_file.write("you can delete or truncate this file, or include sections in\n")
 
222
            bzr_log_file.write("bug reports to https://bugs.launchpad.net/bzr/+filebug\n\n")
 
223
        return bzr_log_file
201
224
    except IOError, e:
202
225
        warning("failed to open trace file: %s" % (e))
 
226
    # TODO: What should happen if we fail to open the trace file?  Maybe the
 
227
    # objects should be pointed at /dev/null or the equivalent?  Currently
 
228
    # returns None which will cause failures later.
 
229
 
 
230
 
 
231
def enable_default_logging():
 
232
    """Configure default logging: messages to stderr and debug to .bzr.log
 
233
    
 
234
    This should only be called once per process.
 
235
 
 
236
    Non-command-line programs embedding bzrlib do not need to call this.  They
 
237
    can instead either pass a file to _push_log_file, or act directly on
 
238
    logging.getLogger("bzr").
 
239
    
 
240
    Output can be redirected away by calling _push_log_file.
 
241
    """
 
242
    # create encoded wrapper around stderr
 
243
    bzr_log_file = _open_bzr_log()
 
244
    push_log_file(bzr_log_file,
 
245
        r'[%(process)5d] %(asctime)s.%(msecs)03d %(levelname)s: %(message)s',
 
246
        r'%Y-%m-%d %H:%M:%S')
 
247
    # after hooking output into bzr_log, we also need to attach a stderr
 
248
    # handler, writing only at level info and with encoding
 
249
    writer_factory = codecs.getwriter(osutils.get_terminal_encoding())
 
250
    encoded_stderr = writer_factory(sys.stderr, errors='replace')
 
251
    stderr_handler = logging.StreamHandler(encoded_stderr)
 
252
    stderr_handler.setLevel(logging.INFO)
 
253
    logging.getLogger('bzr').addHandler(stderr_handler)
 
254
 
 
255
 
 
256
def push_log_file(to_file, log_format=None, date_format=None):
 
257
    """Intercept log and trace messages and send them to a file.
 
258
 
 
259
    :param to_file: A file-like object to which messages will be sent.
 
260
 
 
261
    :returns: A memento that should be passed to _pop_log_file to restore the 
 
262
    previously active logging.
 
263
    """
 
264
    global _trace_file
 
265
    # make a new handler
 
266
    new_handler = logging.StreamHandler(to_file)
 
267
    new_handler.setLevel(logging.DEBUG)
 
268
    if log_format is None:
 
269
        log_format = '%(levelname)8s  %(message)s'
 
270
    new_handler.setFormatter(logging.Formatter(log_format, date_format))
 
271
    # save and remove any existing log handlers
 
272
    bzr_logger = logging.getLogger('bzr')
 
273
    old_handlers = bzr_logger.handlers[:]
 
274
    del bzr_logger.handlers[:]
 
275
    # set that as the default logger
 
276
    bzr_logger.addHandler(new_handler)
 
277
    bzr_logger.setLevel(logging.DEBUG)
 
278
    # TODO: check if any changes are needed to the root logger
 
279
    #
 
280
    # TODO: also probably need to save and restore the level on bzr_logger.
 
281
    # but maybe we can avoid setting the logger level altogether, and just set
 
282
    # the level on the handler?
 
283
    #
 
284
    # save the old trace file
 
285
    old_trace_file = _trace_file
 
286
    # send traces to the new one
 
287
    _trace_file = to_file
 
288
    result = new_handler, _trace_file
 
289
    return ('log_memento', old_handlers, new_handler, old_trace_file, to_file)
 
290
 
 
291
 
 
292
def pop_log_file((magic, old_handlers, new_handler, old_trace_file, new_trace_file)):
 
293
    """Undo changes to logging/tracing done by _push_log_file.
 
294
 
 
295
    This flushes, but does not close the trace file.
 
296
    
 
297
    Takes the memento returned from _push_log_file."""
 
298
    assert magic == 'log_memento'
 
299
    global _trace_file
 
300
    _trace_file = old_trace_file
 
301
    bzr_logger = logging.getLogger('bzr')
 
302
    bzr_logger.removeHandler(new_handler)
 
303
    # must be closed, otherwise logging will try to close it atexit, and the
 
304
    # file will likely already be closed underneath.
 
305
    new_handler.close()
 
306
    bzr_logger.handlers = old_handlers
 
307
    new_trace_file.flush()
 
308
 
 
309
 
 
310
@symbol_versioning.deprecated_function(symbol_versioning.one_two)
 
311
def enable_test_log(to_file):
 
312
    """Redirect logging to a temporary file for a test
 
313
    
 
314
    :returns: an opaque reference that should be passed to disable_test_log
 
315
    after the test completes.
 
316
    """
 
317
    return push_log_file(to_file)
 
318
 
 
319
 
 
320
@symbol_versioning.deprecated_function(symbol_versioning.one_two)
 
321
def disable_test_log(memento):
 
322
    return pop_log_file(memento)
203
323
 
204
324
 
205
325
def log_exception_quietly():
209
329
    interesting to developers but not to users.  For example, 
210
330
    errors loading plugins.
211
331
    """
212
 
    import traceback
213
332
    mutter(traceback.format_exc())
214
333
 
215
334
 
216
 
def enable_default_logging():
217
 
    """Configure default logging to stderr and .bzr.log"""
218
 
    # FIXME: if this is run twice, things get confused
219
 
    global _stderr_handler, _file_handler, _trace_file, _bzr_log_file
220
 
    # create encoded wrapper around stderr
221
 
    stderr = codecs.getwriter(osutils.get_terminal_encoding())(sys.stderr,
222
 
        errors='replace')
223
 
    _stderr_handler = logging.StreamHandler(stderr)
224
 
    logging.getLogger('').addHandler(_stderr_handler)
225
 
    _stderr_handler.setLevel(logging.INFO)
226
 
    if not _file_handler:
227
 
        open_tracefile()
228
 
    _trace_file = _bzr_log_file
229
 
    if _file_handler:
230
 
        _file_handler.setLevel(logging.DEBUG)
231
 
    _bzr_logger.setLevel(logging.DEBUG)
232
 
 
233
 
 
234
335
def set_verbosity_level(level):
235
336
    """Set the verbosity level.
236
337
 
260
361
def _update_logging_level(quiet=True):
261
362
    """Hide INFO messages if quiet."""
262
363
    if quiet:
263
 
        _stderr_handler.setLevel(logging.WARNING)
 
364
        _bzr_logger.setLevel(logging.WARNING)
264
365
    else:
265
 
        _stderr_handler.setLevel(logging.INFO)
 
366
        _bzr_logger.setLevel(logging.INFO)
266
367
 
267
368
 
268
369
def is_quiet():
275
376
    return _verbosity_level > 0
276
377
 
277
378
 
 
379
@symbol_versioning.deprecated_function(symbol_versioning.one_two)
278
380
def disable_default_logging():
279
381
    """Turn off default log handlers.
280
382
 
281
 
    This is intended to be used by the test framework, which doesn't
282
 
    want leakage from the code-under-test into the main logs.
283
 
    """
284
 
 
285
 
    l = logging.getLogger('')
286
 
    l.removeHandler(_stderr_handler)
287
 
    if _file_handler:
288
 
        l.removeHandler(_file_handler)
289
 
    _trace_file = None
290
 
 
291
 
 
292
 
def enable_test_log(to_file):
293
 
    """Redirect logging to a temporary file for a test
294
 
    
295
 
    returns an opaque reference that should be passed to disable_test_log
296
 
    after the test completes.
297
 
    """
298
 
    disable_default_logging()
299
 
    global _trace_file
300
 
    global _trace_depth
301
 
    hdlr = logging.StreamHandler(to_file)
302
 
    hdlr.setLevel(logging.DEBUG)
303
 
    hdlr.setFormatter(logging.Formatter('%(levelname)8s  %(message)s'))
304
 
    _bzr_logger.addHandler(hdlr)
305
 
    _bzr_logger.setLevel(logging.DEBUG)
306
 
    result = hdlr, _trace_file, _trace_depth
307
 
    _trace_file = to_file
308
 
    _trace_depth += 1
309
 
    return result
310
 
 
311
 
 
312
 
def disable_test_log((test_log_hdlr, old_trace_file, old_trace_depth)):
313
 
    _bzr_logger.removeHandler(test_log_hdlr)
314
 
    test_log_hdlr.close()
315
 
    global _trace_file
316
 
    global _trace_depth
317
 
    _trace_file = old_trace_file
318
 
    _trace_depth = old_trace_depth
319
 
    if not _trace_depth:
320
 
        enable_default_logging()
 
383
    Don't call this method, use _push_log_file and _pop_log_file instead.
 
384
    """
 
385
    pass
321
386
 
322
387
 
323
388
def report_exception(exc_info, err_file):
364
429
 
365
430
def report_bug(exc_info, err_file):
366
431
    """Report an exception that probably indicates a bug in bzr"""
367
 
    import traceback
368
432
    exc_type, exc_object, exc_tb = exc_info
369
433
    err_file.write("bzr: ERROR: %s.%s: %s\n" % (
370
434
        exc_type.__module__, exc_type.__name__, exc_object))