From 43dfd70b0a2a29cfbebb641e361bb459d684cf0a Mon Sep 17 00:00:00 2001 From: f4rceful Date: Mon, 1 Jun 2026 05:44:27 -0700 Subject: [PATCH] fix: rotate log files instead of growing without bound (#885) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RotatingFileHandler only rotates when backupCount >= 1: CPython's doRollover skips the whole rotation block when backupCount == 0, so maxBytes is silently ignored and the active log grows forever. The tray hardcoded backupCount=0 and the CLI defaulted --log-backups to 0, so the configured size limit never applied — users reported logs ballooning to hundreds of MB (#885). Centralize handler creation in utils/logging_setup.build_log_handler, which forces backupCount >= 1, and use it from both the tray and CLI log paths. Default --log-backups is now 1. --- proxy/tg_ws_proxy.py | 13 +++++++------ utils/logging_setup.py | 39 +++++++++++++++++++++++++++++++++++++++ utils/tray_common.py | 8 ++------ 3 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 utils/logging_setup.py diff --git a/proxy/tg_ws_proxy.py b/proxy/tg_ws_proxy.py index daee01e5..3869749c 100644 --- a/proxy/tg_ws_proxy.py +++ b/proxy/tg_ws_proxy.py @@ -568,8 +568,9 @@ def main(): help='Log to file with rotation (default: stderr only)') ap.add_argument('--log-max-mb', type=float, default=5, metavar='MB', help='Max log file size in MB before rotation (default 5)') - ap.add_argument('--log-backups', type=int, default=0, metavar='N', - help='Number of rotated log files to keep (default 0)') + ap.add_argument('--log-backups', type=int, default=1, metavar='N', + help='Number of rotated log files to keep (min 1; ' + 'rotation needs at least one backup to bound size)') ap.add_argument('--buf-kb', type=int, default=256, metavar='KB', help='Socket send/recv buffer size in KB (default 256)') ap.add_argument('--pool-size', type=int, default=4, metavar='N', @@ -640,11 +641,11 @@ def main(): root.addHandler(console) if args.log_file: - fh = logging.handlers.RotatingFileHandler( + from utils.logging_setup import build_log_handler + fh = build_log_handler( args.log_file, - maxBytes=max(32 * 1024, int(args.log_max_mb * 1024 * 1024)), - backupCount=max(0, args.log_backups), - encoding='utf-8', + log_max_mb=args.log_max_mb, + backups=args.log_backups, ) fh.setFormatter(log_fmt) root.addHandler(fh) diff --git a/utils/logging_setup.py b/utils/logging_setup.py new file mode 100644 index 00000000..b1dade9c --- /dev/null +++ b/utils/logging_setup.py @@ -0,0 +1,39 @@ +"""Shared construction of the rotating log file handler. + +Centralizes the rotation invariant so both the tray and the CLI log paths +behave identically and the file can never grow without bound (issue #885). + +A ``RotatingFileHandler`` only rotates when ``backupCount >= 1``: CPython's +``doRollover`` skips the entire rotation block when ``backupCount == 0``, so +``maxBytes`` is silently ignored and the active file grows forever. We force +at least one backup here regardless of caller input. +""" + +from __future__ import annotations + +import logging.handlers + + +_MIN_BYTES = 32 * 1024 +_MIN_BACKUPS = 1 + + +def build_log_handler( + path: str, + log_max_mb: float = 5, + backups: int = 1, +) -> logging.handlers.RotatingFileHandler: + """Create a RotatingFileHandler that actually rotates. + + ``backups`` is clamped to at least 1 so rotation is always active, and + ``maxBytes`` keeps a small floor so a misconfigured tiny size can't cause + rotation on every line. + """ + max_bytes = max(_MIN_BYTES, int(log_max_mb * 1024 * 1024)) + backup_count = max(_MIN_BACKUPS, int(backups)) + return logging.handlers.RotatingFileHandler( + path, + maxBytes=max_bytes, + backupCount=backup_count, + encoding="utf-8", + ) diff --git a/utils/tray_common.py b/utils/tray_common.py index e553a35a..6ae2a5b8 100644 --- a/utils/tray_common.py +++ b/utils/tray_common.py @@ -17,6 +17,7 @@ from proxy import __version__, get_link_host, parse_dc_ip_list, proxy_config, coerce_domain_list from proxy.tg_ws_proxy import _run from utils.default_config import default_tray_config +from utils.logging_setup import build_log_handler log = logging.getLogger("tg-ws-tray") @@ -155,12 +156,7 @@ def setup_logging(verbose: bool = False, log_max_mb: float = 5) -> None: root.setLevel(level) logging.getLogger('asyncio').setLevel(logging.WARNING) - fh = logging.handlers.RotatingFileHandler( - str(LOG_FILE), - maxBytes=max(32 * 1024, int(log_max_mb * 1024 * 1024)), - backupCount=0, - encoding="utf-8", - ) + fh = build_log_handler(str(LOG_FILE), log_max_mb=log_max_mb, backups=1) fh.setLevel(logging.DEBUG) fh.setFormatter(logging.Formatter(_LOG_FMT_FILE, datefmt="%Y-%m-%d %H:%M:%S")) root.addHandler(fh)