This commit is contained in:
konjacpotato
2025-11-14 21:45:00 +08:00
parent fd2a3171ad
commit 52d5bbc216
3 changed files with 173 additions and 57 deletions

95
arlo.py
View File

@ -1,5 +1,16 @@
"""Entrypoint for the scheduler process.
This module starts the APScheduler scheduler and ensures a graceful
shutdown on SIGINT/SIGTERM. It also improves job error logging to
include exception tracebacks for easier debugging.
"""
import datetime
import signal
import sys
import traceback
from functools import partial
from typing import Any
from apscheduler.events import EVENT_JOB_ERROR
from apscheduler.schedulers.blocking import BlockingScheduler
@ -9,28 +20,92 @@ from log.log_manager import log, logger
from task.manager_task import manager_task
def job_error_listener(event):
if event.exception:
logger.error(f"Job {event.job_id} crashed: {str(event.exception)}")
# 可添加邮件/钉钉告警逻辑
def _format_exception(exc: BaseException) -> str:
"""Return a nicely formatted exception with traceback."""
return "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
if __name__ == '__main__':
def job_error_listener(event: Any) -> None:
"""Listener for job errors that logs the exception and traceback.
Uses the APScheduler event object. This is defensive: if an exception
is present it logs its string representation and the full traceback to
the configured logger. Keep this lightweight to avoid raising inside
the listener.
"""
try:
if getattr(event, "exception", None):
exc = event.exception
logger.error(f"Job {getattr(event, 'job_id', '<unknown>')} crashed: {exc}")
logger.error(_format_exception(exc))
# 可添加邮件告警逻辑(如果需要且已配置)
except Exception:
# We must not let the listener raise — log and continue.
logger.exception("Exception in job_error_listener")
def _validate_interval(value: Any) -> int:
"""Return a valid positive integer interval in seconds.
If the provided value is invalid, returns a safe default (300).
"""
DEFAULT = 300
try:
iv = int(value)
if iv <= 0:
raise ValueError("interval must be > 0")
return iv
except Exception:
logger.warning(
"Invalid config.scheduler_interval=%r, falling back to %s seconds",
value,
DEFAULT,
)
return DEFAULT
def main() -> None:
"""Create and start the scheduler with graceful shutdown handling."""
scheduler = BlockingScheduler()
# 每隔config.scheduler_interval秒执行一次任务同时设定第一次执行在程序启动后10秒后执行
interval_seconds = _validate_interval(getattr(config, "scheduler_interval", None))
# 每隔 interval_seconds 秒执行一次任务同时设定第一次执行在程序启动10秒后执行
scheduler.add_job(
partial(manager_task, scheduler),
'interval',
seconds=config.scheduler_interval,
jitter=30, # 添加随机抖动避免任务雪崩
next_run_time=datetime.datetime.now() + datetime.timedelta(seconds=10) # 替代 date 触发器
"interval",
seconds=interval_seconds,
jitter=30,
next_run_time=datetime.datetime.now() + datetime.timedelta(seconds=10),
)
# 添加任务错误监听器
scheduler.add_listener(job_error_listener, EVENT_JOB_ERROR)
# Graceful shutdown handlers
def _shutdown(signum, frame):
log(f"Received signal {signum}. Shutting down scheduler...")
try:
scheduler.shutdown(wait=False)
except Exception:
logger.exception("Error while shutting down scheduler")
# Ensure process exits after scheduler shutdown
# sys.exit(0)
signal.signal(signal.SIGINT, _shutdown)
signal.signal(signal.SIGTERM, _shutdown)
try:
log("started successfully.")
scheduler.start() # 阻塞运行
except (KeyboardInterrupt, SystemExit):
log("Shutting down ...")
if scheduler.state == 1:
try:
scheduler.shutdown(wait=False)
except Exception:
logger.exception("Error while shutting down scheduler")
if __name__ == "__main__":
main()