@@ -228,43 +228,57 @@ def _load_icon() -> Image.Image:
228228
229229
230230def _open_browser (icon : pystray .Icon , item : pystray .MenuItem ) -> None :
231- port = _read_port ()
232- url = f"http://localhost:{ port } "
233- logger .info (f"打开浏览器: { url } " )
234- webbrowser .open (url )
231+ try :
232+ port = _read_port ()
233+ url = f"http://localhost:{ port } "
234+ logger .info (f"打开浏览器: { url } " )
235+ webbrowser .open (url )
236+ except Exception as exc :
237+ logger .exception (f"打开浏览器失败: { exc } " )
235238
236239
237240def _restart_gateway (icon : pystray .Icon , item : pystray .MenuItem ) -> None :
238- logger .info ("用户触发重启" )
239- # 先清过期锁(gateway 已被外部 kill -9 / 任务管理器结束的场景)
240- _clear_stale_lock ()
241- # "停止"之后 gateway 已死、.lock 已被 atexit 删掉,_read_pid() 返回 None。
242- # 此时 _kill_gateway() 返回 False,旧实现会短路掉 _spawn_gateway()——
243- # 结果用户点"重启"什么也没发生。这里改成:没有正在跑的进程就直接拉起。
244- # 注意不调 _wait_for_port_free:停止后 .port 已被删,_read_port() 退回
245- # 8130,但 8130 可能被别的应用占用——此时等它空闲会误判为"端口占用"。
246- # gateway 内部的 find_available_port 会自己探测可用端口,托盘不需要干预。
247- pid = _read_pid ()
248- if pid is None :
249- logger .info ("未发现正在运行的 gateway,直接启动新实例" )
250- _spawn_gateway ()
251- return
252- port = _read_port ()
253- if _kill_gateway () and _wait_for_port_free (port , timeout = 10.0 ):
254- _spawn_gateway ()
255- else :
256- logger .warning ("重启失败:旧进程未退出或端口仍占用" )
241+ try :
242+ logger .info ("用户触发重启" )
243+ # 先清过期锁(gateway 已被外部 kill -9 / 任务管理器结束的场景)
244+ _clear_stale_lock ()
245+ # "停止"之后 gateway 已死、.lock 已被 atexit 删掉,_read_pid() 返回 None。
246+ # 此时 _kill_gateway() 返回 False,旧实现会短路掉 _spawn_gateway()——
247+ # 结果用户点"重启"什么也没发生。这里改成:没有正在跑的进程就直接拉起。
248+ # 注意不调 _wait_for_port_free:停止后 .port 已被删,_read_port() 退回
249+ # 8130,但 8130 可能被别的应用占用——此时等它空闲会误判为"端口占用"。
250+ # gateway 内部的 find_available_port 会自己探测可用端口,托盘不需要干预。
251+ pid = _read_pid ()
252+ if pid is None :
253+ logger .info ("未发现正在运行的 gateway,直接启动新实例" )
254+ if not _spawn_gateway ():
255+ logger .error ("重启失败:spawn 返回 False" )
256+ return
257+ port = _read_port ()
258+ if _kill_gateway () and _wait_for_port_free (port , timeout = 10.0 ):
259+ if not _spawn_gateway ():
260+ logger .error ("重启失败:spawn 返回 False" )
261+ else :
262+ logger .warning ("重启失败:旧进程未退出或端口仍占用" )
263+ except Exception as exc :
264+ logger .exception (f"重启失败: { exc } " )
257265
258266
259267def _stop_gateway (icon : pystray .Icon , item : pystray .MenuItem ) -> None :
260- logger .info ("用户触发停止" )
261- _kill_gateway ()
268+ try :
269+ logger .info ("用户触发停止" )
270+ _kill_gateway ()
271+ except Exception as exc :
272+ logger .exception (f"停止失败: { exc } " )
262273
263274
264275def _quit_tray (icon : pystray .Icon , item : pystray .MenuItem ) -> None :
265276 """退出托盘本身;gateway 继续运行(用户可从开始菜单重启托盘)。"""
266- logger .info ("退出托盘;gateway 保持运行" )
267- icon .stop ()
277+ try :
278+ logger .info ("退出托盘;gateway 保持运行" )
279+ icon .stop ()
280+ except Exception as exc :
281+ logger .exception (f"退出托盘失败: { exc } " )
268282
269283
270284def _start_gateway (icon : pystray .Icon , item : pystray .MenuItem ) -> None :
@@ -275,15 +289,19 @@ def _start_gateway(icon: pystray.Icon, item: pystray.MenuItem) -> None:
275289 探测可用端口,托盘不需要预先等端口空闲(否则 8130 被别的应用占用时
276290 会误判为"无法启动")。
277291 """
278- logger .info ("用户触发启动" )
279- _clear_stale_lock ()
280- pid = _read_pid ()
281- if pid is not None and _is_pid_alive_windows (pid ):
282- # 已经在跑——直接打开浏览器,不重复拉起
283- logger .info ("gateway 已在运行,打开浏览器" )
284- _open_browser (icon , item )
285- return
286- _spawn_gateway ()
292+ try :
293+ logger .info ("用户触发启动" )
294+ _clear_stale_lock ()
295+ pid = _read_pid ()
296+ if pid is not None and _is_pid_alive_windows (pid ):
297+ # 已经在跑——直接打开浏览器,不重复拉起
298+ logger .info ("gateway 已在运行,打开浏览器" )
299+ _open_browser (icon , item )
300+ return
301+ if not _spawn_gateway ():
302+ logger .error ("启动失败:spawn 返回 False" )
303+ except Exception as exc :
304+ logger .exception (f"启动失败: { exc } " )
287305
288306
289307def _build_menu () -> pystray .Menu :
@@ -334,5 +352,24 @@ def _ready_signal():
334352if __name__ == "__main__" :
335353 logger .remove ()
336354 logger .add (sys .stderr , level = "INFO" )
355+ # 托盘是 DETACHED_PROCESS 启动的,stderr 在父进程上下文里没有可见
356+ # 的控制台——所有诊断信息会消失。这里把 INFO 及以上级别的日志镜像
357+ # 到 $INSTDIR\logs\tray.log(开发态为仓库根的 logs\tray.log),
358+ # 出问题时有据可查。
359+ try :
360+ log_dir = Path (__file__ ).resolve ().parent .parent / "logs"
361+ # 安装态:qmt_gateway/tray.py 在 $INSTDIR\app\qmt_gateway\,
362+ # parent.parent 是 $INSTDIR\app,所以这里指向 app\logs。
363+ # 退而求其次写到 logs\tray.log(cwd 视角)。
364+ log_dir .mkdir (parents = True , exist_ok = True )
365+ logger .add (
366+ log_dir / "tray.log" ,
367+ level = "INFO" ,
368+ rotation = "1 MB" ,
369+ retention = 3 ,
370+ encoding = "utf-8" ,
371+ )
372+ except OSError :
373+ pass
337374 logger .info ("QMT Gateway 托盘启动" )
338375 run ()
0 commit comments