fix(qmt): 消除静默异常处理并统一日志系统
- 修复6处静默except块(撤单、错误回调、线程停止、健康检查等) - 统一入口模块使用logging替代print - 增强交易日志可追踪性 - 添加完整堆栈跟踪日志
This commit is contained in:
55
qmt/main.py
55
qmt/main.py
@@ -1,38 +1,73 @@
|
|||||||
# coding:utf-8
|
# coding:utf-8
|
||||||
import threading
|
import threading
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import datetime
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
from .qmt_engine import QMTEngine
|
from .qmt_engine import QMTEngine
|
||||||
from .api_server import create_api_server
|
from .api_server import create_api_server
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logger():
|
||||||
|
"""配置日志系统"""
|
||||||
|
log_dir = "logs"
|
||||||
|
if not os.path.exists(log_dir):
|
||||||
|
os.makedirs(log_dir)
|
||||||
|
|
||||||
|
log_file = os.path.join(log_dir, f"{datetime.date.today().strftime('%Y-%m-%d')}.log")
|
||||||
|
|
||||||
|
logger = logging.getLogger("QMT_Main")
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
formatter = logging.Formatter(
|
||||||
|
'[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
|
||||||
|
file_handler = logging.FileHandler(log_file, mode='a', encoding='utf-8')
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
stream_handler = logging.StreamHandler(sys.stdout)
|
||||||
|
stream_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
logger.addHandler(stream_handler)
|
||||||
|
|
||||||
|
return logger
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""主函数 - 启动QMT交易引擎和API服务器"""
|
"""主函数 - 启动QMT交易引擎和API服务器"""
|
||||||
print(">>> 系统正在启动...")
|
logger = setup_logger()
|
||||||
|
logger.info("="*50)
|
||||||
|
logger.info(">>> QMT交易系统启动中...")
|
||||||
|
logger.info("="*50)
|
||||||
|
|
||||||
# 创建QMT引擎实例
|
# 创建QMT引擎实例
|
||||||
engine = QMTEngine()
|
engine = QMTEngine()
|
||||||
|
logger.info("QMT引擎实例创建成功")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 初始化引擎
|
# 初始化引擎
|
||||||
engine.initialize('config.json')
|
engine.initialize('config.json')
|
||||||
print("✅ QMT引擎初始化成功")
|
logger.info("✅ QMT引擎初始化成功")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ QMT引擎初始化失败: {e}")
|
logger.error(f"❌ QMT引擎初始化失败: {str(e)}", exc_info=True)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# 启动交易线程
|
# 启动交易线程
|
||||||
trading_thread = threading.Thread(target=engine.run_trading_loop, daemon=True)
|
trading_thread = threading.Thread(target=engine.run_trading_loop, daemon=True)
|
||||||
trading_thread.start()
|
trading_thread.start()
|
||||||
print("✅ 交易线程启动成功")
|
logger.info("✅ 交易线程启动成功")
|
||||||
|
|
||||||
# 创建API服务器
|
# 创建API服务器
|
||||||
app = create_api_server(engine)
|
app = create_api_server(engine)
|
||||||
print("✅ API服务器创建成功")
|
logger.info("✅ API服务器创建成功")
|
||||||
|
|
||||||
# 启动Web服务
|
# 启动Web服务
|
||||||
print(">>> Web服务启动: http://localhost:8001")
|
logger.info(">>> Web服务启动: http://localhost:8001")
|
||||||
try:
|
try:
|
||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
app,
|
app,
|
||||||
@@ -42,9 +77,13 @@ def main():
|
|||||||
access_log=False
|
access_log=False
|
||||||
)
|
)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\n>>> 正在关闭系统...")
|
logger.info(">>> 正在关闭系统...")
|
||||||
engine.stop()
|
engine.stop()
|
||||||
print(">>> 系统已关闭")
|
logger.info(">>> 系统已关闭")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f">>> 系统异常: {str(e)}", exc_info=True)
|
||||||
|
engine.stop()
|
||||||
|
logger.info(">>> 系统已关闭")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -459,7 +459,8 @@ class MultiEngineManager:
|
|||||||
if unit.callback and not unit.callback.is_connected:
|
if unit.callback and not unit.callback.is_connected:
|
||||||
unit.callback.is_connected = True
|
unit.callback.is_connected = True
|
||||||
self.logger.info(f"✅ 修正终端 {unit.alias} 状态为在线")
|
self.logger.info(f"✅ 修正终端 {unit.alias} 状态为在线")
|
||||||
except:
|
except Exception as e:
|
||||||
|
self.logger.error(f"健康检查失败 - 终端 {unit.alias}: {str(e)}", exc_info=True)
|
||||||
is_unit_alive = False
|
is_unit_alive = False
|
||||||
|
|
||||||
# 断线重连策略
|
# 断线重连策略
|
||||||
|
|||||||
@@ -1,32 +1,77 @@
|
|||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import datetime
|
||||||
from xtquant import xttrader
|
from xtquant import xttrader
|
||||||
from xtquant.xtdata import download_history_data, get_market_data
|
from xtquant.xtdata import download_history_data, get_market_data
|
||||||
from xtquant.xttype import StockAccount
|
from xtquant.xttype import StockAccount
|
||||||
import random
|
import random
|
||||||
|
|
||||||
##订阅账户
|
|
||||||
|
def setup_logger():
|
||||||
|
"""配置日志系统"""
|
||||||
|
log_dir = "logs"
|
||||||
|
if not os.path.exists(log_dir):
|
||||||
|
os.makedirs(log_dir)
|
||||||
|
|
||||||
|
log_file = os.path.join(log_dir, f"{datetime.date.today().strftime('%Y-%m-%d')}_test.log")
|
||||||
|
|
||||||
|
logger = logging.getLogger("QMT_Test")
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
formatter = logging.Formatter(
|
||||||
|
'[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
|
||||||
|
file_handler = logging.FileHandler(log_file, mode='a', encoding='utf-8')
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
stream_handler = logging.StreamHandler(sys.stdout)
|
||||||
|
stream_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
logger.addHandler(stream_handler)
|
||||||
|
|
||||||
|
return logger
|
||||||
|
|
||||||
|
|
||||||
|
logger = setup_logger()
|
||||||
|
|
||||||
# 设置 QMT 交易端的数据路径和会话ID
|
# 设置 QMT 交易端的数据路径和会话ID
|
||||||
min_path = r"C:\\QMT\\中金财富QMT个人版交易端\\userdata_mini"
|
min_path = r"C:\\QMT\\中金财富QMT个人版交易端\\userdata_mini"
|
||||||
session_id = int(random.randint(100000, 999999))
|
session_id = int(random.randint(100000, 999999))
|
||||||
|
|
||||||
|
logger.info(f"QMT路径: {min_path}")
|
||||||
|
logger.info(f"会话ID: {session_id}")
|
||||||
|
|
||||||
# 创建 XtQuantTrader 实例并启动
|
# 创建 XtQuantTrader 实例并启动
|
||||||
|
logger.info("正在创建 XtQuantTrader 实例...")
|
||||||
xt_trader = xttrader.XtQuantTrader(min_path, session_id)
|
xt_trader = xttrader.XtQuantTrader(min_path, session_id)
|
||||||
|
logger.info("正在启动 XtQuantTrader...")
|
||||||
xt_trader.start()
|
xt_trader.start()
|
||||||
|
logger.info("XtQuantTrader 已启动")
|
||||||
|
|
||||||
# 连接 QMT 交易端
|
# 连接 QMT 交易端
|
||||||
|
logger.info("正在连接 QMT 交易端...")
|
||||||
connect_result = xt_trader.connect()
|
connect_result = xt_trader.connect()
|
||||||
if connect_result == 0:
|
if connect_result == 0:
|
||||||
print('连接成功')
|
logger.info("✅ 连接成功")
|
||||||
else:
|
else:
|
||||||
print('连接失败')
|
logger.error(f"❌ 连接失败,错误码: {connect_result}")
|
||||||
xt_trader.stop()
|
xt_trader.stop()
|
||||||
|
logger.info("XtQuantTrader 已停止")
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
# 设置账户信息
|
# 设置账户信息
|
||||||
account = StockAccount('8176081580')
|
account_id = '8176081580'
|
||||||
|
logger.info(f"账户ID: {account_id}")
|
||||||
|
account = StockAccount(account_id)
|
||||||
|
|
||||||
# 订阅账户
|
# 订阅账户
|
||||||
|
logger.info("正在订阅账户...")
|
||||||
res = xt_trader.subscribe(account)
|
res = xt_trader.subscribe(account)
|
||||||
if res == 0:
|
if res == 0:
|
||||||
print('订阅成功')
|
logger.info("✅ 订阅成功")
|
||||||
else:
|
else:
|
||||||
print('订阅失败')
|
logger.error(f"❌ 订阅失败,错误码: {res}")
|
||||||
|
|||||||
@@ -172,11 +172,17 @@ class DailySettlement:
|
|||||||
logger.info("执行收盘清算流程...")
|
logger.info("执行收盘清算流程...")
|
||||||
try:
|
try:
|
||||||
orders = self.trader.query_stock_orders(self.acc, cancelable_only=True)
|
orders = self.trader.query_stock_orders(self.acc, cancelable_only=True)
|
||||||
|
logger.info(f"收盘清算 - 查询可撤单订单: 获取到 {len(orders) if orders else 0} 个订单")
|
||||||
if orders:
|
if orders:
|
||||||
for o in orders:
|
for o in orders:
|
||||||
|
logger.info(f"收盘清算 - 撤单: OrderID={o.order_id}, Stock={o.stock_code}")
|
||||||
self.trader.cancel_order_stock(self.acc, o.order_id)
|
self.trader.cancel_order_stock(self.acc, o.order_id)
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
except: pass
|
logger.info(f"收盘清算 - 完成撤单操作,共处理 {len(orders)} 个订单")
|
||||||
|
else:
|
||||||
|
logger.info("收盘清算 - 无待撤单订单")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"收盘清算 - 查询/撤单失败: {str(e)}", exc_info=True)
|
||||||
|
|
||||||
real_positions = self.trader.query_stock_positions(self.acc)
|
real_positions = self.trader.query_stock_positions(self.acc)
|
||||||
real_pos_map = {p.stock_code: p.volume for p in real_positions if p.volume > 0} if real_positions else {}
|
real_pos_map = {p.stock_code: p.volume for p in real_positions if p.volume > 0} if real_positions else {}
|
||||||
@@ -214,15 +220,19 @@ class MyXtQuantTraderCallback(XtQuantTraderCallback):
|
|||||||
logger.info(f">>> [成交] {strategy} {trade.stock_code} {trade.traded_volume}")
|
logger.info(f">>> [成交] {strategy} {trade.stock_code} {trade.traded_volume}")
|
||||||
if action == 'BUY': self.pos_mgr.update_actual_volume(strategy, trade.stock_code, trade.traded_volume)
|
if action == 'BUY': self.pos_mgr.update_actual_volume(strategy, trade.stock_code, trade.traded_volume)
|
||||||
elif action == 'SELL': self.pos_mgr.update_actual_volume(strategy, trade.stock_code, -trade.traded_volume)
|
elif action == 'SELL': self.pos_mgr.update_actual_volume(strategy, trade.stock_code, -trade.traded_volume)
|
||||||
except: traceback.print_exc()
|
except Exception as e:
|
||||||
|
logger.error(f"on_stock_trade 成交回调处理失败: {str(e)}", exc_info=True)
|
||||||
|
|
||||||
def on_order_error(self, err):
|
def on_order_error(self, err):
|
||||||
try:
|
try:
|
||||||
logger.error(f"下单失败回调: {err.error_msg} OrderID:{err.order_id}")
|
logger.error(f"下单失败回调: OrderID={err.order_id}, 错误信息={err.error_msg}")
|
||||||
cache = ORDER_CACHE.get(err.order_id)
|
cache = ORDER_CACHE.get(err.order_id)
|
||||||
if cache and cache[2] == 'BUY':
|
if cache and cache[2] == 'BUY':
|
||||||
|
logger.info(f"回滚持仓: Strategy={cache[0]}, Stock={cache[1]}")
|
||||||
self.pos_mgr.rollback_holding(cache[0], cache[1])
|
self.pos_mgr.rollback_holding(cache[0], cache[1])
|
||||||
del ORDER_CACHE[err.order_id]
|
del ORDER_CACHE[err.order_id]
|
||||||
except: pass
|
except Exception as e:
|
||||||
|
logger.error(f"on_order_error 错误回调处理失败: {str(e)}", exc_info=True)
|
||||||
|
|
||||||
# ================= 5. 核心消息处理 (重写版:拒绝静默失败) =================
|
# ================= 5. 核心消息处理 (重写版:拒绝静默失败) =================
|
||||||
def process_strategy_queue(strategy_name, r_client, xt_trader, acc, pos_manager):
|
def process_strategy_queue(strategy_name, r_client, xt_trader, acc, pos_manager):
|
||||||
@@ -327,20 +337,26 @@ def process_strategy_queue(strategy_name, r_client, xt_trader, acc, pos_manager)
|
|||||||
# 7. 卖出逻辑
|
# 7. 卖出逻辑
|
||||||
elif action == 'SELL':
|
elif action == 'SELL':
|
||||||
v_vol = pos_manager.get_position(strategy_name, stock_code)
|
v_vol = pos_manager.get_position(strategy_name, stock_code)
|
||||||
logger.info(f"Redis 记录持仓: {v_vol}")
|
logger.info(f"卖出 - Redis 记录虚拟持仓: {v_vol}")
|
||||||
|
|
||||||
if v_vol > 0:
|
if v_vol > 0:
|
||||||
|
logger.info(f"卖出 - 正在查询实盘持仓: {stock_code}")
|
||||||
real_pos = xt_trader.query_stock_positions(acc)
|
real_pos = xt_trader.query_stock_positions(acc)
|
||||||
|
logger.info(f"卖出 - 实盘持仓查询完成,获取到 {len(real_pos) if real_pos else 0} 条记录")
|
||||||
|
|
||||||
if real_pos is None:
|
if real_pos is None:
|
||||||
logger.error("API 错误: query_stock_positions 返回 None")
|
logger.error("API 错误: query_stock_positions 返回 None")
|
||||||
return
|
return
|
||||||
|
|
||||||
rp = next((p for p in real_pos if p.stock_code==stock_code), None)
|
rp = next((p for p in real_pos if p.stock_code==stock_code), None)
|
||||||
can_use = rp.can_use_volume if rp else 0
|
can_use = rp.can_use_volume if rp else 0
|
||||||
logger.info(f"实盘可用持仓: {can_use}")
|
logger.info(f"卖出 - 股票 {stock_code} 实盘可用持仓: {can_use}")
|
||||||
|
|
||||||
final = min(v_vol, can_use)
|
final = min(v_vol, can_use)
|
||||||
|
logger.info(f"卖出 - 计算卖出量: min({v_vol}, {can_use}) = {final}")
|
||||||
|
|
||||||
if final > 0:
|
if final > 0:
|
||||||
|
logger.info(f"卖出 - 执行卖出订单: {stock_code} @ {price}, 数量: {final}")
|
||||||
oid = xt_trader.order_stock(acc, stock_code, xtconstant.STOCK_SELL, final, xtconstant.FIX_PRICE, price, strategy_name, 'PySell')
|
oid = xt_trader.order_stock(acc, stock_code, xtconstant.STOCK_SELL, final, xtconstant.FIX_PRICE, price, strategy_name, 'PySell')
|
||||||
if oid != -1:
|
if oid != -1:
|
||||||
logger.info(f"√√√ 下单成功: ID={oid} {stock_code} 卖出 {final}")
|
logger.info(f"√√√ 下单成功: ID={oid} {stock_code} 卖出 {final}")
|
||||||
@@ -470,8 +486,11 @@ def trading_loop():
|
|||||||
|
|
||||||
logger.warning("🚫 确认连接丢失,执行重连...")
|
logger.warning("🚫 确认连接丢失,执行重连...")
|
||||||
if GLOBAL_STATE.xt_trader:
|
if GLOBAL_STATE.xt_trader:
|
||||||
try: GLOBAL_STATE.xt_trader.stop()
|
try:
|
||||||
except: pass
|
GLOBAL_STATE.xt_trader.stop()
|
||||||
|
logger.info("已停止旧交易实例")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"停止旧交易实例失败: {str(e)}", exc_info=True)
|
||||||
|
|
||||||
new_trader, new_acc, new_cb = init_qmt_trader(
|
new_trader, new_acc, new_cb = init_qmt_trader(
|
||||||
qmt_cfg['path'], qmt_cfg['account_id'], qmt_cfg['account_type'], pos_manager
|
qmt_cfg['path'], qmt_cfg['account_id'], qmt_cfg['account_type'], pos_manager
|
||||||
|
|||||||
65
qmt/run.py
65
qmt/run.py
@@ -6,6 +6,8 @@ QMT多终端交易系统启动器
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
import logging
|
||||||
|
import datetime
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
# 将当前目录添加到Python路径,确保模块导入正常
|
# 将当前目录添加到Python路径,确保模块导入正常
|
||||||
@@ -17,40 +19,73 @@ if current_dir not in sys.path:
|
|||||||
from qmt_engine import MultiEngineManager
|
from qmt_engine import MultiEngineManager
|
||||||
from api_server import create_api_server
|
from api_server import create_api_server
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logger():
|
||||||
|
"""配置日志系统"""
|
||||||
|
log_dir = "logs"
|
||||||
|
if not os.path.exists(log_dir):
|
||||||
|
os.makedirs(log_dir)
|
||||||
|
|
||||||
|
log_file = os.path.join(log_dir, f"{datetime.date.today().strftime('%Y-%m-%d')}.log")
|
||||||
|
|
||||||
|
logger = logging.getLogger("QMT_Run")
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
formatter = logging.Formatter(
|
||||||
|
'[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
|
||||||
|
file_handler = logging.FileHandler(log_file, mode='a', encoding='utf-8')
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
stream_handler = logging.StreamHandler(sys.stdout)
|
||||||
|
stream_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
logger.addHandler(stream_handler)
|
||||||
|
|
||||||
|
return logger
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""主函数 - 启动多终端QMT交易引擎管理中心和API服务器"""
|
"""主函数 - 启动多终端QMT交易引擎管理中心和API服务器"""
|
||||||
|
logger = setup_logger()
|
||||||
|
|
||||||
# 强制设置环境变量,确保Python在Windows控制台输出不因编码崩溃
|
# 强制设置环境变量,确保Python在Windows控制台输出不因编码崩溃
|
||||||
os.environ["PYTHONUTF8"] = "1"
|
os.environ["PYTHONUTF8"] = "1"
|
||||||
|
|
||||||
print("==================================================")
|
logger.info("="*50)
|
||||||
print(" QMT Multi-Terminal System Starting... ")
|
logger.info(">>> QMT Multi-Terminal System Starting...")
|
||||||
print("==================================================")
|
logger.info("="*50)
|
||||||
|
|
||||||
# 1. 获取多终端管理器单例
|
# 1. 获取多终端管理器单例
|
||||||
manager = MultiEngineManager()
|
manager = MultiEngineManager()
|
||||||
|
logger.info("多终端管理器实例获取成功")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 2. 初始化引擎(加载配置、连接Redis、初始化各终端执行单元)
|
# 2. 初始化引擎(加载配置、连接Redis、初始化各终端执行单元)
|
||||||
|
logger.info("正在加载配置文件: config.json")
|
||||||
manager.initialize('config.json')
|
manager.initialize('config.json')
|
||||||
print("Done: Multi-Manager initialized successfully.")
|
logger.info("Done: Multi-Manager initialized successfully.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error: System initialization failed: {repr(e)}")
|
logger.error(f"Error: System initialization failed: {repr(e)}", exc_info=True)
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# 3. 启动全局监控与交易路由主循环线程
|
# 3. 启动全局监控与交易路由主循环线程
|
||||||
# 该线程负责:终端健康检查、断线重连、消息路由、收盘结算
|
# 该线程负责:终端健康检查、断线重连、消息路由、收盘结算
|
||||||
|
logger.info("正在启动交易主循环线程...")
|
||||||
trading_thread = threading.Thread(target=manager.run_trading_loop, name="MainTradeLoop", daemon=True)
|
trading_thread = threading.Thread(target=manager.run_trading_loop, name="MainTradeLoop", daemon=True)
|
||||||
trading_thread.start()
|
trading_thread.start()
|
||||||
print("Done: Global trading loop thread started.")
|
logger.info("Done: Global trading loop thread started.")
|
||||||
|
|
||||||
# 4. 创建适配多终端的API服务器
|
# 4. 创建适配多终端的API服务器
|
||||||
|
logger.info("正在创建API服务器...")
|
||||||
app = create_api_server(manager)
|
app = create_api_server(manager)
|
||||||
print("Done: API server created with multi-terminal support.")
|
logger.info("Done: API server created with multi-terminal support.")
|
||||||
|
|
||||||
# 5. 启动Web服务
|
# 5. 启动Web服务
|
||||||
print(">>> Web Dashboard: http://localhost:8001")
|
logger.info(">>> Web Dashboard: http://localhost:8001")
|
||||||
try:
|
try:
|
||||||
# 建议关闭 access_log 以减少控制台刷屏
|
# 建议关闭 access_log 以减少控制台刷屏
|
||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
@@ -61,20 +96,22 @@ def main():
|
|||||||
access_log=False
|
access_log=False
|
||||||
)
|
)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\n>>> Shutdown signal received. Closing terminals...")
|
logger.info(">>> Shutdown signal received. Closing terminals...")
|
||||||
manager.stop()
|
manager.stop()
|
||||||
print(">>> System safely closed.")
|
logger.info(">>> System safely closed.")
|
||||||
_write_exit_code(0)
|
_write_exit_code(0)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
def _write_exit_code(code):
|
def _write_exit_code(code):
|
||||||
"""将退出码写入临时文件,供 start.bat 读取"""
|
"""将退出码写入临时文件,供 start.bat 读取"""
|
||||||
|
logger = logging.getLogger("QMT_Run")
|
||||||
try:
|
try:
|
||||||
exit_code_file = os.path.join(os.environ.get('TEMP', ''), 'exit_code.txt')
|
exit_code_file = os.path.join(os.environ.get('TEMP', ''), 'exit_code.txt')
|
||||||
with open(exit_code_file, 'w') as f:
|
with open(exit_code_file, 'w') as f:
|
||||||
f.write(str(code))
|
f.write(str(code))
|
||||||
except Exception:
|
logger.info(f">>> 退出码已写入: {code}")
|
||||||
pass
|
except Exception as e:
|
||||||
|
logger.error(f">>> 退出码写入失败: {str(e)}")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# 最佳实践:使用 python -u run.py 运行以获得实时日志输出
|
# 最佳实践:使用 python -u run.py 运行以获得实时日志输出
|
||||||
|
|||||||
Reference in New Issue
Block a user