feat(logging): 为 aria2 安装器、服务和 Telegram 处理器添加详细日志记录

This commit is contained in:
dnslin
2025-12-12 08:39:36 +08:00
parent 3b2e0ee822
commit 342aef7e1e
3 changed files with 96 additions and 0 deletions

View File

@@ -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"})

View File

@@ -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,

View File

@@ -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",