feat: RESTART 走 NSSM 服务重启 + 失败原因可观测化
- RESTART 命令优先通过 nssm restart 重启主程序服务(假死场景可用),未配置服务名回退环回 HTTP - nssm 输出按 UTF-16 解码并记录日志,权限不足时提示需以服务方式运行 - 环回管理接口 code=3001 时明确报鉴权失败,不再误报不可达 - 新增 nssm.path / main.service.name / log.dir 配置项 - 增加文件日志(RotatingFileHandler 5MB×5),服务方式运行可查日志 - HTTP 调用失败与状态码异常统一 WARNING 记录(原 DEBUG 不可见) - 断线日志区分「网络不通」与「被服务器拒绝(未注册/token 错误)」 - 连接 URL 自动拼接 device.id,消除双处配置不一致 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+43
-4
@@ -76,20 +76,59 @@ class ProcessGuard:
|
||||
self._restart_count = 3
|
||||
return
|
||||
|
||||
log.error("主程序宕机,执行第 %d 次拉起:%s", attempts, self.config.main_start_cmd)
|
||||
# 拉起方式:配置了服务名则走 nssm restart(幂等),否则回退启动命令
|
||||
if self.config.main_service_name:
|
||||
start_desc = f"nssm restart {self.config.main_service_name}"
|
||||
else:
|
||||
start_desc = self.config.main_start_cmd
|
||||
log.error("主程序宕机,执行第 %d 次拉起:%s", attempts, start_desc)
|
||||
self.client.send("ALERT", {
|
||||
"level": "WARN",
|
||||
"msg": f"主程序宕机,Agent 正在执行第 {attempts} 次拉起",
|
||||
})
|
||||
try:
|
||||
proc = start_main_app(self.config.main_start_cmd)
|
||||
log.info("启动命令已执行,pid=%s", proc.pid if proc else None)
|
||||
if self.config.main_service_name:
|
||||
ok, reason = restart_main_service(self.config.nssm_path, self.config.main_service_name)
|
||||
if not ok:
|
||||
log.error("nssm 拉起失败:%s", reason)
|
||||
else:
|
||||
proc = start_main_app(self.config.main_start_cmd)
|
||||
log.info("启动命令已执行,pid=%s", proc.pid if proc else None)
|
||||
except Exception as e:
|
||||
log.error("启动命令执行失败:%s", e)
|
||||
log.error("拉起失败:%s", e)
|
||||
|
||||
|
||||
# ================= 公共工具(upgrade.py 复用) =================
|
||||
|
||||
def restart_main_service(nssm_path: str, service_name: str) -> tuple[bool, str]:
|
||||
"""
|
||||
通过 NSSM 重启主程序服务(幂等:无论服务处于停止/运行态均到运行态)。
|
||||
|
||||
nssm_path 为 nssm.exe 完整路径;service_name 为注册的 Windows 服务名。
|
||||
返回 (是否成功, 输出/原因)。nssm 输出打印到日志,权限不足(拒绝访问)等
|
||||
失败原因一眼可见。
|
||||
"""
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[nssm_path, "restart", service_name],
|
||||
capture_output=True,
|
||||
timeout=60,
|
||||
)
|
||||
# nssm 输出为 UTF-16 编码(宽字符),需按 utf-16 解码
|
||||
out = ((proc.stdout or b"") + (proc.stderr or b"")).decode("utf-16", errors="replace").strip()
|
||||
ok = proc.returncode == 0
|
||||
if ok:
|
||||
log.info("nssm restart %s 成功:%s", service_name, out or "(无输出)")
|
||||
else:
|
||||
log.warning("nssm restart %s 失败(exit=%s):%s", service_name, proc.returncode, out)
|
||||
if "拒绝访问" in out or "Access" in out:
|
||||
out += "(提示:重启服务需要管理员权限,请将 Agent 以 Windows 服务方式运行)"
|
||||
return ok, out
|
||||
except Exception as e:
|
||||
log.warning("nssm restart %s 执行异常:%s", service_name, e)
|
||||
return False, str(e)
|
||||
|
||||
|
||||
def start_main_app(start_cmd: str) -> subprocess.Popen | None:
|
||||
"""
|
||||
拉起主程序。
|
||||
|
||||
Reference in New Issue
Block a user