URI: 
       tMerge pull request #5321 from SomberNight/logging_shortcut_filtering_20190507 - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 2a6b02d43e7bdd65538a42291a778a7ece6c663f
   DIR parent 92260a798afb94996041b3544c81323dba59b6e3
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Wed,  8 May 2019 11:41:32 +0200
       
       Merge pull request #5321 from SomberNight/logging_shortcut_filtering_20190507
       
       logging: '-V' cli option can blacklist/whitelist classes with short names
       Diffstat:
         M electrum/commands.py                |       3 ++-
         M electrum/interface.py               |       2 ++
         M electrum/logging.py                 |      90 +++++++++++++++++++++++++++++---
         M electrum/network.py                 |       2 ++
         M electrum/plugin.py                  |       2 ++
         M electrum/util.py                    |       2 ++
         M electrum/wallet.py                  |       1 +
       
       7 files changed, 95 insertions(+), 7 deletions(-)
       ---
   DIR diff --git a/electrum/commands.py b/electrum/commands.py
       t@@ -934,7 +934,8 @@ def add_network_options(parser):
        
        def add_global_options(parser):
            group = parser.add_argument_group('global options')
       -    group.add_argument("-v", dest="verbosity", help="Set verbosity filter", default='')
       +    group.add_argument("-v", dest="verbosity", help="Set verbosity (log levels)", default='')
       +    group.add_argument("-V", dest="verbosity_shortcuts", help="Set verbosity (shortcut-filter list)", default='')
            group.add_argument("-D", "--dir", dest="electrum_path", help="electrum directory")
            group.add_argument("-P", "--portable", action="store_true", dest="portable", default=False, help="Use local 'electrum_data' directory")
            group.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path")
   DIR diff --git a/electrum/interface.py b/electrum/interface.py
       t@@ -179,6 +179,8 @@ def serialize_server(host: str, port: Union[str, int], protocol: str) -> str:
        
        class Interface(Logger):
        
       +    LOGGING_SHORTCUT = 'i'
       +
            def __init__(self, network: 'Network', server: str, proxy: Optional[dict]):
                self.ready = asyncio.Future()
                self.got_disconnected = asyncio.Future()
   DIR diff --git a/electrum/logging.py b/electrum/logging.py
       t@@ -33,7 +33,11 @@ class LogFormatterForConsole(logging.Formatter):
        
            def format(self, record):
                record = _shorten_name_of_logrecord(record)
       -        return super().format(record)
       +        text = super().format(record)
       +        shortcut = getattr(record, 'custom_shortcut', None)
       +        if shortcut:
       +            text = text[:1] + f"/{shortcut}" + text[1:]
       +        return text
        
        
        # try to make console log lines short... no timestamp, short levelname, no "electrum."
       t@@ -93,11 +97,15 @@ def _configure_file_logging(log_directory: pathlib.Path):
            root_logger.addHandler(file_handler)
        
        
       -def _configure_verbosity(config):
       -    verbosity = config.get('verbosity')
       -    if not verbosity:
       +def _configure_verbosity(*, verbosity, verbosity_shortcuts):
       +    if not verbosity and not verbosity_shortcuts:
                return
            console_stderr_handler.setLevel(logging.DEBUG)
       +    _process_verbosity_log_levels(verbosity)
       +    _process_verbosity_filter_shortcuts(verbosity_shortcuts)
       +
       +
       +def _process_verbosity_log_levels(verbosity):
            if verbosity == '*' or not isinstance(verbosity, str):
                return
            # example verbosity:
       t@@ -118,6 +126,65 @@ def _configure_verbosity(config):
                    raise Exception(f"invalid log filter: {filt}")
        
        
       +def _process_verbosity_filter_shortcuts(verbosity_shortcuts):
       +    if not isinstance(verbosity_shortcuts, str):
       +        return
       +    if len(verbosity_shortcuts) < 1:
       +        return
       +    # depending on first character being '^', either blacklist or whitelist
       +    is_blacklist = verbosity_shortcuts[0] == '^'
       +    if is_blacklist:
       +        filters = verbosity_shortcuts[1:]
       +    else:  # whitelist
       +        filters = verbosity_shortcuts[0:]
       +    filt = ShortcutFilteringFilter(is_blacklist=is_blacklist, filters=filters)
       +    # apply filter directly (and only!) on stderr handler
       +    # note that applying on one of the root loggers directly would not work,
       +    # see https://docs.python.org/3/howto/logging.html#logging-flow
       +    console_stderr_handler.addFilter(filt)
       +
       +
       +class ShortcutInjectingFilter(logging.Filter):
       +
       +    def __init__(self, *, shortcut: Optional[str]):
       +        super().__init__()
       +        self.__shortcut = shortcut
       +
       +    def filter(self, record):
       +        record.custom_shortcut = self.__shortcut
       +        return True
       +
       +
       +class ShortcutFilteringFilter(logging.Filter):
       +
       +    def __init__(self, *, is_blacklist: bool, filters: str):
       +        super().__init__()
       +        self.__is_blacklist = is_blacklist
       +        self.__filters = filters
       +
       +    def filter(self, record):
       +        # all errors are let through
       +        if record.levelno >= logging.ERROR:
       +            return True
       +        # the logging module itself is let through
       +        if record.name == __name__:
       +            return True
       +        # do filtering
       +        shortcut = getattr(record, 'custom_shortcut', None)
       +        if self.__is_blacklist:
       +            if shortcut is None:
       +                return True
       +            if shortcut in self.__filters:
       +                return False
       +            return True
       +        else:  # whitelist
       +            if shortcut is None:
       +                return False
       +            if shortcut in self.__filters:
       +                return True
       +            return False
       +
       +
        # --- External API
        
        def get_logger(name: str) -> logging.Logger:
       t@@ -131,6 +198,11 @@ _logger.setLevel(logging.INFO)
        
        
        class Logger:
       +
       +    # Single character short "name" for this class.
       +    # Can be used for filtering log lines. Does not need to be unique.
       +    LOGGING_SHORTCUT = None  # type: Optional[str]
       +
            def __init__(self):
                self.logger = self.__get_logger_for_obj()
        
       t@@ -146,14 +218,19 @@ class Logger:
                    raise Exception("diagnostic name not yet available?") from e
                if diag_name:
                    name += f".[{diag_name}]"
       -        return get_logger(name)
       +        logger = get_logger(name)
       +        if self.LOGGING_SHORTCUT:
       +            logger.addFilter(ShortcutInjectingFilter(shortcut=self.LOGGING_SHORTCUT))
       +        return logger
        
            def diagnostic_name(self):
                return ''
        
        
        def configure_logging(config):
       -    _configure_verbosity(config)
       +    verbosity = config.get('verbosity')
       +    verbosity_shortcuts = config.get('verbosity_shortcuts')
       +    _configure_verbosity(verbosity=verbosity, verbosity_shortcuts=verbosity_shortcuts)
        
            is_android = 'ANDROID_DATA' in os.environ
            if is_android or config.get('disablefilelogging'):
       t@@ -169,6 +246,7 @@ def configure_logging(config):
            _logger.info(f"Electrum version: {ELECTRUM_VERSION} - https://electrum.org - https://github.com/spesmilo/electrum")
            _logger.info(f"Python version: {sys.version}. On platform: {describe_os_version()}")
            _logger.info(f"Logging to file: {str(_logfile_path)}")
       +    _logger.info(f"Log filters: verbosity {repr(verbosity)}, verbosity_shortcuts {repr(verbosity_shortcuts)}")
        
        
        def get_logfile_path() -> Optional[pathlib.Path]:
   DIR diff --git a/electrum/network.py b/electrum/network.py
       t@@ -224,6 +224,8 @@ class Network(Logger):
            servers, each connected socket is handled by an Interface() object.
            """
        
       +    LOGGING_SHORTCUT = 'n'
       +
            def __init__(self, config: SimpleConfig=None):
                global INSTANCE
                INSTANCE = self
   DIR diff --git a/electrum/plugin.py b/electrum/plugin.py
       t@@ -48,6 +48,8 @@ hooks = {}
        
        class Plugins(DaemonThread):
        
       +    LOGGING_SHORTCUT = 'p'
       +
            @profiler
            def __init__(self, config: SimpleConfig, gui_name):
                DaemonThread.__init__(self)
   DIR diff --git a/electrum/util.py b/electrum/util.py
       t@@ -260,6 +260,8 @@ class DebugMem(ThreadJob):
        class DaemonThread(threading.Thread, Logger):
            """ daemon thread that terminates cleanly """
        
       +    LOGGING_SHORTCUT = 'd'
       +
            def __init__(self):
                threading.Thread.__init__(self)
                Logger.__init__(self)
   DIR diff --git a/electrum/wallet.py b/electrum/wallet.py
       t@@ -201,6 +201,7 @@ class Abstract_Wallet(AddressSynchronizer):
            Completion states (watching-only, single account, no seed, etc) are handled inside classes.
            """
        
       +    LOGGING_SHORTCUT = 'w'
            max_change_outputs = 3
            gap_limit_for_change = 6