Files
abot/main.py
liuwei 3b9bd02b5f 修复转图浏览器预热跨事件循环失效问题
变更项:\n1. 新增 async_job 启动钩子能力 add_startup_job,在调度器事件循环中执行一次性初始化任务。\n2. 将 main.py 的 Markdown 转图预热从独立线程改为调度器 loop 内执行,确保预热实例可被后续任务复用。\n3. 增强 markdown_to_image 常驻浏览器管理:记录 owner loop、检测跨 loop 复用并自动重建。\n4. 补充预热与常驻浏览器日志,输出 loop 标识和浏览器 PID,便于线上排查进程状态。\n5. 保持现有转图超时与重试逻辑不变,仅修复预热生效链路与可观测性。
2026-04-17 09:55:03 +08:00

140 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
import asyncio
import threading
from admin.GlancesMonitor import GlancesMonitor
from utils.decorator.async_job import async_job
from utils.markdown_to_image import warmup_md2img_browser
from configuration import Config
from robot import Robot
from loguru import logger
from utils.sehuatang.sehuatang_bot import SehuatangCrawler
# 普通日志不附带 traceback避免 debug/info 文件被异常堆栈刷屏。
def _plain_log_format(record):
record["exception"] = None
return "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level:<8} | {name}:{function}:{line} - {message}\n"
def _error_log_format(record):
return (
"{time:YYYY-MM-DD HH:mm:ss.SSS} | {level:<8} | {name}:{function}:{line} - {message}\n"
"{exception}"
)
# INFO 日志(包含 INFO、DEBUG但不包含 WARNING、ERROR
logger.add(
f"logs/wx_info.log",
level="INFO",
filter=lambda record: record["level"].name in ["INFO", "DEBUG"],
rotation="10 MB",
retention="7 days",
encoding="utf-8",
format=_plain_log_format,
backtrace=False,
diagnose=False,
)
# ERROR 日志(仅 ERROR 及以上)
logger.add(
f"logs/wx_error.log",
level="ERROR",
rotation="10 MB",
retention="7 days",
encoding="utf-8",
format=_error_log_format,
backtrace=True,
diagnose=True,
)
# ERROR 日志(仅 ERROR 及以上)
logger.add(
f"logs/wx_debug.log",
level="DEBUG",
rotation="10 MB",
retention="7 days",
encoding="utf-8",
format=_plain_log_format,
backtrace=False,
diagnose=False,
)
def main():
config = Config()
# 创建机器人实例
robot = Robot(config)
robot.LOG.info(f"ABOT服务 正在启动...")
# 初始化并启动wechat_ipad客户端
if robot.init_wechat_ipad():
robot.LOG.info("wechat_ipad客户端启动成功")
else:
robot.LOG.error("wechat_ipad客户端启动失败")
# 注册定时任务
jobs(robot)
# 启动Dashboard服务器
try:
# 创建Dashboard服务器实例共享robot对象
from admin.dashboard.server import DashboardServer
dashboard_server = DashboardServer(robot_instance=robot)
# 在单独的线程中启动Dashboard服务器
dashboard_thread = threading.Thread(target=dashboard_server.run, daemon=True)
dashboard_thread.start()
robot.LOG.info(f"Dashboard服务器已在 http://{dashboard_server.host}:{dashboard_server.port} 启动")
except Exception as e:
robot.LOG.error(f"Dashboard服务器启动失败: {e}")
try:
robot.LOG.debug(f"开始启动GlancesMonitor")
# 初始化 Glances 监控
monitor = GlancesMonitor(
email_sender=robot.email_sender,
host=config.glances.get("host"),
port=config.glances.get("port"),
cpu_threshold=80.0,
load_threshold=16, # 自动设为 CPU 核心数 * 2
io_threshold=100.0,
disk_usage_threshold=70.0,
handle_threshold=20000,
recipient=config.email.get("alert_recipient")
)
monitor.run()
except Exception as e:
robot.LOG.error(f"GlancesMonitor服务器启动失败: {e}")
# 启动后在“调度器同一事件循环”中预热 Markdown 转图浏览器。
# 这样可确保预热得到的常驻浏览器与后续截图任务复用同一 loop避免跨 loop 句柄失效。
try:
async def _warmup_md2img():
ok = await warmup_md2img_browser(timeout_seconds=60)
if ok:
robot.LOG.info("Markdown 转图浏览器预热成功(调度器事件循环)")
else:
robot.LOG.warning("Markdown 转图浏览器预热失败,运行期将按需重试")
async_job.add_startup_job(_warmup_md2img, name="md2img_warmup")
except Exception as e:
robot.LOG.error(f"注册 Markdown 转图预热任务失败: {e}")
robot.LOG.info(f"=" * 50)
asyncio.run(async_job.run_all())
# 让机器人一直跑
robot.keep_running_and_block_process()
def jobs(robot: Robot):
# 系统级定时任务统一改为数据库驱动,不再在 main.py 里硬编码维护。
# 这里保留入口,只负责按表配置重新加载,便于运行时刷新。
if hasattr(robot, "system_job_loader") and robot.system_job_loader:
robot.system_job_loader.reload_from_db()
if __name__ == "__main__":
main()