/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: Jelmer Vernooij
  • Date: 2020-05-06 02:13:25 UTC
  • mfrom: (7490.7.21 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200506021325-awbmmqu1zyorz7sj
Merge 3.1 branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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: to stderr, and to ~/.brz.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.
 
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
29
 
30
 
~/.brz.log gets all messages, and full tracebacks for uncaught exceptions.
 
30
`brz.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
 
45
45
form.
46
46
"""
47
47
 
48
 
from __future__ import absolute_import
49
 
 
50
48
# FIXME: Unfortunately it turns out that python's logging module
51
49
# is quite expensive, even when the message is not printed by any handlers.
52
50
# We should perhaps change back to just simply doing it here.
57
55
# that.
58
56
 
59
57
import errno
 
58
from io import StringIO
60
59
import logging
61
60
import os
62
61
import sys
64
63
 
65
64
from .lazy_import import lazy_import
66
65
lazy_import(globals(), """
67
 
import locale
68
66
import tempfile
69
67
import traceback
70
68
""")
73
71
 
74
72
lazy_import(globals(), """
75
73
from breezy import (
 
74
    bedding,
76
75
    debug,
77
 
    errors,
78
76
    osutils,
79
77
    ui,
80
78
    )
81
79
""")
82
 
 
83
 
from .sixish import (
84
 
    BytesIO,
85
 
    PY3,
86
 
    StringIO,
87
 
    text_type,
 
80
from . import (
 
81
    errors,
88
82
    )
89
83
 
90
84
 
98
92
# than push/pop_log_file.
99
93
_trace_file = None
100
94
 
101
 
# Absolute path for ~/.brz.log.  Not changed even if the log/trace output is
 
95
# Absolute path for brz.log.  Not changed even if the log/trace output is
102
96
# redirected elsewhere.  Used to show the location in --version.
103
97
_brz_log_filename = None
104
98
 
138
132
    _brz_logger.error(*args, **kwargs)
139
133
 
140
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
 
141
145
def mutter(fmt, *args):
142
146
    if _trace_file is None:
143
147
        return
146
150
    if (getattr(_trace_file, 'closed', None) is not None) and _trace_file.closed:
147
151
        return
148
152
 
149
 
    if isinstance(fmt, text_type):
150
 
        fmt = fmt.encode('utf8')
 
153
    # Let format strings be specified as ascii bytes to help Python 2
 
154
    if isinstance(fmt, bytes):
 
155
        fmt = fmt.decode('ascii', 'replace')
151
156
 
152
 
    if len(args) > 0:
153
 
        # It seems that if we do ascii % (unicode, ascii) we can
154
 
        # get a unicode cannot encode ascii error, so make sure that "fmt"
155
 
        # is a unicode string
156
 
        real_args = []
157
 
        for arg in args:
158
 
            if isinstance(arg, text_type):
159
 
                arg = arg.encode('utf8')
160
 
            real_args.append(arg)
161
 
        out = fmt % tuple(real_args)
 
157
    if args:
 
158
        out = fmt % args
162
159
    else:
163
160
        out = fmt
164
161
    now = time.time()
165
 
    out = b'%0.3f  %s\n' % (now - _brz_log_start_time, out)
166
 
    _trace_file.write(out)
 
162
    out = '%0.3f  %s\n' % (now - _brz_log_start_time, out)
 
163
    _trace_file.write(out.encode('utf-8'))
167
164
    # there's no explicit flushing; the file is typically line buffered.
168
165
 
169
166
 
199
196
 
200
197
 
201
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
    """
202
204
    brz_log = osutils.path_from_environ('BRZ_LOG')
203
205
    if brz_log:
204
206
        return brz_log
205
 
    home = osutils.path_from_environ('BRZ_HOME')
206
 
    if home is None:
207
 
        # GZ 2012-02-01: Logging to the home dir is bad, but XDG is unclear
208
 
        #                over what would be better. On windows, bug 240550
209
 
        #                suggests LOCALAPPDATA be used instead.
210
 
        home = osutils._get_home_dir()
211
 
    return os.path.join(home, '.brz.log')
 
207
    return os.path.join(bedding.cache_dir(), 'brz.log')
212
208
 
213
209
 
214
210
def _open_brz_log():
215
 
    """Open the .brz.log trace file.
 
211
    """Open the brz.log trace file.
216
212
 
217
213
    If the log is more than a particular length, the old file is renamed to
218
 
    .brz.log.old and a new file is started.  Otherwise, we append to the
 
214
    brz.log.old and a new file is started.  Otherwise, we append to the
219
215
    existing file.
220
216
 
221
217
    This sets the global _brz_log_filename.
245
241
            else:
246
242
                osutils.copy_ownership_from_path(filename)
247
243
                break
248
 
        return os.fdopen(fd, 'ab', 0) # unbuffered
249
 
 
250
 
 
251
 
    _brz_log_filename = _get_brz_log_filename()
252
 
    _rollover_trace_maybe(_brz_log_filename)
 
244
        return os.fdopen(fd, 'ab', 0)  # unbuffered
 
245
 
253
246
    try:
 
247
        _brz_log_filename = _get_brz_log_filename()
 
248
        _rollover_trace_maybe(_brz_log_filename)
 
249
 
254
250
        brz_log_file = _open_or_create_log_file(_brz_log_filename)
255
251
        brz_log_file.write(b'\n')
256
252
        if brz_log_file.tell() <= 2:
257
 
            brz_log_file.write(b"this is a debug log for diagnosing/reporting problems in brz\n")
258
 
            brz_log_file.write(b"you can delete or truncate this file, or include sections in\n")
259
 
            brz_log_file.write(b"bug reports to https://bugs.launchpad.net/brz/+filebug\n\n")
 
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")
260
259
 
261
260
        return brz_log_file
262
261
 
274
273
 
275
274
 
276
275
def enable_default_logging():
277
 
    """Configure default logging: messages to stderr and debug to .brz.log
 
276
    """Configure default logging: messages to stderr and debug to brz.log
278
277
 
279
278
    This should only be called once per process.
280
279
 
291
290
    brz_log_file = _open_brz_log()
292
291
    if brz_log_file is not None:
293
292
        brz_log_file.write(start_time.encode('utf-8') + b'\n')
294
 
    memento = push_log_file(brz_log_file,
 
293
    memento = push_log_file(
 
294
        brz_log_file,
295
295
        r'[%(process)5d] %(asctime)s.%(msecs)03d %(levelname)s: %(message)s',
296
296
        r'%Y-%m-%d %H:%M:%S')
297
297
    # after hooking output into brz_log, we also need to attach a stderr
298
298
    # handler, writing only at level info and with encoding
299
 
    stderr_handler = EncodedStreamHandler(sys.stderr,
300
 
        osutils.get_terminal_encoding(), 'replace', level=logging.INFO)
 
299
    stderr_handler = logging.StreamHandler(stream=sys.stderr)
301
300
    logging.getLogger('brz').addHandler(stderr_handler)
302
301
    return memento
303
302
 
333
332
    old_trace_file = _trace_file
334
333
    # send traces to the new one
335
334
    _trace_file = to_file
336
 
    result = new_handler, _trace_file
337
335
    return ('log_memento', old_handlers, new_handler, old_trace_file, to_file)
338
336
 
339
337
 
422
420
 
423
421
_short_fields = ('VmPeak', 'VmSize', 'VmRSS')
424
422
 
 
423
 
425
424
def _debug_memory_proc(message='', short=True):
426
425
    try:
427
 
        status_file = file('/proc/%s/status' % os.getpid(), 'rb')
 
426
        status_file = open('/proc/%s/status' % os.getpid(), 'rb')
428
427
    except IOError:
429
428
        return
430
429
    try:
442
441
                    note(line)
443
442
                    break
444
443
 
 
444
 
445
445
def _dump_memory_usage(err_file):
446
446
    try:
447
447
        try:
453
453
        except ImportError:
454
454
            err_file.write("Dumping memory requires meliae module.\n")
455
455
            log_exception_quietly()
456
 
        except:
 
456
        except BaseException:
457
457
            err_file.write("Exception while dumping memory.\n")
458
458
            log_exception_quietly()
459
459
    finally:
478
478
 
479
479
 
480
480
def report_exception(exc_info, err_file):
481
 
    """Report an exception to err_file (typically stderr) and to .brz.log.
 
481
    """Report an exception to err_file (typically stderr) and to brz.log.
482
482
 
483
483
    This will show either a full traceback or a short message as appropriate.
484
484
 
485
485
    :return: The appropriate exit code for this error.
486
486
    """
487
 
    # Log the full traceback to ~/.brz.log
 
487
    # Log the full traceback to brz.log
488
488
    log_exception_quietly()
489
489
    if 'error' in debug.debug_flags:
490
490
        print_exception(exc_info, err_file)
501
501
            err_file.write("Use -Dmem_dump to dump memory to a file.\n")
502
502
        return errors.EXIT_ERROR
503
503
    elif isinstance(exc_object, ImportError) \
504
 
        and str(exc_object).startswith("No module named "):
505
 
        report_user_error(exc_info, err_file,
 
504
            and str(exc_object).startswith("No module named "):
 
505
        report_user_error(
 
506
            exc_info, err_file,
506
507
            'You may need to install this Python library separately.')
507
508
        return errors.EXIT_ERROR
508
509
    elif not getattr(exc_object, 'internal_error', True):
539
540
    :param advice: Extra advice to the user to be printed following the
540
541
        exception.
541
542
    """
542
 
    err_file.write("brz: ERROR: %s\n" % (exc_info[1],))
 
543
    err_file.write(("brz: ERROR: %s\n" % (str(exc_info[1]),)))
543
544
    if advice:
544
 
        err_file.write("%s\n" % advice)
 
545
        err_file.write(("%s\n" % advice))
545
546
 
546
547
 
547
548
def report_bug(exc_info, err_file):
555
556
    try:
556
557
        sys.stdout.flush()
557
558
        sys.stderr.flush()
558
 
    except ValueError as e:
 
559
    except ValueError:
559
560
        # On Windows, I get ValueError calling stdout.flush() on a closed
560
561
        # handle
561
562
        pass
599
600
 
600
601
    def emit(self, record):
601
602
        try:
602
 
            if not isinstance(record.msg, text_type):
 
603
            if not isinstance(record.msg, str):
603
604
                msg = record.msg.decode("utf-8")
604
 
                if PY3:
605
 
                    record.msg = msg
 
605
                record.msg = msg
606
606
            line = self.format(record)
607
 
            if not isinstance(line, text_type):
 
607
            if not isinstance(line, str):
608
608
                line = line.decode("utf-8")
609
609
            self.stream.write(line.encode(self.encoding, self.errors) + b"\n")
610
610
        except Exception:
612
612
            # Try saving the details that would have been logged in some form
613
613
            msg = args = "<Unformattable>"
614
614
            try:
615
 
                msg = repr(record.msg).encode("ascii", "backslashescape")
616
 
                args = repr(record.args).encode("ascii", "backslashescape")
 
615
                msg = repr(record.msg)
 
616
                args = repr(record.args)
617
617
            except Exception:
618
618
                pass
619
619
            # Using mutter() bypasses the logging module and writes directly
630
630
    """
631
631
 
632
632
    def __enter__(self):
633
 
        return self # This is bound to the 'as' clause in a with statement.
 
633
        return self  # This is bound to the 'as' clause in a with statement.
634
634
 
635
635
    def __exit__(self, exc_type, exc_val, exc_tb):
636
 
        return False # propogate exceptions.
 
636
        return False  # propogate exceptions.
637
637
 
638
638
 
639
639
class DefaultConfig(Config):
645
645
    def __enter__(self):
646
646
        self._original_filename = _brz_log_filename
647
647
        self._original_state = enable_default_logging()
648
 
        return self # This is bound to the 'as' clause in a with statement.
 
648
        return self  # This is bound to the 'as' clause in a with statement.
649
649
 
650
650
    def __exit__(self, exc_type, exc_val, exc_tb):
651
651
        pop_log_file(self._original_state)
652
652
        global _brz_log_filename
653
653
        _brz_log_filename = self._original_filename
654
 
        return False # propogate exceptions.
 
654
        return False  # propogate exceptions.