~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/trace.py

(gz) Making logging non-ascii strings more robust with new
 EncodedStreamHandler class (Martin Packman)

Show diffs side-by-side

added added

removed removed

Lines of Context:
54
54
# increased cost of logging.py is not so bad, and we could standardize on
55
55
# that.
56
56
 
57
 
import codecs
58
57
import logging
59
58
import os
60
59
import sys
116
115
 
117
116
    :return: None
118
117
    """
119
 
    # FIXME note always emits utf-8, regardless of the terminal encoding
120
 
    #
121
118
    # FIXME: clearing the ui and then going through the abstract logging
122
119
    # framework is whack; we should probably have a logging Handler that
123
120
    # deals with terminal output if needed.
308
305
    """
309
306
    start_time = osutils.format_local_date(_bzr_log_start_time,
310
307
                                           timezone='local')
311
 
    # create encoded wrapper around stderr
312
308
    bzr_log_file = _open_bzr_log()
313
309
    if bzr_log_file is not None:
314
310
        bzr_log_file.write(start_time.encode('utf-8') + '\n')
317
313
        r'%Y-%m-%d %H:%M:%S')
318
314
    # after hooking output into bzr_log, we also need to attach a stderr
319
315
    # handler, writing only at level info and with encoding
320
 
    term_encoding = osutils.get_terminal_encoding()
321
 
    writer_factory = codecs.getwriter(term_encoding)
322
 
    encoded_stderr = writer_factory(sys.stderr, errors='replace')
323
 
    stderr_handler = logging.StreamHandler(encoded_stderr)
324
 
    stderr_handler.setLevel(logging.INFO)
 
316
    stderr_handler = EncodedStreamHandler(sys.stderr,
 
317
        osutils.get_terminal_encoding(), 'replace', level=logging.INFO)
325
318
    logging.getLogger('bzr').addHandler(stderr_handler)
326
319
    return memento
327
320
 
336
329
    """
337
330
    global _trace_file
338
331
    # make a new handler
339
 
    new_handler = logging.StreamHandler(to_file)
340
 
    new_handler.setLevel(logging.DEBUG)
 
332
    new_handler = EncodedStreamHandler(to_file, "utf-8", level=logging.DEBUG)
341
333
    if log_format is None:
342
334
        log_format = '%(levelname)8s  %(message)s'
343
335
    new_handler.setFormatter(logging.Formatter(log_format, date_format))
597
589
        _trace_file.flush()
598
590
 
599
591
 
 
592
class EncodedStreamHandler(logging.Handler):
 
593
    """Robustly write logging events to a stream using the specified encoding
 
594
 
 
595
    Messages are expected to be formatted to unicode, but UTF-8 byte strings
 
596
    are also accepted. An error during formatting or a str message in another
 
597
    encoding will be quitely noted as an error in the Bazaar log file.
 
598
 
 
599
    The stream is not closed so sys.stdout or sys.stderr may be passed.
 
600
    """
 
601
 
 
602
    def __init__(self, stream, encoding=None, errors='strict', level=0):
 
603
        logging.Handler.__init__(self, level)
 
604
        self.stream = stream
 
605
        if encoding is None:
 
606
            encoding = getattr(stream, "encoding", "ascii")
 
607
        self.encoding = encoding
 
608
        self.errors = errors
 
609
 
 
610
    def flush(self):
 
611
        flush = getattr(self.stream, "flush", None)
 
612
        if flush is not None:
 
613
            flush()
 
614
 
 
615
    def emit(self, record):
 
616
        try:
 
617
            line = self.format(record)
 
618
            if not isinstance(line, unicode):
 
619
                line = line.decode("utf-8")
 
620
            self.stream.write(line.encode(self.encoding, self.errors) + "\n")
 
621
        except Exception:
 
622
            log_exception_quietly()
 
623
            # Try saving the details that would have been logged in some form
 
624
            msg = args = "<Unformattable>"
 
625
            try:
 
626
                msg = repr(record.msg).encode("ascii")
 
627
                args = repr(record.args).encode("ascii")
 
628
            except Exception:
 
629
                pass
 
630
            # Using mutter() bypasses the logging module and writes directly
 
631
            # to the file so there's no danger of getting into a loop here.
 
632
            mutter("Logging record unformattable: %s %% %s", msg, args)
 
633
 
 
634
 
600
635
class Config(object):
601
636
    """Configuration of message tracing in bzrlib.
602
637