/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 breezy/trace.py

  • Committer: Gustav Hartvigsson
  • Date: 2021-01-09 21:36:27 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20210109213627-h1xwcutzy9m7a99b
Added 'Case Preserving Working Tree Use Cases' from Canonical Wiki

* Addod a page from the Canonical Bazaar wiki
  with information on the scmeatics of case
  perserving filesystems an a case insensitive
  filesystem works.
  
  * Needs re-work, but this will do as it is the
    same inforamoton as what was on the linked
    page in the currint documentation.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2011 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Messages and logging.
 
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: 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.
 
29
 
 
30
`brz.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 debug mode, stderr gets debug messages too.
 
37
 
 
38
Errors that terminate an operation are generally passed back as exceptions;
 
39
others may be just emitted as messages.
 
40
 
 
41
Exceptions are reported in a brief form to stderr so as not to look scary.
 
42
BzrErrors are required to be able to format themselves into a properly
 
43
explanatory message.  This is not true for builtin exceptions such as
 
44
KeyError, which typically just str to "0".  They're printed in a different
 
45
form.
 
46
"""
 
47
 
 
48
# FIXME: Unfortunately it turns out that python's logging module
 
49
# is quite expensive, even when the message is not printed by any handlers.
 
50
# We should perhaps change back to just simply doing it here.
 
51
#
 
52
# On the other hand, as of 1.2 we generally only call the mutter() statement
 
53
# if (according to debug_flags) we actually intend to write it.  So the
 
54
# increased cost of logging.py is not so bad, and we could standardize on
 
55
# that.
 
56
 
 
57
import errno
 
58
from io import StringIO
 
59
import logging
 
60
import os
 
61
import sys
 
62
import time
 
63
 
 
64
from .lazy_import import lazy_import
 
65
lazy_import(globals(), """
 
66
import tempfile
 
67
import traceback
 
68
""")
 
69
 
 
70
import breezy
 
71
 
 
72
lazy_import(globals(), """
 
73
from breezy import (
 
74
    bedding,
 
75
    debug,
 
76
    osutils,
 
77
    ui,
 
78
    )
 
79
""")
 
80
from . import (
 
81
    errors,
 
82
    )
 
83
 
 
84
 
 
85
# global verbosity for breezy; controls the log level for stderr; 0=normal; <0
 
86
# is quiet; >0 is verbose.
 
87
_verbosity_level = 0
 
88
 
 
89
# File-like object where mutter/debug output is currently sent.  Can be
 
90
# changed by _push_log_file etc.  This is directly manipulated by some
 
91
# external code; maybe there should be functions to do that more precisely
 
92
# than push/pop_log_file.
 
93
_trace_file = None
 
94
 
 
95
# Absolute path for brz.log.  Not changed even if the log/trace output is
 
96
# redirected elsewhere.  Used to show the location in --version.
 
97
_brz_log_filename = None
 
98
 
 
99
# The time the first message was written to the trace file, so that we can
 
100
# show relative times since startup.
 
101
_brz_log_start_time = breezy._start_time
 
102
 
 
103
 
 
104
# held in a global for quick reference
 
105
_brz_logger = logging.getLogger('brz')
 
106
 
 
107
 
 
108
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
    """
 
115
    # FIXME: clearing the ui and then going through the abstract logging
 
116
    # framework is whack; we should probably have a logging Handler that
 
117
    # deals with terminal output if needed.
 
118
    ui.ui_factory.clear_term()
 
119
    _brz_logger.info(*args, **kwargs)
 
120
 
 
121
 
 
122
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
 
 
144
 
 
145
def mutter(fmt, *args):
 
146
    if _trace_file is None:
 
147
        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
    if (getattr(_trace_file, 'closed', None) is not None) and _trace_file.closed:
 
151
        return
 
152
 
 
153
    # Let format strings be specified as ascii bytes to help Python 2
 
154
    if isinstance(fmt, bytes):
 
155
        fmt = fmt.decode('ascii', 'replace')
 
156
 
 
157
    if args:
 
158
        out = fmt % args
 
159
    else:
 
160
        out = fmt
 
161
    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.
 
165
 
 
166
 
 
167
def mutter_callsite(stacklevel, fmt, *args):
 
168
    """Perform a mutter of fmt and args, logging the call trace.
 
169
 
 
170
    :param stacklevel: The number of frames to show. None will show all
 
171
        frames.
 
172
    :param fmt: The format string to pass to mutter.
 
173
    :param args: A list of substitution variables.
 
174
    """
 
175
    outf = StringIO()
 
176
    if stacklevel is None:
 
177
        limit = None
 
178
    else:
 
179
        limit = stacklevel + 1
 
180
    traceback.print_stack(limit=limit, file=outf)
 
181
    formatted_lines = outf.getvalue().splitlines()
 
182
    formatted_stack = '\n'.join(formatted_lines[:-2])
 
183
    mutter(fmt + "\nCalled from:\n%s", *(args + (formatted_stack,)))
 
184
 
 
185
 
 
186
def _rollover_trace_maybe(trace_fname):
 
187
    import stat
 
188
    try:
 
189
        size = os.stat(trace_fname)[stat.ST_SIZE]
 
190
        if size <= 4 << 20:
 
191
            return
 
192
        old_fname = trace_fname + '.old'
 
193
        osutils.rename(trace_fname, old_fname)
 
194
    except OSError:
 
195
        return
 
196
 
 
197
 
 
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.
 
212
 
 
213
    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
 
215
    existing file.
 
216
 
 
217
    This sets the global _brz_log_filename.
 
218
    """
 
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
 
 
246
    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,))
 
269
    # TODO: What should happen if we fail to open the trace file?  Maybe the
 
270
    # objects should be pointed at /dev/null or the equivalent?  Currently
 
271
    # returns None which will cause failures later.
 
272
    return None
 
273
 
 
274
 
 
275
def enable_default_logging():
 
276
    """Configure default logging: messages to stderr and debug to brz.log
 
277
 
 
278
    This should only be called once per process.
 
279
 
 
280
    Non-command-line programs embedding breezy do not need to call this.  They
 
281
    can instead either pass a file to _push_log_file, or act directly on
 
282
    logging.getLogger("brz").
 
283
 
 
284
    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
    """
 
288
    start_time = osutils.format_local_date(_brz_log_start_time,
 
289
                                           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,
 
295
        r'[%(process)5d] %(asctime)s.%(msecs)03d %(levelname)s: %(message)s',
 
296
        r'%Y-%m-%d %H:%M:%S')
 
297
    # after hooking output into brz_log, we also need to attach a stderr
 
298
    # 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
 
302
 
 
303
 
 
304
def push_log_file(to_file, log_format=None, date_format=None):
 
305
    """Intercept log and trace messages and send them to a file.
 
306
 
 
307
    :param to_file: A file-like object to which messages will be sent.
 
308
 
 
309
    :returns: A memento that should be passed to _pop_log_file to restore the
 
310
        previously active logging.
 
311
    """
 
312
    global _trace_file
 
313
    # make a new handler
 
314
    new_handler = EncodedStreamHandler(to_file, "utf-8", level=logging.DEBUG)
 
315
    if log_format is None:
 
316
        log_format = '%(levelname)8s  %(message)s'
 
317
    new_handler.setFormatter(logging.Formatter(log_format, date_format))
 
318
    # save and remove any existing log handlers
 
319
    brz_logger = logging.getLogger('brz')
 
320
    old_handlers = brz_logger.handlers[:]
 
321
    del brz_logger.handlers[:]
 
322
    # set that as the default logger
 
323
    brz_logger.addHandler(new_handler)
 
324
    brz_logger.setLevel(logging.DEBUG)
 
325
    # TODO: check if any changes are needed to the root logger
 
326
    #
 
327
    # TODO: also probably need to save and restore the level on brz_logger.
 
328
    # but maybe we can avoid setting the logger level altogether, and just set
 
329
    # the level on the handler?
 
330
    #
 
331
    # save the old trace file
 
332
    old_trace_file = _trace_file
 
333
    # send traces to the new one
 
334
    _trace_file = to_file
 
335
    return ('log_memento', old_handlers, new_handler, old_trace_file, to_file)
 
336
 
 
337
 
 
338
def pop_log_file(entry):
 
339
    """Undo changes to logging/tracing done by _push_log_file.
 
340
 
 
341
    This flushes, but does not close the trace file (so that anything that was
 
342
    in it is output.
 
343
 
 
344
    Takes the memento returned from _push_log_file."""
 
345
    (magic, old_handlers, new_handler, old_trace_file, new_trace_file) = entry
 
346
    global _trace_file
 
347
    _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
 
351
    # file will likely already be closed underneath.
 
352
    new_handler.close()
 
353
    brz_logger.handlers = old_handlers
 
354
    if new_trace_file is not None:
 
355
        new_trace_file.flush()
 
356
 
 
357
 
 
358
def log_exception_quietly():
 
359
    """Log the last exception to the trace file only.
 
360
 
 
361
    Used for exceptions that occur internally and that may be
 
362
    interesting to developers but not to users.  For example,
 
363
    errors loading plugins.
 
364
    """
 
365
    mutter(traceback.format_exc())
 
366
 
 
367
 
 
368
def set_verbosity_level(level):
 
369
    """Set the verbosity level.
 
370
 
 
371
    :param level: -ve for quiet, 0 for normal, +ve for verbose
 
372
    """
 
373
    global _verbosity_level
 
374
    _verbosity_level = level
 
375
    _update_logging_level(level < 0)
 
376
    ui.ui_factory.be_quiet(level < 0)
 
377
 
 
378
 
 
379
def get_verbosity_level():
 
380
    """Get the verbosity level.
 
381
 
 
382
    See set_verbosity_level() for values.
 
383
    """
 
384
    return _verbosity_level
 
385
 
 
386
 
 
387
def be_quiet(quiet=True):
 
388
    if quiet:
 
389
        set_verbosity_level(-1)
 
390
    else:
 
391
        set_verbosity_level(0)
 
392
 
 
393
 
 
394
def _update_logging_level(quiet=True):
 
395
    """Hide INFO messages if quiet."""
 
396
    if quiet:
 
397
        _brz_logger.setLevel(logging.WARNING)
 
398
    else:
 
399
        _brz_logger.setLevel(logging.INFO)
 
400
 
 
401
 
 
402
def is_quiet():
 
403
    """Is the verbosity level negative?"""
 
404
    return _verbosity_level < 0
 
405
 
 
406
 
 
407
def is_verbose():
 
408
    """Is the verbosity level positive?"""
 
409
    return _verbosity_level > 0
 
410
 
 
411
 
 
412
def debug_memory(message='', short=True):
 
413
    """Write out a memory dump."""
 
414
    if sys.platform == 'win32':
 
415
        from breezy import win32utils
 
416
        win32utils.debug_memory_win32api(message=message, short=short)
 
417
    else:
 
418
        _debug_memory_proc(message=message, short=short)
 
419
 
 
420
 
 
421
_short_fields = ('VmPeak', 'VmSize', 'VmRSS')
 
422
 
 
423
 
 
424
def _debug_memory_proc(message='', short=True):
 
425
    try:
 
426
        status_file = open('/proc/%s/status' % os.getpid(), 'rb')
 
427
    except IOError:
 
428
        return
 
429
    try:
 
430
        status = status_file.read()
 
431
    finally:
 
432
        status_file.close()
 
433
    if message:
 
434
        note(message)
 
435
    for line in status.splitlines():
 
436
        if not short:
 
437
            note(line)
 
438
        else:
 
439
            for field in _short_fields:
 
440
                if line.startswith(field):
 
441
                    note(line)
 
442
                    break
 
443
 
 
444
 
 
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
def report_exception(exc_info, err_file):
 
481
    """Report an exception to err_file (typically stderr) and to brz.log.
 
482
 
 
483
    This will show either a full traceback or a short message as appropriate.
 
484
 
 
485
    :return: The appropriate exit code for this error.
 
486
    """
 
487
    # Log the full traceback to brz.log
 
488
    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")
 
502
        return errors.EXIT_ERROR
 
503
    elif isinstance(exc_object, ImportError) \
 
504
            and str(exc_object).startswith("No module named "):
 
505
        report_user_error(
 
506
            exc_info, err_file,
 
507
            'You may need to install this Python library separately.')
 
508
        return errors.EXIT_ERROR
 
509
    elif not getattr(exc_object, 'internal_error', True):
 
510
        report_user_error(exc_info, err_file)
 
511
        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
 
516
        # Might be nice to catch all of these and show them as something more
 
517
        # specific, but there are too many cases at the moment.
 
518
        report_user_error(exc_info, err_file)
 
519
        return errors.EXIT_ERROR
 
520
    else:
 
521
        report_bug(exc_info, err_file)
 
522
        return errors.EXIT_INTERNAL_ERROR
 
523
 
 
524
 
 
525
def print_exception(exc_info, err_file):
 
526
    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))
 
529
    err_file.write('\n')
 
530
    traceback.print_exception(exc_type, exc_object, exc_tb, file=err_file)
 
531
 
 
532
 
 
533
# TODO: Should these be specially encoding the output?
 
534
def report_user_error(exc_info, err_file, advice=None):
 
535
    """Report to err_file an error that's not an internal error.
 
536
 
 
537
    These don't get a traceback unless -Derror was given.
 
538
 
 
539
    :param exc_info: 3-tuple from sys.exc_info()
 
540
    :param advice: Extra advice to the user to be printed following the
 
541
        exception.
 
542
    """
 
543
    err_file.write(("brz: ERROR: %s\n" % (str(exc_info[1]),)))
 
544
    if advice:
 
545
        err_file.write(("%s\n" % advice))
 
546
 
 
547
 
 
548
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
 
551
    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.