1
0

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:
SomberNight
2025-07-07 14:25:20 +00:00
parent d8014ac068
commit 0f1a282316
2 changed files with 30 additions and 5 deletions

View File

@@ -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

View File

@@ -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)