"""装配入口:配置日志、注册命令并运行 bot。""" import asyncio import sys from aiohttp import ClientSession from loguru import logger from qqbotsdk import EventEmitter, run_loop from .config import BotConfig from .music import register # 日志文件按天轮转、保留 7 天;目录写不进去时退化为仅控制台 _LOG_RETENTION = "7 days" _LOG_FORMAT = ( "{time:YYYY-MM-DD HH:mm:ss.SSS} | " "{level: <8} | " "{name}:{function}:{line} - " "{message}" ) def setup_logging(config: BotConfig) -> None: """日志同时输出到控制台与 logs/musicbot.log(按天轮转)。""" logger.remove() # 去掉 loguru 默认的 stderr handler,统一由这里接管 logger.add(sys.stderr, level=config.log_level, format=_LOG_FORMAT) try: config.log_dir.mkdir(parents=True, exist_ok=True) logger.add( config.log_dir / "musicbot.log", level=config.log_level, format=_LOG_FORMAT, rotation="00:00", # 每天零点切分 retention=_LOG_RETENTION, encoding="utf-8", enqueue=True, # 多任务写入串行化,避免并发写坏文件 backtrace=True, diagnose=False, # 不回显变量值,避免把凭证写进日志 ) except OSError as e: logger.warning(f"日志目录不可写({config.log_dir}),仅输出到控制台: {e}") async def amain() -> None: config = BotConfig.load() setup_logging(config) logger.info(f"musicbot 启动,日志级别 {config.log_level}") ee = EventEmitter() # 队列由 emitter 自建,run_loop 复用同一实例 # bot 自建连接池:直链探测不受 SDK 的 TIMEOUT 约束 async with ClientSession() as http: register(ee, config, http) await run_loop(ee) def main() -> None: try: asyncio.run(amain()) except KeyboardInterrupt: logger.info("musicbot 已退出")