From 342aef7e1eccf1eb260c6c1d8de936da53e698f9 Mon Sep 17 00:00:00 2001 From: dnslin Date: Fri, 12 Dec 2025 08:39:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(logging):=20=E4=B8=BA=20aria2=20=E5=AE=89?= =?UTF-8?q?=E8=A3=85=E5=99=A8=E3=80=81=E6=9C=8D=E5=8A=A1=E5=92=8C=20Telegr?= =?UTF-8?q?am=20=E5=A4=84=E7=90=86=E5=99=A8=E6=B7=BB=E5=8A=A0=E8=AF=A6?= =?UTF-8?q?=E7=BB=86=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aria2/installer.py | 36 ++++++++++++++++++++++++++++++ src/aria2/service.py | 12 ++++++++++ src/telegram/handlers.py | 48 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) diff --git a/src/aria2/installer.py b/src/aria2/installer.py index 96cdd82..f56d344 100644 --- a/src/aria2/installer.py +++ b/src/aria2/installer.py @@ -11,6 +11,8 @@ from concurrent.futures import ThreadPoolExecutor from pathlib import Path from urllib import error, request +from src.utils.logger import get_logger + from src.core import ( ARIA2_BIN, ARIA2_CONFIG_DIR, @@ -28,6 +30,9 @@ from src.core import ( ) +logger = get_logger("installer") + + class Aria2Installer: GITHUB_API = "https://api.github.com/repos/P3TERX/Aria2-Pro-Core/releases/latest" GITHUB_MIRROR = "https://gh-api.p3terx.com/repos/P3TERX/Aria2-Pro-Core/releases/latest" @@ -45,11 +50,13 @@ class Aria2Installer: async def get_latest_version(self) -> str: """从 GitHub API 获取最新版本号""" + logger.info("正在获取 aria2 最新版本...") loop = asyncio.get_running_loop() last_error: Exception | None = None for url in (self.GITHUB_API, self.GITHUB_MIRROR): try: + logger.info(f"尝试从 {url} 获取版本信息") data = await loop.run_in_executor( self._executor, functools.partial(self._fetch_url, url) ) @@ -57,8 +64,10 @@ class Aria2Installer: tag_name = payload.get("tag_name") if not tag_name: raise DownloadError("tag_name missing in GitHub API response") + logger.info(f"获取到最新版本: {tag_name}") return tag_name except Exception as exc: # noqa: PERF203 + logger.error(f"从 {url} 获取版本失败: {exc}") last_error = exc continue @@ -74,6 +83,8 @@ class Aria2Installer: f"{resolved_version}/{archive_name}" ) + logger.info(f"正在下载 aria2 二进制文件: {archive_name}") + logger.info(f"下载地址: {download_url}") loop = asyncio.get_running_loop() with tempfile.TemporaryDirectory() as tmpdir: @@ -89,14 +100,18 @@ class Aria2Installer: await loop.run_in_executor( self._executor, functools.partial(self._write_file, archive_path, data) ) + logger.info("二进制文件下载完成") except Exception as exc: # noqa: PERF203 + logger.error(f"下载二进制文件失败: {exc}") raise DownloadError(f"Failed to download aria2 binary: {exc}") from exc try: + logger.info("正在解压二进制文件...") binary_path = await loop.run_in_executor( self._executor, functools.partial(self._extract_binary, archive_path, extract_dir) ) except Exception as exc: # noqa: PERF203 + logger.error(f"解压二进制文件失败: {exc}") raise DownloadError(f"Failed to extract aria2 binary: {exc}") from exc try: @@ -105,13 +120,16 @@ class Aria2Installer: ARIA2_BIN.unlink() shutil.move(str(binary_path), ARIA2_BIN) ARIA2_BIN.chmod(0o755) + logger.info(f"aria2 二进制文件已安装到: {ARIA2_BIN}") except Exception as exc: # noqa: PERF203 + logger.error(f"安装二进制文件失败: {exc}") raise DownloadError(f"Failed to install aria2 binary: {exc}") from exc return ARIA2_BIN async def download_config(self) -> None: """下载配置模板文件""" + logger.info("正在下载配置文件...") ARIA2_CONFIG_DIR.mkdir(parents=True, exist_ok=True) loop = asyncio.get_running_loop() @@ -127,16 +145,19 @@ class Aria2Installer: await loop.run_in_executor( self._executor, functools.partial(self._write_file, target, data) ) + logger.info(f"配置文件已下载: {filename}") last_error = None break except Exception as exc: # noqa: PERF203 last_error = exc continue if last_error is not None: + logger.error(f"下载配置文件失败: {filename} - {last_error}") raise DownloadError(f"Failed to download {filename}: {last_error}") from last_error def render_config(self) -> None: """渲染配置文件,注入用户参数""" + logger.info("正在渲染配置文件...") if not ARIA2_CONF.exists(): raise ConfigError("Config template not found. Run download_config first.") @@ -156,6 +177,8 @@ class Aria2Installer: "max-connection-per-server=": str(self.config.max_connection_per_server), } + logger.info(f"配置参数: RPC端口={self.config.rpc_port}, 下载目录={self.config.download_dir}") + new_lines: list[str] = [] for line in content.splitlines(): stripped = line.lstrip() @@ -174,16 +197,20 @@ class Aria2Installer: ARIA2_SESSION.touch(exist_ok=True) self.config.download_dir.mkdir(parents=True, exist_ok=True) ARIA2_LOG.touch(exist_ok=True) + logger.info(f"配置文件已保存: {ARIA2_CONF}") except OSError as exc: + logger.error(f"保存配置文件失败: {exc}") raise ConfigError(f"Failed to render config: {exc}") from exc async def install(self, version: str | None = None) -> dict: """完整安装流程""" + logger.info("开始安装 aria2...") resolved_version = version or await self.get_latest_version() await self.download_binary(resolved_version) await self.download_config() self.render_config() + logger.info(f"aria2 安装完成! 版本: {resolved_version}, 路径: {ARIA2_BIN}") return { "version": resolved_version, "binary": str(ARIA2_BIN), @@ -195,31 +222,40 @@ class Aria2Installer: def uninstall(self) -> None: """卸载 aria2""" + logger.info("开始卸载 aria2...") errors: list[Exception] = [] try: if ARIA2_BIN.exists(): ARIA2_BIN.unlink() + logger.info(f"已删除二进制文件: {ARIA2_BIN}") except Exception as exc: # noqa: PERF203 + logger.error(f"删除二进制文件失败: {exc}") errors.append(exc) try: if ARIA2_CONFIG_DIR.exists(): shutil.rmtree(ARIA2_CONFIG_DIR) + logger.info(f"已删除配置目录: {ARIA2_CONFIG_DIR}") except Exception as exc: # noqa: PERF203 + logger.error(f"删除配置目录失败: {exc}") errors.append(exc) try: service_path = Path.home() / ".config" / "systemd" / "user" / "aria2.service" if service_path.exists(): service_path.unlink() + logger.info(f"已删除服务文件: {service_path}") except Exception as exc: # noqa: PERF203 + logger.error(f"删除服务文件失败: {exc}") errors.append(exc) if errors: messages = "; ".join(str(err) for err in errors) raise Aria2Error(f"Failed to uninstall aria2: {messages}") + logger.info("aria2 卸载完成") + def _fetch_url(self, url: str) -> bytes: """阻塞式 URL 获取,放在线程池中运行""" req = request.Request(url, headers={"User-Agent": "aria2-installer"}) diff --git a/src/aria2/service.py b/src/aria2/service.py index bfffc93..49ca1d3 100644 --- a/src/aria2/service.py +++ b/src/aria2/service.py @@ -4,6 +4,8 @@ from __future__ import annotations import os import subprocess +from src.utils.logger import get_logger + from src.core import ( ARIA2_BIN, ARIA2_CONF, @@ -31,6 +33,8 @@ RestartSec=5 WantedBy=default.target """ +logger = get_logger("service") + class Aria2ServiceManager: def __init__(self) -> None: @@ -63,16 +67,22 @@ class Aria2ServiceManager: raise ServiceError(f"Failed to write service file: {exc}") from exc def start(self) -> None: + logger.info("正在启动 aria2 服务...") if not is_aria2_installed(): raise NotInstalledError("aria2 is not installed") self._ensure_service_file() self._run_systemctl("start", "aria2") + logger.info("aria2 服务已启动") def stop(self) -> None: + logger.info("正在停止 aria2 服务...") self._run_systemctl("stop", "aria2") + logger.info("aria2 服务已停止") def restart(self) -> None: + logger.info("正在重启 aria2 服务...") self._run_systemctl("restart", "aria2") + logger.info("aria2 服务已重启") def enable(self) -> None: self._run_systemctl("enable", "aria2") @@ -81,6 +91,7 @@ class Aria2ServiceManager: self._run_systemctl("disable", "aria2") def status(self) -> dict: + logger.info("正在获取 aria2 服务状态...") installed = is_aria2_installed() pid = self.get_pid() if installed else None @@ -103,6 +114,7 @@ class Aria2ServiceManager: running = active_proc.returncode == 0 enabled = enabled_proc.returncode == 0 + logger.info(f"aria2 状态: 已安装={installed}, 运行中={running}, PID={pid}") return { "installed": installed, "running": running, diff --git a/src/telegram/handlers.py b/src/telegram/handlers.py index d9f60cc..8e94dbe 100644 --- a/src/telegram/handlers.py +++ b/src/telegram/handlers.py @@ -4,6 +4,8 @@ from __future__ import annotations from telegram import Update from telegram.ext import ContextTypes, CommandHandler +from src.utils.logger import get_logger + from src.core import ( Aria2Config, Aria2Error, @@ -17,6 +19,16 @@ from src.core import ( ) from src.aria2 import Aria2Installer, Aria2ServiceManager +logger = get_logger("handlers") + + +def _get_user_info(update: Update) -> str: + """获取用户信息用于日志""" + user = update.effective_user + if user: + return f"用户ID={user.id}, 用户名={user.username or 'N/A'}" + return "未知用户" + class Aria2BotAPI: def __init__(self, config: Aria2Config | None = None): @@ -61,6 +73,7 @@ class Aria2BotAPI: return self.config.rpc_port async def install(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + logger.info(f"收到 /install 命令 - {_get_user_info(update)}") await self._reply(update, context, "正在安装 aria2,处理中,请稍候...") try: result = await self.installer.install() @@ -82,12 +95,16 @@ class Aria2BotAPI: ] ), ) + logger.info(f"/install 命令执行成功 - {_get_user_info(update)}") except (DownloadError, ConfigError, Aria2Error) as exc: + logger.error(f"/install 命令执行失败: {exc} - {_get_user_info(update)}") await self._reply(update, context, f"安装失败:{exc}") except Exception as exc: # noqa: BLE001 + logger.error(f"/install 命令执行失败(未知错误): {exc} - {_get_user_info(update)}") await self._reply(update, context, f"安装失败,发生未知错误:{exc}") async def uninstall(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + logger.info(f"收到 /uninstall 命令 - {_get_user_info(update)}") await self._reply(update, context, "正在卸载 aria2,处理中,请稍候...") try: try: @@ -96,53 +113,73 @@ class Aria2BotAPI: pass self.installer.uninstall() await self._reply(update, context, "卸载完成 ✅") + logger.info(f"/uninstall 命令执行成功 - {_get_user_info(update)}") except Aria2Error as exc: + logger.error(f"/uninstall 命令执行失败: {exc} - {_get_user_info(update)}") await self._reply(update, context, f"卸载失败:{exc}") except Exception as exc: # noqa: BLE001 + logger.error(f"/uninstall 命令执行失败(未知错误): {exc} - {_get_user_info(update)}") await self._reply(update, context, f"卸载失败,发生未知错误:{exc}") async def start_service(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + logger.info(f"收到 /start 命令 - {_get_user_info(update)}") try: if not is_aria2_installed(): + logger.info(f"/start 命令: aria2 未安装 - {_get_user_info(update)}") await self._reply(update, context, "aria2 未安装,请先运行 /install") return self.service.start() await self._reply(update, context, "aria2 服务已启动 ✅") + logger.info(f"/start 命令执行成功 - {_get_user_info(update)}") except NotInstalledError: + logger.info(f"/start 命令: aria2 未安装 - {_get_user_info(update)}") await self._reply(update, context, "aria2 未安装,请先运行 /install") except ServiceError as exc: + logger.error(f"/start 命令执行失败: {exc} - {_get_user_info(update)}") await self._reply(update, context, f"启动失败:{exc}") except Exception as exc: # noqa: BLE001 + logger.error(f"/start 命令执行失败(未知错误): {exc} - {_get_user_info(update)}") await self._reply(update, context, f"启动失败,发生未知错误:{exc}") async def stop_service(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + logger.info(f"收到 /stop 命令 - {_get_user_info(update)}") try: self.service.stop() await self._reply(update, context, "aria2 服务已停止 ✅") + logger.info(f"/stop 命令执行成功 - {_get_user_info(update)}") except ServiceError as exc: + logger.error(f"/stop 命令执行失败: {exc} - {_get_user_info(update)}") await self._reply(update, context, f"停止失败:{exc}") except Exception as exc: # noqa: BLE001 + logger.error(f"/stop 命令执行失败(未知错误): {exc} - {_get_user_info(update)}") await self._reply(update, context, f"停止失败,发生未知错误:{exc}") async def restart_service(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + logger.info(f"收到 /restart 命令 - {_get_user_info(update)}") try: self.service.restart() await self._reply(update, context, "aria2 服务已重启 ✅") + logger.info(f"/restart 命令执行成功 - {_get_user_info(update)}") except ServiceError as exc: + logger.error(f"/restart 命令执行失败: {exc} - {_get_user_info(update)}") await self._reply(update, context, f"重启失败:{exc}") except Exception as exc: # noqa: BLE001 + logger.error(f"/restart 命令执行失败(未知错误): {exc} - {_get_user_info(update)}") await self._reply(update, context, f"重启失败,发生未知错误:{exc}") async def status(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + logger.info(f"收到 /status 命令 - {_get_user_info(update)}") try: info = self.service.status() version = get_aria2_version() or "未知" rpc_secret = self._get_rpc_secret() or "未设置" rpc_port = self._get_rpc_port() or self.config.rpc_port or "未知" except ServiceError as exc: + logger.error(f"/status 命令执行失败: {exc} - {_get_user_info(update)}") await self._reply(update, context, f"获取状态失败:{exc}") return except Exception as exc: # noqa: BLE001 + logger.error(f"/status 命令执行失败(未知错误): {exc} - {_get_user_info(update)}") await self._reply(update, context, f"获取状态失败,发生未知错误:{exc}") return @@ -156,33 +193,44 @@ class Aria2BotAPI: f"- RPC 密钥:`{rpc_secret}`" ) await self._reply(update, context, text, parse_mode="Markdown") + logger.info(f"/status 命令执行成功 - {_get_user_info(update)}") async def view_logs(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + logger.info(f"收到 /logs 命令 - {_get_user_info(update)}") try: logs = self.service.view_log(lines=30) except ServiceError as exc: + logger.error(f"/logs 命令执行失败: {exc} - {_get_user_info(update)}") await self._reply(update, context, f"读取日志失败:{exc}") return except Exception as exc: # noqa: BLE001 + logger.error(f"/logs 命令执行失败(未知错误): {exc} - {_get_user_info(update)}") await self._reply(update, context, f"读取日志失败,发生未知错误:{exc}") return if not logs.strip(): await self._reply(update, context, "暂无日志内容。") + logger.info(f"/logs 命令执行成功(无日志) - {_get_user_info(update)}") return await self._reply(update, context, f"最近 30 行日志:\n{logs}") + logger.info(f"/logs 命令执行成功 - {_get_user_info(update)}") async def clear_logs(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + logger.info(f"收到 /clear_logs 命令 - {_get_user_info(update)}") try: self.service.clear_log() await self._reply(update, context, "日志已清空 ✅") + logger.info(f"/clear_logs 命令执行成功 - {_get_user_info(update)}") except ServiceError as exc: + logger.error(f"/clear_logs 命令执行失败: {exc} - {_get_user_info(update)}") await self._reply(update, context, f"清空日志失败:{exc}") except Exception as exc: # noqa: BLE001 + logger.error(f"/clear_logs 命令执行失败(未知错误): {exc} - {_get_user_info(update)}") await self._reply(update, context, f"清空日志失败,发生未知错误:{exc}") async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + logger.info(f"收到 /help 命令 - {_get_user_info(update)}") commands = [ "/install - 安装 aria2", "/uninstall - 卸载 aria2",