diff --git a/crypto_monitor_binance/app.py b/crypto_monitor_binance/app.py index fe2ffda..dda6c43 100644 --- a/crypto_monitor_binance/app.py +++ b/crypto_monitor_binance/app.py @@ -9968,5 +9968,8 @@ _purge_key_monitors_if_full_margin() # 启动 if __name__ == "__main__": + from lib.common.flask_access_log_lib import silence_werkzeug_access_log + + silence_werkzeug_access_log() threading.Thread(target=background_task, daemon=True).start() app.run(host=HOST, port=PORT, debug=DEBUG, threaded=True) diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py index 15a8dd5..97ab581 100644 --- a/crypto_monitor_gate/app.py +++ b/crypto_monitor_gate/app.py @@ -9844,5 +9844,8 @@ _purge_key_monitors_if_full_margin() # 启动 if __name__ == "__main__": + from lib.common.flask_access_log_lib import silence_werkzeug_access_log + + silence_werkzeug_access_log() threading.Thread(target=background_task, daemon=True).start() app.run(host=HOST, port=PORT, debug=DEBUG, threaded=True) diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index e4fca51..5fe185b 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -9564,5 +9564,8 @@ _purge_key_monitors_if_full_margin() # 启动 if __name__ == "__main__": + from lib.common.flask_access_log_lib import silence_werkzeug_access_log + + silence_werkzeug_access_log() threading.Thread(target=background_task, daemon=True).start() app.run(host=HOST, port=PORT, debug=DEBUG, threaded=True) diff --git a/deploy/pm2_log_policy.sh b/deploy/pm2_log_policy.sh new file mode 100644 index 0000000..5d084a6 --- /dev/null +++ b/deploy/pm2_log_policy.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# PM2 日志策略:关灌屏后的兜底轮转 — 按天保留 3 天,单文件过大也切分. +# 用法(root): +# bash deploy/pm2_log_policy.sh +# bash deploy/pm2_log_policy.sh --clean-only +set -euo pipefail + +PM2_LOG_DIR="${PM2_LOG_DIR:-/root/.pm2/logs}" +LOGROTATE_CONF="${LOGROTATE_CONF:-/etc/logrotate.d/crypto_monitor_pm2}" +RETENTION_DAYS="${RETENTION_DAYS:-3}" +MAXSIZE="${MAXSIZE:-20M}" +CLEAN_ONLY=0 +if [[ "${1:-}" == "--clean-only" ]]; then + CLEAN_ONLY=1 +fi + +echo ">>> PM2 log dir: ${PM2_LOG_DIR}" +if [[ ! -d "${PM2_LOG_DIR}" ]]; then + echo "目录不存在,跳过" + exit 0 +fi + +echo ">>> truncate oversized active logs (>20MB) and drop stale files older than ${RETENTION_DAYS}d" +# 当前正在写的大文件用 truncate 清空内容(copytruncate/空写兼容 PM2 仍持有 fd) +find "${PM2_LOG_DIR}" -type f -name '*.log' -size +20M -print -exec truncate -s 0 {} \; +# 过期归档/旧编号日志直接删 +find "${PM2_LOG_DIR}" -type f \( -name '*.log' -o -name '*.log.gz' -o -name '*.log.[0-9]*' \) -mtime +"${RETENTION_DAYS}" -print -delete || true + +if [[ "${CLEAN_ONLY}" -eq 1 ]]; then + echo ">>> --clean-only done" + du -sh "${PM2_LOG_DIR}" || true + exit 0 +fi + +echo ">>> install logrotate: ${LOGROTATE_CONF} (daily, rotate ${RETENTION_DAYS}, maxsize ${MAXSIZE})" +cat > "${LOGROTATE_CONF}" </dev/null 2>&1; then + logrotate -f "${LOGROTATE_CONF}" || true +else + echo "warn: logrotate 未安装,仅完成清理;请 apt install logrotate" +fi + +echo ">>> size after" +du -sh "${PM2_LOG_DIR}" || true +ls -lah "${PM2_LOG_DIR}" | head -30 || true +echo "done" diff --git a/deploy/pull_and_restart.sh b/deploy/pull_and_restart.sh index 15b8c66..903ceac 100644 --- a/deploy/pull_and_restart.sh +++ b/deploy/pull_and_restart.sh @@ -23,10 +23,14 @@ echo ">>> force-close policy: gate=ON, binance/okx=OFF" python3 scripts/sync_common_trading_env.py --apply-force-close-policy "${DRY[@]}" if [[ ${#DRY[@]} -gt 0 ]]; then - echo "(dry-run, skip pm2 restart)" + echo "(dry-run, skip pm2 log policy + restart)" exit 0 fi +echo ">>> pm2 log policy (silence leftover + 3-day retain)" +sed -i 's/\r$//' deploy/pm2_log_policy.sh 2>/dev/null || true +bash deploy/pm2_log_policy.sh + echo ">>> pm2 restart" pm2 restart crypto_gate crypto_binance crypto_okx manual-trading-hub manual-agent-gate 2>/dev/null \ || pm2 restart crypto-monitor-gate crypto-monitor-binance crypto-monitor-okx manual-trading-hub 2>/dev/null \ diff --git a/lib/common/flask_access_log_lib.py b/lib/common/flask_access_log_lib.py new file mode 100644 index 0000000..68e7403 --- /dev/null +++ b/lib/common/flask_access_log_lib.py @@ -0,0 +1,16 @@ +"""关闭 Flask/Werkzeug 开发服务器 access log 刷屏(避免灌满 PM2 error 日志).""" +from __future__ import annotations + +import logging + + +def silence_werkzeug_access_log() -> None: + """仅抑制 request access 行;WARNING/ERROR 仍可读.""" + log = logging.getLogger("werkzeug") + log.setLevel(logging.WARNING) + # 部分环境会挂 StreamHandler 到 stderr;抬高阈值即可 + for h in list(log.handlers): + try: + h.setLevel(logging.WARNING) + except Exception: + pass diff --git a/tests/test_flask_access_log_lib.py b/tests/test_flask_access_log_lib.py new file mode 100644 index 0000000..df253b2 --- /dev/null +++ b/tests/test_flask_access_log_lib.py @@ -0,0 +1,17 @@ +"""silence_werkzeug_access_log 烟雾测试.""" +import logging +import unittest + +from lib.common.flask_access_log_lib import silence_werkzeug_access_log + + +class TestSilenceAccessLog(unittest.TestCase): + def test_sets_warning_level(self): + log = logging.getLogger("werkzeug") + log.setLevel(logging.INFO) + silence_werkzeug_access_log() + self.assertGreaterEqual(log.level, logging.WARNING) + + +if __name__ == "__main__": + unittest.main()