logging: add "only_once=False" param to logger.info/warning/etc calls
very basic form of opt-in rate-limiting of log spam
This commit is contained in:
@@ -58,17 +58,23 @@ def _ensure_translation_keeps_format_string_syntax_similar(translator):
|
||||
try:
|
||||
parsed2 = list(sf.parse(translation))
|
||||
except ValueError: # malformed format string in translation
|
||||
_logger.info(f"rejected translation string: failed to parse. original={msg!r}. {translation=!r}")
|
||||
_logger.warning(
|
||||
f"rejected translation string: failed to parse. original={msg!r}. {translation=!r}",
|
||||
only_once=True)
|
||||
return msg
|
||||
# num of replacement fields must match:
|
||||
if len(parsed1) != len(parsed2):
|
||||
_logger.info(f"rejected translation string: num replacement fields mismatch. original={msg!r}. {translation=!r}")
|
||||
_logger.warning(
|
||||
f"rejected translation string: num replacement fields mismatch. original={msg!r}. {translation=!r}",
|
||||
only_once=True)
|
||||
return msg
|
||||
# set of "field_name"s must not change. (re-ordering is explicitly allowed):
|
||||
field_names1 = set(tupl[1] for tupl in parsed1)
|
||||
field_names2 = set(tupl[1] for tupl in parsed2)
|
||||
if field_names1 != field_names2:
|
||||
_logger.info(f"rejected translation string: set of field_names mismatch. original={msg!r}. {translation=!r}")
|
||||
_logger.warning(
|
||||
f"rejected translation string: set of field_names mismatch. original={msg!r}. {translation=!r}",
|
||||
only_once=True)
|
||||
return msg
|
||||
# checks done.
|
||||
return translation
|
||||
|
||||
@@ -9,9 +9,10 @@ import sys
|
||||
import pathlib
|
||||
import os
|
||||
import platform
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from typing import Optional, TYPE_CHECKING, Set
|
||||
import copy
|
||||
import subprocess
|
||||
import hashlib
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .simple_config import SimpleConfig
|
||||
@@ -190,6 +191,24 @@ def _process_verbosity_log_levels(verbosity):
|
||||
raise Exception(f"invalid log filter: {filt}")
|
||||
|
||||
|
||||
class _CustomLogger(logging.getLoggerClass()):
|
||||
def __init__(self, name, *args, **kwargs):
|
||||
super().__init__(name, *args, **kwargs)
|
||||
self.msg_hashes_seen = set() # type: Set[bytes]
|
||||
# ^ note: size grows without bounds, but only for log lines using "only_once".
|
||||
|
||||
def _log(self, level, msg: str, *args, only_once: bool = False, **kwargs) -> None:
|
||||
"""Overridden to add 'only_once' arg to logger.debug()/logger.info()/logger.warning()/etc."""
|
||||
if only_once: # if set, this logger will only log this msg a single time during its lifecycle
|
||||
msg_hash = hashlib.sha256(msg.encode("utf-8")).digest()
|
||||
if msg_hash in self.msg_hashes_seen:
|
||||
return
|
||||
self.msg_hashes_seen.add(msg_hash)
|
||||
super()._log(level, msg, *args, **kwargs)
|
||||
|
||||
logging.setLoggerClass(_CustomLogger)
|
||||
|
||||
|
||||
# enable logs universally (including for other libraries)
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(logging.WARNING)
|
||||
@@ -216,7 +235,7 @@ electrum_logger.setLevel(logging.DEBUG)
|
||||
|
||||
# --- External API
|
||||
|
||||
def get_logger(name: str) -> logging.Logger:
|
||||
def get_logger(name: str) -> _CustomLogger:
|
||||
if name.startswith("electrum."):
|
||||
name = name[9:]
|
||||
return electrum_logger.getChild(name)
|
||||
|
||||
Reference in New Issue
Block a user