From 32c175ec2ec173d9a84fe2d6a9087c8ec6d3be54 Mon Sep 17 00:00:00 2001 From: lanyeeee Date: Wed, 4 Mar 2026 04:59:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BB=99=E6=97=A5=E5=BF=97=E5=92=8C?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF=E8=A1=A5=E5=85=85=E4=B8=B0?= =?UTF-8?q?=E5=AF=8C=E7=9A=84SpanTrace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/bili_client.rs | 41 +++- src-tauri/src/commands.rs | 28 ++- src-tauri/src/config.rs | 3 + .../src/danmaku_xml_to_ass/ass_writer.rs | 12 +- src-tauri/src/danmaku_xml_to_ass/mod.rs | 4 + .../src/downloader/download_chunk_task.rs | 13 ++ src-tauri/src/downloader/download_manager.rs | 57 ++++-- src-tauri/src/downloader/download_progress.rs | 83 ++++---- src-tauri/src/downloader/download_task.rs | 188 ++++++++++++++---- src-tauri/src/downloader/episode_info.rs | 2 + src-tauri/src/downloader/fmt_params.rs | 2 + src-tauri/src/downloader/tasks/audio_task.rs | 88 ++++---- src-tauri/src/downloader/tasks/cover_task.rs | 2 + .../src/downloader/tasks/danmaku_task.rs | 5 +- src-tauri/src/downloader/tasks/json_task.rs | 2 + src-tauri/src/downloader/tasks/nfo_task.rs | 21 +- .../src/downloader/tasks/subtitle_task.rs | 2 + .../downloader/tasks/video_process_task.rs | 17 +- src-tauri/src/downloader/tasks/video_task.rs | 67 ++++--- src-tauri/src/errors.rs | 2 + src-tauri/src/extensions.rs | 2 + src-tauri/src/logger.rs | 15 +- src-tauri/src/types/bangumi_info.rs | 8 +- .../src/types/get_bangumi_info_params.rs | 16 ++ src-tauri/src/types/get_cheese_info_params.rs | 16 ++ src-tauri/src/types/get_normal_info_params.rs | 16 ++ src-tauri/src/utils.rs | 4 + src-tauri/src/wbi.rs | 5 +- 28 files changed, 519 insertions(+), 202 deletions(-) diff --git a/src-tauri/src/bili_client.rs b/src-tauri/src/bili_client.rs index 4a5d8c1..48f4934 100644 --- a/src-tauri/src/bili_client.rs +++ b/src-tauri/src/bili_client.rs @@ -15,6 +15,7 @@ use tauri::{ http::{HeaderMap, HeaderValue}, }; use tokio::task::JoinSet; +use tracing::{Instrument, instrument}; use crate::{ config::ProxyMode, @@ -74,6 +75,7 @@ impl BiliClient { *self.content_length_client.write() = content_length_client; } + #[instrument(level = "error", skip_all)] pub async fn generate_qrcode(&self) -> eyre::Result { // 发送生成二维码请求 let request = self @@ -106,6 +108,7 @@ impl BiliClient { Ok(qrcode_data) } + #[instrument(level = "error", skip_all)] pub async fn get_qrcode_status(&self, qrcode_key: &str) -> eyre::Result { // 发送获取二维码状态请求 let params = json!({"qrcode_key": qrcode_key}); @@ -142,6 +145,7 @@ impl BiliClient { Ok(qrcode_status) } + #[instrument(level = "error", skip_all)] pub async fn get_user_info(&self, sessdata: &str) -> eyre::Result { // 发送获取用户信息的请求 let request = self @@ -177,6 +181,7 @@ impl BiliClient { Ok(user_info) } + #[instrument(level = "error", skip_all, fields(bvid = params.get_bvid(), aid = params.get_aid()))] pub async fn get_normal_info(&self, params: GetNormalInfoParams) -> eyre::Result { use GetNormalInfoParams::{Aid, Bvid}; let params = match params { @@ -216,6 +221,7 @@ impl BiliClient { Ok(normal_info) } + #[instrument(level = "error", skip_all, fields(ep_id = params.get_ep_id(), season_id = params.get_season_id()))] pub async fn get_bangumi_info( &self, params: GetBangumiInfoParams, @@ -258,6 +264,7 @@ impl BiliClient { Ok(bangumi_info) } + #[instrument(level = "error", skip_all, fields(ep_id = params.get_ep_id(), season_id = params.get_season_id()))] pub async fn get_cheese_info(&self, params: GetCheeseInfoParams) -> eyre::Result { use GetCheeseInfoParams::{EpId, SeasonId}; let params = match params { @@ -297,6 +304,7 @@ impl BiliClient { Ok(cheese_info) } + #[instrument(level = "error", skip_all)] pub async fn get_user_video_info( &self, params: GetUserVideoInfoParams, @@ -358,6 +366,7 @@ impl BiliClient { Ok(user_video_info) } + #[instrument(level = "error", skip_all, fields(bvid = bvid, cid = cid))] pub async fn get_normal_url(&self, bvid: &str, cid: i64) -> eyre::Result { let params = json!({ "bvid": bvid, @@ -398,6 +407,7 @@ impl BiliClient { Ok(media_url) } + #[instrument(level = "error", skip_all, fields(cid = cid))] pub async fn get_bangumi_url(&self, cid: i64) -> eyre::Result { let media_url_v2 = self.get_bangumi_url_v2(cid).await?; if media_url_v2.video_info.is_drm { @@ -407,6 +417,7 @@ impl BiliClient { } } + #[instrument(level = "error", skip_all, fields(cid = cid))] async fn get_bangumi_url_v1(&self, cid: i64) -> eyre::Result { let params = json!({ "cid": cid, @@ -449,6 +460,7 @@ impl BiliClient { Ok(media_url) } + #[instrument(level = "error", skip_all, fields(cid = cid))] async fn get_bangumi_url_v2(&self, cid: i64) -> eyre::Result { let params = json!({ "cid": cid, @@ -492,6 +504,7 @@ impl BiliClient { Ok(media_url) } + #[instrument(level = "error", skip_all, fields(ep_id = ep_id))] pub async fn get_cheese_url(&self, ep_id: i64) -> eyre::Result { let params = json!({ "ep_id": ep_id, @@ -534,6 +547,7 @@ impl BiliClient { Ok(media_url) } + #[instrument(level = "error", skip_all, fields(aid = aid, cid = cid))] pub async fn get_player_info(&self, aid: i64, cid: i64) -> eyre::Result { let params = json!({ "aid": aid, @@ -572,6 +586,7 @@ impl BiliClient { Ok(player_info) } + #[instrument(level = "error", skip_all)] pub async fn get_fav_folders(&self, uid: i64) -> eyre::Result { let params = json!({"up_mid": uid}); // 发送获取收藏夹信息的请求 @@ -607,6 +622,7 @@ impl BiliClient { Ok(fav_folders) } + #[instrument(level = "error", skip_all)] pub async fn get_fav_info(&self, params: GetFavInfoParams) -> eyre::Result { let params = json!({ "media_id": params.media_list_id, @@ -647,6 +663,7 @@ impl BiliClient { Ok(fav_info) } + #[instrument(level = "error", skip_all)] pub async fn get_watch_later_info(&self, page: i32) -> eyre::Result { // 发送获取稍后观看信息的请求 let params = json!({"ps": 20, "pn": page}); @@ -682,6 +699,7 @@ impl BiliClient { Ok(watch_later_info) } + #[instrument(level = "error", skip_all)] pub async fn get_bangumi_follow_info( &self, params: GetBangumiFollowInfoParams, @@ -726,6 +744,7 @@ impl BiliClient { Ok(bangumi_follow_info) } + #[instrument(level = "error", skip_all)] pub async fn get_history_info( &self, params: GetHistoryInfoParams, @@ -773,6 +792,7 @@ impl BiliClient { Ok(history_info) } + #[instrument(level = "error", skip_all)] pub async fn get_media_chunk( &self, media_url: &str, @@ -796,7 +816,9 @@ impl BiliClient { Ok(bytes) } + #[instrument(level = "error", skip_all, fields(media_url = media_url))] pub async fn get_content_length(&self, media_url: &str) -> eyre::Result { + #[instrument(level = "error", skip_all)] fn parse_content_length(headers: &HeaderMap) -> eyre::Result { headers .get("Content-Length") @@ -807,6 +829,7 @@ impl BiliClient { .wrap_err("Content-Length 响应头无法转换为整数") } + #[instrument(level = "error", skip_all)] fn parse_total_from_content_range(headers: &HeaderMap) -> eyre::Result { // Example: "bytes 0-0/12345" let content_range = headers @@ -854,19 +877,21 @@ impl BiliClient { Err(eyre!("预料之外的状态码({status})")) } + #[instrument(level = "error", skip_all)] pub async fn get_url_with_content_length(&self, urls: Vec) -> Vec<(String, u64)> { let mut url_with_content_length = Vec::new(); let mut join_set = JoinSet::new(); for url in urls { let app = self.app.clone(); - join_set.spawn(async move { + let get_content_length_task = async move { let bili_client = app.get_bili_client(); let Ok(content_length) = bili_client.get_content_length(&url).await else { return None; }; Some((url, content_length)) - }); + }; + join_set.spawn(get_content_length_task.in_current_span()); } while let Some(join_result) = join_set.join_next().await { @@ -880,6 +905,7 @@ impl BiliClient { url_with_content_length } + #[instrument(level = "error", skip_all, fields(aid = aid, cid = cid, duration = duration))] pub async fn get_danmaku( &self, aid: i64, @@ -895,7 +921,9 @@ impl BiliClient { let client = client.clone(); let cookie = self.get_cookie(); - join_set.spawn(async move { + let segment_span = + tracing::error_span!("get_danmaku_segment", segment_index = segment_index); + let segment_task = async move { // 发送获取分段弹幕的请求 let params = json!({ "type": 1, @@ -919,7 +947,8 @@ impl BiliClient { DmSegMobileReply::decode(body).wrap_err("将body解析为DmSegMobileReply失败")?; Ok(reply) - }); + }; + join_set.spawn(segment_task.instrument(segment_span)); } let mut replies = Vec::new(); @@ -935,6 +964,7 @@ impl BiliClient { Ok(replies) } + #[instrument(level = "error", skip_all, fields(url = url))] pub async fn get_subtitle(&self, url: &str) -> eyre::Result { let request = self.api_client.read().get(url); let http_resp = request.send().await?; @@ -950,6 +980,7 @@ impl BiliClient { Ok(subtitle) } + #[instrument(level = "error", skip_all, fields(url = url))] pub async fn get_cover_data_and_ext(&self, url: &str) -> eyre::Result<(Bytes, String)> { let request = self.api_client.read().get(url); let http_resp = request.send().await?; @@ -980,6 +1011,7 @@ impl BiliClient { Ok((bytes, ext.to_string())) } + #[instrument(level = "error", skip_all, fields(aid = aid))] pub async fn get_tags(&self, aid: i64) -> eyre::Result { // 发送获取普通视频标签的请求 let params = json!({"aid": aid}); @@ -1015,6 +1047,7 @@ impl BiliClient { Ok(tags) } + #[instrument(level = "error", skip_all, fields(bvid = bvid, cid = cid))] pub async fn get_skip_segments( &self, bvid: &str, diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index cab4a8e..506c291 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -2,6 +2,7 @@ use eyre::WrapErr; use parking_lot::RwLock; use tauri::AppHandle; use tauri_plugin_opener::OpenerExt; +use tracing::instrument; use crate::{ config::Config, @@ -43,6 +44,8 @@ use crate::{ #[tauri::command(async)] #[specta::specta] #[allow(clippy::needless_pass_by_value)] +#[instrument(level = "error", skip_all)] +// TODO: 改成 app.get_config_manager().read().clone() pub fn get_config(config: tauri::State>) -> Config { config.read().clone() } @@ -50,6 +53,7 @@ pub fn get_config(config: tauri::State>) -> Config { #[tauri::command(async)] #[specta::specta] #[allow(clippy::needless_pass_by_value)] +#[instrument(level = "error", skip_all)] pub fn save_config(app: AppHandle, config: Config) -> CommandResult<()> { let bili_client = app.get_bili_client(); let config_state = app.get_config(); @@ -94,6 +98,7 @@ pub fn save_config(app: AppHandle, config: Config) -> CommandResult<()> { #[allow(clippy::needless_pass_by_value)] #[tauri::command(async)] #[specta::specta] +#[instrument(level = "error", skip_all)] pub async fn generate_qrcode(app: AppHandle) -> CommandResult { let bili_client = app.get_bili_client(); let qrcode_data = bili_client @@ -106,6 +111,7 @@ pub async fn generate_qrcode(app: AppHandle) -> CommandResult { #[allow(clippy::needless_pass_by_value)] #[tauri::command(async)] #[specta::specta] +#[instrument(level = "error", skip_all)] pub async fn get_qrcode_status(app: AppHandle, qrcode_key: String) -> CommandResult { let bili_client = app.get_bili_client(); let qrcode_status = bili_client @@ -117,6 +123,7 @@ pub async fn get_qrcode_status(app: AppHandle, qrcode_key: String) -> CommandRes #[tauri::command(async)] #[specta::specta] +#[instrument(level = "error", skip_all)] pub async fn get_user_info(app: AppHandle, sessdata: String) -> CommandResult { let bili_client = app.get_bili_client(); let user_info = bili_client @@ -128,6 +135,7 @@ pub async fn get_user_info(app: AppHandle, sessdata: String) -> CommandResult CommandResult { let bili_client = app.get_bili_client(); let fav_folders = bili_client @@ -181,6 +192,7 @@ pub async fn get_fav_folders(app: AppHandle, uid: i64) -> CommandResult CommandResult { let bili_client = app.get_bili_client(); let fav_info = bili_client @@ -192,6 +204,7 @@ pub async fn get_fav_info(app: AppHandle, params: GetFavInfoParams) -> CommandRe #[tauri::command(async)] #[specta::specta] +#[instrument(level = "error", skip_all)] pub async fn get_watch_later_info(app: AppHandle, page: i32) -> CommandResult { let bili_client = app.get_bili_client(); let watch_later_info = bili_client @@ -203,6 +216,7 @@ pub async fn get_watch_later_info(app: AppHandle, page: i32) -> CommandResult) { let download_manager = app.get_download_manager(); download_manager.pause_download_tasks(&task_ids); @@ -249,6 +265,7 @@ pub fn pause_download_tasks(app: AppHandle, task_ids: Vec) { #[allow(clippy::needless_pass_by_value)] #[tauri::command(async)] #[specta::specta] +#[instrument(level = "error", skip_all)] pub fn resume_download_tasks(app: AppHandle, task_ids: Vec) { let download_manager = app.get_download_manager(); download_manager.resume_download_tasks(&task_ids); @@ -257,6 +274,7 @@ pub fn resume_download_tasks(app: AppHandle, task_ids: Vec) { #[allow(clippy::needless_pass_by_value)] #[tauri::command(async)] #[specta::specta] +#[instrument(level = "error", skip_all)] pub fn delete_download_tasks(app: AppHandle, task_ids: Vec) { let download_manager = app.get_download_manager(); download_manager.delete_download_tasks(&task_ids); @@ -265,6 +283,7 @@ pub fn delete_download_tasks(app: AppHandle, task_ids: Vec) { #[allow(clippy::needless_pass_by_value)] #[tauri::command(async)] #[specta::specta] +#[instrument(level = "error", skip_all)] pub fn restart_download_tasks(app: AppHandle, task_ids: Vec) { let download_manager = app.get_download_manager(); download_manager.restart_download_tasks(&task_ids); @@ -273,6 +292,7 @@ pub fn restart_download_tasks(app: AppHandle, task_ids: Vec) { #[allow(clippy::needless_pass_by_value)] #[tauri::command(async)] #[specta::specta] +#[instrument(level = "error", skip_all, fields(task_id = params.task_id))] pub fn restart_download_task(app: AppHandle, params: RestartDownloadTaskParams) { let download_manager = app.get_download_manager(); download_manager.restart_download_task(¶ms); @@ -281,6 +301,7 @@ pub fn restart_download_task(app: AppHandle, params: RestartDownloadTaskParams) #[allow(clippy::needless_pass_by_value)] #[tauri::command(async)] #[specta::specta] +#[instrument(level = "error", skip_all)] pub fn restore_download_tasks(app: AppHandle) -> CommandResult<()> { let download_manager = app.get_download_manager(); download_manager @@ -292,6 +313,7 @@ pub fn restore_download_tasks(app: AppHandle) -> CommandResult<()> { #[tauri::command(async)] #[specta::specta] +#[instrument(level = "error", skip_all)] pub async fn search(app: AppHandle, params: SearchParams) -> CommandResult { use SearchParams::{Bangumi, Cheese, Fav, Normal, UserVideo}; let bili_client = app.get_bili_client(); @@ -364,6 +386,7 @@ pub async fn search(app: AppHandle, params: SearchParams) -> CommandResult CommandResult { let logs_dir = logger::logs_dir(&app) .wrap_err("获取日志目录失败") @@ -382,6 +405,7 @@ pub fn get_logs_dir_size(app: AppHandle) -> CommandResult { #[allow(clippy::needless_pass_by_value)] #[tauri::command(async)] #[specta::specta] +#[instrument(level = "error", skip_all)] pub fn show_path_in_file_manager(app: AppHandle, path: &str) -> CommandResult<()> { app.opener() .reveal_item_in_dir(path) @@ -392,6 +416,7 @@ pub fn show_path_in_file_manager(app: AppHandle, path: &str) -> CommandResult<() #[tauri::command(async)] #[specta::specta] +#[instrument(level = "error", skip_all, fields(bvid = bvid, cid = cid))] pub async fn get_skip_segments( app: AppHandle, bvid: String, @@ -407,6 +432,7 @@ pub async fn get_skip_segments( #[tauri::command(async)] #[specta::specta] +#[instrument(level = "error", skip_all)] pub async fn get_available_media_formats( app: AppHandle, params: GetAvailableMediaFormatsParams, diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index e4795f4..0cc8844 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -3,6 +3,7 @@ use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; use specta::Type; use tauri::{AppHandle, Manager}; +use tracing::instrument; use crate::{ danmaku_xml_to_ass::canvas::CanvasConfig, @@ -47,6 +48,7 @@ pub struct Config { } impl Config { + #[instrument(level = "error", skip_all)] pub fn new(app: &AppHandle) -> eyre::Result { let app_data_dir = app.path().app_data_dir()?; let config_path = app_data_dir.join("config.json"); @@ -67,6 +69,7 @@ impl Config { Ok(config) } + #[instrument(level = "error", skip_all)] pub fn save(&self, app: &AppHandle) -> eyre::Result<()> { let app_data_dir = app.path().app_data_dir()?; let config_path = app_data_dir.join("config.json"); diff --git a/src-tauri/src/danmaku_xml_to_ass/ass_writer.rs b/src-tauri/src/danmaku_xml_to_ass/ass_writer.rs index ffa9727..9ac3529 100644 --- a/src-tauri/src/danmaku_xml_to_ass/ass_writer.rs +++ b/src-tauri/src/danmaku_xml_to_ass/ass_writer.rs @@ -1,8 +1,9 @@ -use eyre::Result; use std::borrow::Cow; use std::fmt; use std::io::{BufWriter, Write}; +use tracing::instrument; + use super::canvas::CanvasConfig; use super::drawable::{DrawEffect, Drawable}; @@ -99,7 +100,8 @@ pub struct AssWriter { } impl AssWriter { - pub fn new(f: W, title: String, canvas_config: CanvasConfig) -> Result { + #[instrument(level = "error", skip_all)] + pub fn new(f: W, title: String, canvas_config: CanvasConfig) -> eyre::Result { let mut this = AssWriter { // 对于 HDD、docker 之类的场景,磁盘 IO 是非常大的瓶颈。使用大缓存 f: BufWriter::with_capacity(10 << 20, f), @@ -112,7 +114,8 @@ impl AssWriter { Ok(this) } - pub fn init(&mut self) -> Result<()> { + #[instrument(level = "error", skip_all)] + pub fn init(&mut self) -> eyre::Result<()> { write!( self.f, "\ @@ -147,7 +150,8 @@ impl AssWriter { Ok(()) } - pub fn write(&mut self, drawable: Drawable) -> Result<()> { + #[instrument(level = "error", skip_all)] + pub fn write(&mut self, drawable: Drawable) -> eyre::Result<()> { writeln!( self.f, // Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text diff --git a/src-tauri/src/danmaku_xml_to_ass/mod.rs b/src-tauri/src/danmaku_xml_to_ass/mod.rs index e2b6887..a832dc3 100644 --- a/src-tauri/src/danmaku_xml_to_ass/mod.rs +++ b/src-tauri/src/danmaku_xml_to_ass/mod.rs @@ -9,6 +9,7 @@ use ass_writer::AssWriter; use canvas::CanvasConfig; use danmaku::{Danmaku, DanmakuType}; use eyre::eyre; +use tracing::instrument; use yaserde::{YaDeserialize, YaSerialize}; #[derive(YaSerialize, YaDeserialize)] @@ -28,6 +29,7 @@ pub struct DanmakuXmlITag { pub elems: Vec, } +#[instrument(level = "error", skip_all)] pub fn xml_to_ass( xml: &str, ass_file: File, @@ -58,6 +60,7 @@ trait ToDanmakuType { } impl ToDanmakuType for u32 { + #[instrument(level = "error", skip_all)] fn to_danmaku_type(&self) -> eyre::Result { match self { 1 => Ok(DanmakuType::Float), @@ -69,6 +72,7 @@ impl ToDanmakuType for u32 { } } +#[instrument(level = "error", skip_all)] pub fn xml_to_danmakus(xml: &str) -> eyre::Result> { let xml = sanitize_xml(xml); let i_tag: DanmakuXmlITag = yaserde::de::from_str(&xml).map_err(|e| eyre!(e))?; diff --git a/src-tauri/src/downloader/download_chunk_task.rs b/src-tauri/src/downloader/download_chunk_task.rs index d8075b2..753405b 100644 --- a/src-tauri/src/downloader/download_chunk_task.rs +++ b/src-tauri/src/downloader/download_chunk_task.rs @@ -7,6 +7,7 @@ use std::{ use parking_lot::Mutex; use tokio::{sync::SemaphorePermit, time::sleep}; +use tracing::instrument; use crate::{ downloader::{download_task::DownloadTask, download_task_state::DownloadTaskState}, @@ -23,6 +24,16 @@ pub struct DownloadChunkTask { } impl DownloadChunkTask { + #[instrument( + level = "error", + skip_all, + fields( + url = self.url, + chunk_index = ?self.chunk_index, + start = self.start, + end = self.end, + ) + )] pub async fn process(self) -> eyre::Result { let download_chunk_task = self.download_chunk(); tokio::pin!(download_chunk_task); @@ -64,6 +75,7 @@ impl DownloadChunkTask { } } + #[instrument(level = "error", skip_all)] async fn download_chunk(&self) -> eyre::Result { let bili_client = self.download_task.app.get_bili_client(); let chunk_data = bili_client @@ -94,6 +106,7 @@ impl DownloadChunkTask { Ok(self.chunk_index) } + #[instrument(level = "error", skip_all)] async fn acquire_chunk_permit<'a>( &'a self, permit: &mut Option>, diff --git a/src-tauri/src/downloader/download_manager.rs b/src-tauri/src/downloader/download_manager.rs index ad42d1c..aee4fd9 100644 --- a/src-tauri/src/downloader/download_manager.rs +++ b/src-tauri/src/downloader/download_manager.rs @@ -8,11 +8,12 @@ use std::{ time::Duration, }; -use eyre::WrapErr; +use eyre::{WrapErr, eyre}; use parking_lot::RwLock; use tauri::{AppHandle, Manager}; use tauri_specta::Event; use tokio::sync::Semaphore; +use tracing::instrument; use crate::{ events::DownloadEvent, @@ -59,6 +60,7 @@ impl DownloadManager { manager } + #[instrument(level = "error", skip_all)] pub fn restore_download_tasks(&self) -> eyre::Result<()> { let task_dir = self.get_task_dir()?; std::fs::create_dir_all(&task_dir) @@ -104,98 +106,121 @@ impl DownloadManager { } } + #[instrument(level = "error", skip_all)] pub fn pause_download_tasks(&self, task_ids: &Vec) { let tasks = self.download_tasks.read(); for task_id in task_ids { + let span = tracing::error_span!("pause_download_task", task_id = task_id); + let _enter = span.enter(); + let Some(task) = tasks.get(task_id) else { + let err = eyre!("未找到ID对应的下载任务"); let err_title = "暂停下载任务失败"; - let message = format!("未找到ID为`{task_id}`的下载任务"); + let message = err.to_message(); tracing::error!(err_title, message); continue; }; task.set_state(DownloadTaskState::Paused); - tracing::debug!("已将ID为`{task_id}`的下载任务状态设置为`Paused`"); + tracing::debug!("已将ID对应的下载任务状态设置为`Paused`"); } } + #[instrument(level = "error", skip_all)] pub fn resume_download_tasks(&self, task_ids: &Vec) { let tasks = self.download_tasks.read(); for task_id in task_ids { + let span = tracing::error_span!("resume_download_task", task_id = task_id); + let _enter = span.enter(); + let Some(task) = tasks.get(task_id) else { + let err = eyre!("未找到ID对应的下载任务"); let err_title = "继续下载任务失败"; - let message = format!("未找到ID为`{task_id}`的下载任务"); + let message = err.to_message(); tracing::error!(err_title, message); continue; }; task.set_state(DownloadTaskState::Pending); - tracing::debug!("已将ID为`{task_id}`的下载任务状态设置为`Pending`"); + tracing::debug!("已将ID对应的下载任务状态设置为`Pending`"); } } + #[instrument(level = "error", skip_all)] pub fn delete_download_tasks(&self, task_ids: &Vec) { let mut tasks = self.download_tasks.write(); for task_id in task_ids { + let span = tracing::error_span!("delete_download_task", task_id = task_id); + let _enter = span.enter(); + let Some(task) = tasks.remove(task_id) else { + let err = eyre!("未找到ID对应的下载任务"); let err_title = "删除下载任务失败"; - let message = format!("未找到ID为`{task_id}`的下载任务"); + let message = err.to_message(); tracing::error!(err_title, message); continue; }; if let Err(err) = self.delete_progress_file(task_id) { let err_title = "删除下载任务失败"; - let message = format!("删除ID为`{task_id}`的下载任务文件失败: {err}"); + let message = err.to_message(); tracing::error!(err_title, message); tasks.insert(task_id.clone(), task); continue; } if let Err(err) = task.delete_sender.send(()).map_err(eyre::Report::from) { + let err = err.wrap_err("通知ID对应的下载任务删除失败"); let err_title = "删除下载任务失败"; - let err = err.wrap_err(format!("通知ID为`{task_id}`的下载任务删除失败")); let message = err.to_message(); tracing::error!(err_title, message); tasks.insert(task_id.clone(), task); continue; } - tracing::debug!("已通知ID为`{task_id}`的下载任务删除"); + tracing::debug!("已通知ID对应的下载任务删除"); } } + #[instrument(level = "error", skip_all)] pub fn restart_download_tasks(&self, task_ids: &Vec) { let tasks = self.download_tasks.read(); for task_id in task_ids { + let span = tracing::error_span!("restart_download_task", task_id = task_id); + let _enter = span.enter(); + let Some(task) = tasks.get(task_id) else { + let err = eyre!("未找到ID对应的下载任务"); let err_title = "重来下载任务失败"; - let message = format!("未找到ID为`{task_id}`的下载任务"); + let message = err.to_message(); tracing::error!(err_title, message); continue; }; if let Err(err) = task.restart_sender.send(()).map_err(eyre::Report::from) { let err_title = "重来下载任务失败"; - let err = err.wrap_err(format!("通知ID为`{task_id}`的下载任务重来失败")); + let err = err.wrap_err("通知ID对应的下载任务重来失败"); let message = err.to_message(); tracing::error!(err_title, message); continue; } - tracing::debug!("已通知ID为`{task_id}`的下载任务重来"); + tracing::debug!("已通知ID对应的下载任务重来"); } } + #[instrument(level = "error", skip_all, fields(task_id = params.task_id))] pub fn restart_download_task(&self, params: &RestartDownloadTaskParams) { let task_id = ¶ms.task_id; let tasks = self.download_tasks.read(); let Some(task) = tasks.get(task_id) else { + let err = eyre!("未找到ID对应的下载任务"); let err_title = "重来下载任务失败"; - let message = format!("未找到ID为`{task_id}`的下载任务"); + let message = err.to_message(); tracing::error!(err_title, message); return; }; + // TODO: 把这块代码变成DownloadProgress的mark_restart函数 { let mut progress = task.progress.write(); @@ -219,13 +244,13 @@ impl DownloadManager { if let Err(err) = task.restart_sender.send(()).map_err(eyre::Report::from) { let err_title = "重来下载任务失败"; - let err = err.wrap_err(format!("通知ID为`{task_id}`的下载任务重来失败")); + let err = err.wrap_err("通知ID对应的下载任务重来失败"); let message = err.to_message(); tracing::error!(err_title, message); return; } - tracing::debug!("已通知ID为`{task_id}`的下载任务重来"); + tracing::debug!("已通知ID对应的下载任务重来"); } async fn emit_download_speed_loop(app: AppHandle, byte_per_sec: Arc) { @@ -241,12 +266,14 @@ impl DownloadManager { } } + #[instrument(level = "error", skip_all)] fn get_task_dir(&self) -> eyre::Result { let app_data_dir = self.app.path().app_data_dir()?; let task_dir = app_data_dir.join(".下载任务"); Ok(task_dir) } + #[instrument(level = "error", skip_all, fields(task_id = task_id))] fn delete_progress_file(&self, task_id: &str) -> eyre::Result<()> { let task_dir = self.get_task_dir()?; let task_file = task_dir.join(format!("{task_id}.json")); diff --git a/src-tauri/src/downloader/download_progress.rs b/src-tauri/src/downloader/download_progress.rs index fe516ca..1725ca4 100644 --- a/src-tauri/src/downloader/download_progress.rs +++ b/src-tauri/src/downloader/download_progress.rs @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; use specta::Type; use tauri::{AppHandle, Manager}; use tauri_specta::Event; +use tracing::instrument; use uuid::Uuid; use crate::{ @@ -71,6 +72,7 @@ pub struct DownloadProgress { } impl DownloadProgress { + #[instrument(level = "error", skip_all)] pub fn from_normal( app: &AppHandle, info: &NormalInfo, @@ -87,10 +89,11 @@ impl DownloadProgress { } #[allow(clippy::cast_possible_wrap)] + #[instrument(level = "error", skip_all)] pub fn from_bangumi(app: &AppHandle, info: &BangumiInfo, ep_id: i64) -> eyre::Result { let (episode, episode_order) = info.get_episode_with_order(ep_id)?; let Some(duration) = episode.duration else { - return Err(eyre!("找不到ep_id为`{ep_id}`的番剧的时长")); + return Err(eyre!("duration为None")); }; // 将毫秒转换为秒 let duration = duration / 1000; @@ -98,7 +101,6 @@ impl DownloadProgress { let config = app.get_config().read().clone(); let tasks = Tasks::new(&config, &episode.cover); - let (up_name, up_uid, up_avatar) = if let Some(up_info) = &info.up_info { ( Some(up_info.uname.clone()), @@ -147,12 +149,13 @@ impl DownloadProgress { Ok(progress) } + #[instrument(level = "error", skip_all)] pub fn from_cheese(app: &AppHandle, info: &CheeseInfo, ep_id: i64) -> eyre::Result { let episode = info .episodes .iter() .find(|ep| ep.id == ep_id) - .ok_or_eyre(format!("找不到ep_id为`{ep_id}`的课程"))?; + .ok_or_eyre("找不到ep_id对应的课程")?; let config = app.get_config().read().clone(); @@ -196,9 +199,8 @@ impl DownloadProgress { Ok(progress) } + #[instrument(level = "error", skip_all)] pub async fn process(&mut self, download_task: &Arc) -> eyre::Result<()> { - let ids_string = self.get_ids_string(); - let _ = DownloadEvent::ProgressPreparing { task_id: self.task_id.clone(), } @@ -211,12 +213,8 @@ impl DownloadProgress { self.completed_ts = None; // 重置完成时间戳 download_task.update_progress(|p| *p = self.clone()); - let (episode_dir, filename) = (&self.episode_dir, &self.filename); - - std::fs::create_dir_all(episode_dir).wrap_err(format!( - "{ids_string} 创建目录`{}`失败", - episode_dir.display() - ))?; + std::fs::create_dir_all(&self.episode_dir) + .wrap_err(format!("创建目录`{}`失败", self.episode_dir.display()))?; let video_task = &self.video_task; let audio_task = &self.audio_task; @@ -234,16 +232,16 @@ impl DownloadProgress { video_task .process(download_task, self) .await - .wrap_err(format!("{ids_string} `{filename}`下载视频文件失败"))?; - tracing::debug!("{ids_string} `{filename}`视频下载任务完成"); + .wrap_err("下载视频文件失败")?; + tracing::debug!("视频下载任务完成"); } if !audio_task.is_completed() && audio_task.content_length != 0 { audio_task .process(download_task, self) .await - .wrap_err(format!("{ids_string} `{filename}`下载音频文件失败"))?; - tracing::debug!("{ids_string} `{filename}`音频下载任务完成"); + .wrap_err("下载音频文件失败")?; + tracing::debug!("音频下载任务完成"); } let video_process_task_is_completed = video_process_task.is_completed(); @@ -252,55 +250,53 @@ impl DownloadProgress { p.video_process_task.skipped = true; p.video_process_task.completed = true; }); - tracing::debug!( - "{ids_string} `{filename}`受版权保护(DRM),无法处理,已跳过视频处理任务" - ); + tracing::debug!("受版权保护(DRM),无法处理,已跳过视频处理任务"); } else if !video_process_task_is_completed { video_process_task .process(download_task, self, &mut player_info) .await - .wrap_err(format!("{ids_string} `{filename}`视频处理失败"))?; - tracing::debug!("{ids_string} `{filename}`视频处理任务完成"); + .wrap_err("视频处理失败")?; + tracing::debug!("视频处理任务完成"); } if !danmaku_task.is_completed() { danmaku_task .process(download_task, self) .await - .wrap_err(format!("{ids_string} `{filename}`下载弹幕失败"))?; - tracing::debug!("{ids_string} `{filename}`弹幕下载任务完成"); + .wrap_err("下载弹幕失败")?; + tracing::debug!("弹幕下载任务完成"); } if !subtitle_task.is_completed() { subtitle_task .process(download_task, self, &mut player_info) .await - .wrap_err(format!("{ids_string} `{filename}`下载字幕失败"))?; - tracing::debug!("{ids_string} `{filename}`字幕下载任务完成"); + .wrap_err("下载字幕失败")?; + tracing::debug!("字幕下载任务完成"); } if !cover_task.is_completed() { cover_task .process(download_task, self) .await - .wrap_err(format!("{ids_string} `{filename}`下载封面失败"))?; - tracing::debug!("{ids_string} `{filename}`封面下载任务完成"); + .wrap_err("下载封面失败")?; + tracing::debug!("封面下载任务完成"); } if !nfo_task.is_completed() { nfo_task .process(download_task, self, &mut episode_info) .await - .wrap_err(format!("{ids_string} `{filename}`下载NFO失败"))?; - tracing::debug!("{ids_string} `{filename}`NFO下载任务完成"); + .wrap_err("下载NFO失败")?; + tracing::debug!("NFO下载任务完成"); } if !json_task.is_completed() { json_task .process(download_task, self, &mut episode_info) .await - .wrap_err(format!("{ids_string} `{filename}`下载JSON元数据失败"))?; - tracing::debug!("{ids_string} `{filename}`JSON元数据下载任务完成"); + .wrap_err("下载JSON元数据失败")?; + tracing::debug!("JSON元数据下载任务完成"); } let completed_ts = SystemTime::now() @@ -314,6 +310,7 @@ impl DownloadProgress { Ok(()) } + #[instrument(level = "error", skip_all)] async fn prepare(&mut self, app: &AppHandle) -> eyre::Result<()> { let video_selected = self.video_task.selected; let video_completed = self.video_task.completed; @@ -348,7 +345,7 @@ impl DownloadProgress { if audio_selected && !audio_completed { // 如果音频被选中且未完成,则准备音频任务 - self.audio_task.prepare_normal(app, &media_url).await?; + self.audio_task.prepare_normal(app, &media_url).await; } } EpisodeType::Bangumi => { @@ -366,7 +363,7 @@ impl DownloadProgress { if audio_selected && !audio_completed { // 如果音频被选中且未完成,则准备音频任务 - self.audio_task.prepare_bangumi(app, &media_url).await?; + self.audio_task.prepare_bangumi(app, &media_url).await; } } EpisodeType::Cheese => { @@ -388,7 +385,7 @@ impl DownloadProgress { if audio_selected && !audio_completed { // 如果音频被选中且未完成,则准备音频任务 - self.audio_task.prepare_cheese(app, &media_url).await?; + self.audio_task.prepare_cheese(app, &media_url).await; } } } @@ -399,6 +396,7 @@ impl DownloadProgress { Ok(()) } + #[instrument(level = "error", skip_all)] fn update_fmt_fields(&mut self, app: &AppHandle) -> eyre::Result<()> { let fmt_params = self.create_fmt_params(); @@ -435,6 +433,7 @@ impl DownloadProgress { } } + #[instrument(level = "error", skip_all)] pub fn save(&self, app: &AppHandle, allow_create: bool) -> eyre::Result<()> { let progress = self.clone(); let file_name = format!("{}.json", progress.task_id); @@ -475,17 +474,10 @@ impl DownloadProgress { self.nfo_task.mark_uncompleted(); self.json_task.mark_uncompleted(); } - - pub fn get_ids_string(&self) -> String { - let aid = self.aid; - let bvid = self.bvid.as_deref().unwrap_or("None"); - let cid = self.cid; - let ep_id = self.ep_id.map_or("None".to_string(), |id| id.to_string()); - format!("aid: {aid}, bvid: {bvid}, cid: {cid}, ep_id: {ep_id}") - } } #[allow(clippy::too_many_lines)] +#[instrument(level = "error", skip_all)] fn create_normal_progresses_for_single( info: &NormalInfo, cid: Option, @@ -498,7 +490,7 @@ fn create_normal_progresses_for_single( if let Some(cid) = cid { // 如果有cid,则说明是要下载单个分P let Some(page) = info.pages.iter().find(|p| p.cid == cid) else { - return Err(eyre!("找不到cid为`{cid}`的分P")); + return Err(eyre!("找不到cid对应的分P")); }; let progress = DownloadProgress { task_id: Uuid::new_v4().to_string(), @@ -615,6 +607,7 @@ fn create_normal_progresses_for_single( } #[allow(clippy::too_many_lines)] +#[instrument(level = "error", skip_all)] fn create_normal_progresses_for_season( ugc_season: &UgcSeason, info: &NormalInfo, @@ -626,7 +619,7 @@ fn create_normal_progresses_for_season( .sections .iter() .position(|s| s.episodes.iter().any(|e| e.aid == aid)) - .ok_or_eyre(format!("找不到含有aid为`{aid}`的ep的section"))?; + .ok_or_eyre("找不到含有对应aid的section")?; let section = &ugc_season.sections[section_index]; #[allow(clippy::cast_possible_wrap)] let (ep, episode_order) = section @@ -635,7 +628,7 @@ fn create_normal_progresses_for_season( .enumerate() .map(|(i, e)| (e, i as i64 + 1)) .find(|(e, _)| e.aid == aid) - .ok_or_eyre(format!("在section中找不到aid为`{aid}`的ep"))?; + .ok_or_eyre("在section中找不到aid对应的ep")?; let tasks = Tasks::new(config, &ep.arc.pic); @@ -644,7 +637,7 @@ fn create_normal_progresses_for_season( if let Some(cid) = cid { // 如果有cid,则说明是要下载单个分P let Some(page) = ep.pages.iter().find(|p| p.cid == cid) else { - return Err(eyre!("找不到cid为`{cid}`的分P")); + return Err(eyre!("找不到cid对应的分P")); }; let progress = DownloadProgress { task_id: Uuid::new_v4().to_string(), diff --git a/src-tauri/src/downloader/download_task.rs b/src-tauri/src/downloader/download_task.rs index 0100fb6..3fb0372 100644 --- a/src-tauri/src/downloader/download_task.rs +++ b/src-tauri/src/downloader/download_task.rs @@ -1,4 +1,4 @@ -use std::{sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; use eyre::WrapErr; use parking_lot::RwLock; @@ -8,8 +8,10 @@ use tokio::{ sync::{SemaphorePermit, watch}, time::sleep, }; +use tracing::instrument; use crate::{ + downloader::episode_type::EpisodeType, events::DownloadEvent, extensions::{AppHandleExt, EyreReportToMessage}, types::create_download_task_params::CreateDownloadTaskParams, @@ -24,10 +26,13 @@ pub struct DownloadTask { pub cancel_sender: watch::Sender<()>, pub delete_sender: watch::Sender<()>, pub task_id: String, + pub trace_fields: DownloadTaskTraceFields, pub progress: RwLock, } impl DownloadTask { + #[allow(clippy::too_many_lines)] + #[instrument(level = "error", skip_all)] pub fn from_params(app: &AppHandle, params: &CreateDownloadTaskParams) -> Vec> { use CreateDownloadTaskParams::{Bangumi, Cheese, Normal}; @@ -35,13 +40,22 @@ impl DownloadTask { match params { Normal(params) => { for &(aid, cid) in ¶ms.aid_cid_pairs { + let span = tracing::error_span!( + "from_params_normal", + aid = aid, + bvid = params.info.bvid, + cid = cid, + collection_title = params.info.title, + up_name = params.info.owner.name, + up_uid = params.info.owner.mid, + ); + let _enter = span.enter(); + let progress = match DownloadProgress::from_normal(app, ¶ms.info, aid, cid) { Ok(progress) => progress, Err(err) => { - let cid = cid.map_or("None".to_string(), |id| id.to_string()); - let ids_string = format!("aid: {aid}, cid: {cid}"); - let err_title = format!("{ids_string} 创建普通视频的下载进度失败"); + let err_title = "创建普通视频的下载进度失败"; let message = err.to_message(); tracing::error!(err_title, message); continue; @@ -53,11 +67,19 @@ impl DownloadTask { } Bangumi(params) => { for ep_id in ¶ms.ep_ids { + let span = tracing::error_span!( + "from_params_bangumi", + ep_id = ep_id, + collection_title = params.info.title, + up_name = params.info.up_info.as_ref().map(|up_info| &up_info.uname), + up_uid = params.info.up_info.as_ref().map(|up_info| up_info.mid), + ); + let _enter = span.enter(); + let progress = match DownloadProgress::from_bangumi(app, ¶ms.info, *ep_id) { Ok(progress) => progress, Err(err) => { - let ids_string = format!("ep_id: {ep_id}"); - let err_title = format!("{ids_string} 创建番剧的下载进度失败"); + let err_title = "创建番剧的下载进度失败"; let message = err.to_message(); tracing::error!(err_title, message); continue; @@ -69,11 +91,19 @@ impl DownloadTask { } Cheese(params) => { for ep_id in ¶ms.ep_ids { + let span = tracing::error_span!( + "from_params_cheese", + ep_id = ep_id, + collection_title = params.info.title, + up_name = params.info.up_info.uname, + up_uid = params.info.up_info.mid, + ); + let _enter = span.enter(); + let progress = match DownloadProgress::from_cheese(app, ¶ms.info, *ep_id) { Ok(progress) => progress, Err(err) => { - let ids_string = format!("ep_id: {ep_id}"); - let err_title = format!("{ids_string} 创建课程的下载进度失败"); + let err_title = "创建课程的下载进度失败"; let message = err.to_message(); tracing::error!(err_title, message); continue; @@ -87,10 +117,26 @@ impl DownloadTask { let mut tasks = Vec::new(); for progress in progresses { + let span = tracing::error_span!( + "create_tasks", + task_id = progress.task_id, + episode_type = ?progress.episode_type, + aid = progress.aid, + bvid = progress.bvid, + cid = progress.cid, + ep_id = progress.ep_id, + collection_title = progress.collection_title, + episode_title = progress.episode_title, + episode_order = progress.episode_order, + part_title = progress.part_title, + part_order = progress.part_order, + up_name = progress.up_name, + up_uid = progress.up_uid, + ); + let _enter = span.enter(); + if let Err(err) = progress.save(app, true) { - let ids_string = progress.get_ids_string(); - let episode_title = &progress.episode_title; - let err_title = format!("{ids_string} `{episode_title}`保存下载进度到文件失败"); + let err_title = "保存下载进度到文件失败"; let message = err.to_message(); tracing::error!(err_title, message); } @@ -114,6 +160,7 @@ impl DownloadTask { cancel_sender, delete_sender, task_id: progress.task_id.clone(), + trace_fields: DownloadTaskTraceFields::from(&progress), progress: RwLock::new(progress), }); @@ -143,6 +190,7 @@ impl DownloadTask { cancel_sender, delete_sender, task_id: progress.task_id.clone(), + trace_fields: DownloadTaskTraceFields::from(&progress), progress: RwLock::new(progress), }); @@ -151,8 +199,26 @@ impl DownloadTask { task } + #[instrument( + level = "error", + skip_all, + fields( + task_id = self.trace_fields.task_id, + episode_type = ?self.trace_fields.episode_type, + aid = self.trace_fields.aid, + bvid = self.trace_fields.bvid, + cid = self.trace_fields.cid, + ep_id = self.trace_fields.ep_id, + collection_title = self.trace_fields.collection_title, + episode_title = self.trace_fields.episode_title, + episode_order = self.trace_fields.episode_order, + part_title = self.trace_fields.part_title, + part_order = self.trace_fields.part_order, + up_name = self.trace_fields.up_name, + up_uid = self.trace_fields.up_uid, + ) + )] async fn process(self: Arc) { - let task_id = &self.task_id; let state = *self.state_sender.borrow(); let progress = self.progress.read().clone(); let _ = DownloadEvent::TaskCreate { state, progress }.emit(&self.app); @@ -194,7 +260,7 @@ impl DownloadTask { _ = restart_receiver.changed() => { self.handle_restart_notify(); - tracing::debug!("ID为`{task_id}`的下载任务已重来"); + tracing::debug!("下载任务已重来"); download_task_option = None; } @@ -212,31 +278,30 @@ impl DownloadTask { sleep(Duration::from_millis(100)).await; } - tracing::debug!("ID为`{task_id}`的下载任务已删除"); + tracing::debug!("下载任务已删除"); return; } } } } + #[instrument(level = "error", skip_all)] async fn download(self: &Arc) { let mut progress = self.progress.read().clone(); - let ids_string = progress.get_ids_string(); - let episode_title = progress.episode_title.clone(); if progress.is_completed() { - tracing::info!("{ids_string} 跳过`{episode_title}`的下载,因为它已经完成"); + tracing::info!("跳过下载,因为下载任务已完成"); self.set_state(DownloadTaskState::Completed); return; } - tracing::debug!("{ids_string} 开始下载`{episode_title}`"); + tracing::debug!("开始下载"); if let Err(err) = progress .process(self) .await .wrap_err("[继续]失败的任务可以断点续传") { - let err_title = format!("{ids_string} `{episode_title}`下载失败"); + let err_title = "下载失败"; let message = err.to_message(); tracing::error!(err_title, message); @@ -248,7 +313,7 @@ impl DownloadTask { self.sleep_between_task().await; self.set_state(DownloadTaskState::Completed); - tracing::info!("{ids_string} `{episode_title}`下载完成"); + tracing::info!("下载成功"); } async fn sleep_between_task(&self) { @@ -266,12 +331,8 @@ impl DownloadTask { } } + #[instrument(level = "error", skip_all)] async fn acquire_task_permit<'a>(&'a self, permit: &mut Option>) { - let (episode_title, ids_string) = { - let progress = self.progress.read(); - (progress.episode_title.clone(), progress.get_ids_string()) - }; - *permit = match permit.take() { // 如果有permit,则直接用 Some(permit) => Some(permit), @@ -287,8 +348,7 @@ impl DownloadTask { { Ok(permit) => Some(permit), Err(err) => { - let err_title = - format!("{ids_string} `{episode_title}`获取下载任务的permit失败"); + let err_title = "获取下载任务的permit失败"; let message = err.to_message(); tracing::error!(err_title, message); @@ -308,7 +368,7 @@ impl DownloadTask { .send(DownloadTaskState::Downloading) .map_err(eyre::Report::from) { - let err_title = format!("{ids_string} `{episode_title}`发送状态`Downloading`失败"); + let err_title = "发送状态`Downloading`失败"; let message = err.to_message(); tracing::error!(err_title, message); @@ -316,6 +376,7 @@ impl DownloadTask { } } + #[instrument(level = "error", skip_all)] async fn handle_state_change<'a>( &'a self, permit: &mut Option>, @@ -329,14 +390,14 @@ impl DownloadTask { // 稍微等一下再释放permit // 避免大批量暂停时,本应暂停的任务因拿到permit而稍微下载一小段(虽然最终会被暂停) sleep(Duration::from_millis(100)).await; - let task_id = &self.task_id; - tracing::debug!("ID为`{task_id}`的下载任务已暂停"); + tracing::debug!("下载任务已暂停"); if let Some(permit) = permit.take() { drop(permit); } } } + #[instrument(level = "error", skip_all)] fn handle_restart_notify(&self) { self.update_progress(|p| { p.mark_uncompleted(); @@ -344,19 +405,34 @@ impl DownloadTask { self.set_state(DownloadTaskState::Pending); } + #[instrument( + level = "error", + skip_all, + fields( + task_id = self.trace_fields.task_id, + episode_type = ?self.trace_fields.episode_type, + aid = self.trace_fields.aid, + bvid = self.trace_fields.bvid, + cid = self.trace_fields.cid, + ep_id = self.trace_fields.ep_id, + collection_title = self.trace_fields.collection_title, + episode_title = self.trace_fields.episode_title, + episode_order = self.trace_fields.episode_order, + part_title = self.trace_fields.part_title, + part_order = self.trace_fields.part_order, + up_name = self.trace_fields.up_name, + up_uid = self.trace_fields.up_uid, + ) + )] pub fn set_state(&self, state: DownloadTaskState) { - let (episode_title, ids_string) = { - let progress = self.progress.read(); - (progress.episode_title.clone(), progress.get_ids_string()) - }; - if let Err(err) = self.state_sender.send(state).map_err(eyre::Report::from) { - let err_title = format!("{ids_string} `{episode_title}`发送状态`{state:?}`失败"); + let err_title = format!("发送状态`{state:?}`失败"); let message = err.to_message(); tracing::error!(err_title, message); } } + #[instrument(level = "error", skip_all)] pub fn update_progress(&self, update_fn: impl FnOnce(&mut DownloadProgress)) { // 修改数据 let updated_progress = { @@ -371,11 +447,45 @@ impl DownloadTask { .emit(&self.app); if let Err(err) = updated_progress.save(&self.app, false) { - let ids_string = updated_progress.get_ids_string(); - let episode_title = &updated_progress.episode_title; - let err_title = format!("{ids_string} `{episode_title}`保存下载进度到文件失败"); + let err_title = "保存下载进度到文件失败"; let message = err.to_message(); tracing::error!(err_title, message); } } } + +pub struct DownloadTaskTraceFields { + pub task_id: String, + pub episode_type: EpisodeType, + pub aid: i64, + pub bvid: Option, + pub cid: i64, + pub ep_id: Option, + pub collection_title: String, + pub episode_title: String, + pub episode_order: i64, + pub part_title: Option, + pub part_order: Option, + pub up_name: Option, + pub up_uid: Option, +} + +impl From<&DownloadProgress> for DownloadTaskTraceFields { + fn from(progress: &DownloadProgress) -> Self { + Self { + task_id: progress.task_id.clone(), + episode_type: progress.episode_type, + aid: progress.aid, + bvid: progress.bvid.clone(), + cid: progress.cid, + ep_id: progress.ep_id, + collection_title: progress.collection_title.clone(), + episode_title: progress.episode_title.clone(), + episode_order: progress.episode_order, + part_title: progress.part_title.clone(), + part_order: progress.part_order, + up_name: progress.up_name.clone(), + up_uid: progress.up_uid, + } + } +} diff --git a/src-tauri/src/downloader/episode_info.rs b/src-tauri/src/downloader/episode_info.rs index 61edc8d..bfe6f6c 100644 --- a/src-tauri/src/downloader/episode_info.rs +++ b/src-tauri/src/downloader/episode_info.rs @@ -1,5 +1,6 @@ use eyre::{OptionExt, WrapErr}; use tauri::AppHandle; +use tracing::instrument; use crate::{ downloader::{download_progress::DownloadProgress, episode_type::EpisodeType}, @@ -27,6 +28,7 @@ pub trait GetOrInitEpisodeInfo { } impl GetOrInitEpisodeInfo for Option { + #[instrument(level = "error", skip_all)] async fn get_or_init<'a>( &'a mut self, app: &AppHandle, diff --git a/src-tauri/src/downloader/fmt_params.rs b/src-tauri/src/downloader/fmt_params.rs index 1b80935..71e3a1d 100644 --- a/src-tauri/src/downloader/fmt_params.rs +++ b/src-tauri/src/downloader/fmt_params.rs @@ -3,6 +3,7 @@ use std::{collections::HashMap, path::PathBuf}; use eyre::{OptionExt, WrapErr}; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; +use tracing::instrument; use crate::{ config::Config, @@ -36,6 +37,7 @@ pub struct FmtParams { } impl FmtParams { + #[instrument(level = "error", skip_all)] pub fn get_episode_dir_and_filename(&self, config: &Config) -> eyre::Result<(PathBuf, String)> { use strfmt::strfmt; diff --git a/src-tauri/src/downloader/tasks/audio_task.rs b/src-tauri/src/downloader/tasks/audio_task.rs index 38840c4..c63c090 100644 --- a/src-tauri/src/downloader/tasks/audio_task.rs +++ b/src-tauri/src/downloader/tasks/audio_task.rs @@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize}; use specta::Type; use tauri::AppHandle; use tokio::task::JoinSet; +use tracing::{Instrument, instrument}; use crate::{ config::FileExistAction, @@ -41,11 +42,7 @@ pub struct AudioTask { } impl AudioTask { - pub async fn prepare_normal( - &mut self, - app: &AppHandle, - media_url: &NormalMediaUrl, - ) -> eyre::Result<()> { + pub async fn prepare_normal(&mut self, app: &AppHandle, media_url: &NormalMediaUrl) { let mut join_set = JoinSet::new(); if let Some(medias) = &media_url.dash.audio { @@ -57,7 +54,7 @@ impl AudioTask { urls.extend_from_slice(&media.backup_url); urls.push(media.base_url.clone()); - join_set.spawn(async move { + let get_url_with_content_length_task = async move { let bili_client = app.get_bili_client(); let url_with_content_length = bili_client.get_url_with_content_length(urls).await; @@ -65,7 +62,9 @@ impl AudioTask { id, url_with_content_length, } - }); + }; + + join_set.spawn(get_url_with_content_length_task.in_current_span()); } } @@ -78,7 +77,7 @@ impl AudioTask { urls.extend_from_slice(&media.backup_url); urls.push(media.base_url.clone()); - join_set.spawn(async move { + let get_url_with_content_length_task = async move { let bili_client = app.get_bili_client(); let url_with_content_length = bili_client.get_url_with_content_length(urls).await; @@ -86,7 +85,9 @@ impl AudioTask { id, url_with_content_length, } - }); + }; + + join_set.spawn(get_url_with_content_length_task.in_current_span()); } } @@ -99,14 +100,16 @@ impl AudioTask { urls.extend_from_slice(&media.backup_url); urls.push(media.base_url.clone()); - join_set.spawn(async move { + let get_url_with_content_length_task = async move { let bili_client = app.get_bili_client(); let url_with_content_length = bili_client.get_url_with_content_length(urls).await; MediaForPrepare { id, url_with_content_length, } - }); + }; + + join_set.spawn(get_url_with_content_length_task.in_current_span()); } let mut medias: Vec = Vec::new(); @@ -122,31 +125,25 @@ impl AudioTask { } self.prepare(app, &medias); - - Ok(()) } - pub async fn prepare_bangumi( - &mut self, - app: &AppHandle, - media_url: &BangumiMediaUrl, - ) -> eyre::Result<()> { + pub async fn prepare_bangumi(&mut self, app: &AppHandle, media_url: &BangumiMediaUrl) { let Some(dash) = &media_url.dash else { // 如果没有音频,则直接返回 self.completed = true; - return Ok(()); + return; }; let Some(medias) = &dash.audio else { // 如果没有音频,则直接返回 self.completed = true; - return Ok(()); + return; }; if medias.is_empty() { // 如果没有音频,则直接返回 self.completed = true; - return Ok(()); + return; } let mut join_set = JoinSet::new(); @@ -159,14 +156,16 @@ impl AudioTask { urls.extend_from_slice(&media.backup_url); urls.push(media.base_url.clone()); - join_set.spawn(async move { + let get_url_with_content_length_task = async move { let bili_client = app.get_bili_client(); let url_with_content_length = bili_client.get_url_with_content_length(urls).await; MediaForPrepare { id, url_with_content_length, } - }); + }; + + join_set.spawn(get_url_with_content_length_task.in_current_span()); } let mut medias: Vec = Vec::new(); @@ -182,31 +181,25 @@ impl AudioTask { } self.prepare(app, &medias); - - Ok(()) } - pub async fn prepare_cheese( - &mut self, - app: &AppHandle, - media_url: &CheeseMediaUrl, - ) -> eyre::Result<()> { + pub async fn prepare_cheese(&mut self, app: &AppHandle, media_url: &CheeseMediaUrl) { let Some(dash) = &media_url.dash else { // 如果没有音频,则直接返回 self.completed = true; - return Ok(()); + return; }; let Some(medias) = &dash.audio else { // 如果没有音频,则直接返回 self.completed = true; - return Ok(()); + return; }; if medias.is_empty() { // 如果没有音频,则直接返回 self.completed = true; - return Ok(()); + return; } let mut join_set = JoinSet::new(); @@ -219,14 +212,16 @@ impl AudioTask { urls.extend_from_slice(&media.backup_url); urls.push(media.base_url.clone()); - join_set.spawn(async move { + let get_url_with_content_length_task = async move { let bili_client = app.get_bili_client(); let url_with_content_length = bili_client.get_url_with_content_length(urls).await; MediaForPrepare { id, url_with_content_length, } - }); + }; + + join_set.spawn(get_url_with_content_length_task.in_current_span()); } let mut medias: Vec = Vec::new(); @@ -242,8 +237,6 @@ impl AudioTask { } self.prepare(app, &medias); - - Ok(()) } fn prepare(&mut self, app: &AppHandle, medias: &[MediaForPrepare]) { @@ -310,24 +303,19 @@ impl AudioTask { } #[allow(clippy::too_many_lines)] + #[instrument(level = "error", skip_all)] pub async fn process( &self, download_task: &Arc, progress: &DownloadProgress, ) -> eyre::Result<()> { let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); - let (audio_task, episode_title, ids_string) = { - ( - progress.audio_task.clone(), - progress.episode_title.clone(), - progress.get_ids_string(), - ) - }; + let audio_task = progress.audio_task.clone(); let m4a_path = episode_dir.join(format!("{filename}.m4a")); let file_exist_action = download_task.app.get_config().read().file_exist_action; if file_exist_action == FileExistAction::Skip && m4a_path.exists() { - tracing::debug!("{ids_string} `{filename}`音频文件已存在,跳过下载"); + tracing::debug!("音频文件已存在,跳过下载"); download_task.update_progress(|p| { p.audio_task.skipped = true; p.audio_task.completed = true; @@ -377,11 +365,13 @@ impl AudioTask { chunk_index, }; - join_set.spawn(async move { + let chunk_order = chunk_index + 1; + let chunk_task = async move { download_chunk_task.process().await.wrap_err(format!( - "分片`{chunk_index}/{chunk_count}`下载失败({start}-{end})" + "分片`{chunk_order}/{chunk_count}`下载失败({start}-{end})" )) - }); + }; + join_set.spawn(chunk_task.in_current_span()); } while let Some(join_result) = join_set.join_next().await { @@ -392,7 +382,7 @@ impl AudioTask { match download_audio_result { Ok(i) => download_task.update_progress(|p| p.audio_task.chunks[i].completed = true), Err(err) => { - let err_title = format!("{ids_string} `{episode_title}`音频的一个分片下载失败"); + let err_title = "音频的一个分片下载失败"; let message = err.to_message(); tracing::error!(err_title, message); } diff --git a/src-tauri/src/downloader/tasks/cover_task.rs b/src-tauri/src/downloader/tasks/cover_task.rs index 478ea6f..8a88a4b 100644 --- a/src-tauri/src/downloader/tasks/cover_task.rs +++ b/src-tauri/src/downloader/tasks/cover_task.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use eyre::WrapErr; use serde::{Deserialize, Serialize}; use specta::Type; +use tracing::instrument; use crate::{ downloader::{download_progress::DownloadProgress, download_task::DownloadTask}, @@ -26,6 +27,7 @@ impl CoverTask { !self.selected || self.completed } + #[instrument(level = "error", skip_all)] pub async fn process( &self, download_task: &Arc, diff --git a/src-tauri/src/downloader/tasks/danmaku_task.rs b/src-tauri/src/downloader/tasks/danmaku_task.rs index d38e33e..81d493c 100644 --- a/src-tauri/src/downloader/tasks/danmaku_task.rs +++ b/src-tauri/src/downloader/tasks/danmaku_task.rs @@ -3,6 +3,7 @@ use std::{fs::File, sync::Arc}; use eyre::WrapErr; use serde::{Deserialize, Serialize}; use specta::Type; +use tracing::instrument; use crate::{ config::FileExistAction, @@ -33,6 +34,7 @@ impl DanmakuTask { !self.xml_selected && !self.ass_selected && !self.json_selected || self.completed } + #[instrument(level = "error", skip_all)] pub async fn process( &self, download_task: &Arc, @@ -40,7 +42,6 @@ impl DanmakuTask { ) -> eyre::Result<()> { let danmaku_task = &progress.danmaku_task; let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); - let ids_string = progress.get_ids_string(); let xml_path = episode_dir.join(format!("{filename}.弹幕.xml")); let ass_path = episode_dir.join(format!("{filename}.弹幕.ass")); @@ -53,7 +54,7 @@ impl DanmakuTask { let skip_json = !danmaku_task.json_selected || json_path.exists(); if skip_xml && skip_ass && skip_json { - tracing::debug!("{ids_string} `{filename}`弹幕文件已存在,跳过下载"); + tracing::debug!("弹幕文件已存在,跳过下载"); download_task.update_progress(|p| { p.danmaku_task.skipped = true; p.danmaku_task.completed = true; diff --git a/src-tauri/src/downloader/tasks/json_task.rs b/src-tauri/src/downloader/tasks/json_task.rs index 89a9024..600d1ea 100644 --- a/src-tauri/src/downloader/tasks/json_task.rs +++ b/src-tauri/src/downloader/tasks/json_task.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use eyre::WrapErr; use serde::{Deserialize, Serialize}; use specta::Type; +use tracing::instrument; use crate::downloader::{ download_progress::DownloadProgress, @@ -26,6 +27,7 @@ impl JsonTask { !self.selected || self.completed } + #[instrument(level = "error", skip_all)] pub async fn process( &self, download_task: &Arc, diff --git a/src-tauri/src/downloader/tasks/nfo_task.rs b/src-tauri/src/downloader/tasks/nfo_task.rs index f68c826..0114b9a 100644 --- a/src-tauri/src/downloader/tasks/nfo_task.rs +++ b/src-tauri/src/downloader/tasks/nfo_task.rs @@ -4,6 +4,7 @@ use chrono::{DateTime, Datelike, NaiveDateTime}; use eyre::{OptionExt, WrapErr, eyre}; use serde::{Deserialize, Serialize}; use specta::Type; +use tracing::instrument; use yaserde::{YaDeserialize, YaSerialize}; use crate::{ @@ -37,6 +38,7 @@ impl NfoTask { !self.selected || self.completed } + #[instrument(level = "error", skip_all)] pub async fn process( &self, download_task: &Arc, @@ -64,6 +66,7 @@ impl NfoTask { Ok(()) } + #[instrument(level = "error", skip_all)] async fn process_normal( &self, download_task: &Arc, @@ -71,12 +74,11 @@ impl NfoTask { info: &NormalInfo, ) -> eyre::Result<()> { let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); - let ids_string = progress.get_ids_string(); let nfo_path = episode_dir.join(format!("{filename}.nfo")); let file_exist_action = download_task.app.get_config().read().file_exist_action; if file_exist_action == FileExistAction::Skip && nfo_path.exists() { - tracing::debug!("{ids_string} `{filename}`NFO文件已存在,跳过下载"); + tracing::debug!("NFO文件已存在,跳过下载"); download_task.update_progress(|p| { p.nfo_task.skipped = true; p.nfo_task.completed = true; @@ -114,6 +116,7 @@ impl NfoTask { Ok(()) } + #[instrument(level = "error", skip_all)] async fn process_bangumi( &self, download_task: &Arc, @@ -122,12 +125,11 @@ impl NfoTask { ep_id: &i64, ) -> eyre::Result<()> { let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); - let ids_string = progress.get_ids_string(); let episode_details_nfo_path = episode_dir.join(format!("{filename}.nfo")); let file_exist_action = download_task.app.get_config().read().file_exist_action; if file_exist_action == FileExistAction::Skip && episode_details_nfo_path.exists() { - tracing::debug!("{ids_string} `{filename}`NFO文件已存在,跳过下载"); + tracing::debug!("NFO文件已存在,跳过下载"); download_task.update_progress(|p| { p.nfo_task.skipped = true; p.nfo_task.completed = true; @@ -178,6 +180,7 @@ impl NfoTask { Ok(()) } + #[instrument(level = "error", skip_all)] async fn process_cheese( &self, download_task: &Arc, @@ -186,12 +189,11 @@ impl NfoTask { ep_id: &i64, ) -> eyre::Result<()> { let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); - let ids_string = progress.get_ids_string(); let episode_details_nfo_path = episode_dir.join(format!("{filename}.nfo")); let file_exist_action = download_task.app.get_config().read().file_exist_action; if file_exist_action == FileExistAction::Skip && episode_details_nfo_path.exists() { - tracing::debug!("{ids_string} `{filename}`NFO文件已存在,跳过下载"); + tracing::debug!("NFO文件已存在,跳过下载"); download_task.update_progress(|p| { p.nfo_task.skipped = true; p.nfo_task.completed = true; @@ -299,6 +301,7 @@ struct EpisodeDetails { } impl NormalInfo { + #[instrument(level = "error", skip_all)] pub fn to_movie_nfo(&self, tags: Tags) -> eyre::Result { let genre = vec![ "Bilibili视频".to_string(), @@ -364,6 +367,7 @@ impl NormalInfo { } impl BangumiInfo { + #[instrument(level = "error", skip_all)] pub fn to_tvshow_nfo(&self) -> eyre::Result { let time_str = &self.publish.pub_time; let date_time = NaiveDateTime::parse_from_str(time_str, "%Y-%m-%d %H:%M:%S").wrap_err( @@ -399,6 +403,7 @@ impl BangumiInfo { Ok(nfo) } + #[instrument(level = "error", skip_all)] pub fn to_episode_details_nfo(&self, ep_id: i64) -> eyre::Result { let (episode, episode_order) = self.get_episode_with_order(ep_id)?; @@ -489,6 +494,7 @@ impl BangumiInfo { } impl CheeseInfo { + #[instrument(level = "error", skip_all)] pub fn to_tvshow_nfo(&self) -> eyre::Result { let episode = self.episodes.first().ok_or_eyre("episodes列表为空")?; let ts = episode.release_date; @@ -525,12 +531,13 @@ impl CheeseInfo { Ok(nfo) } + #[instrument(level = "error", skip_all)] pub fn to_episode_details_nfo(&self, ep_id: i64) -> eyre::Result { let episode = self .episodes .iter() .find(|ep| ep.id == ep_id) - .ok_or_eyre(format!("找不到ep_id为`{ep_id}`的课程"))?; + .ok_or_eyre("找不到ep_id对应的课程")?; let ts = episode.release_date; let date_time = DateTime::from_timestamp(ts, 0) diff --git a/src-tauri/src/downloader/tasks/subtitle_task.rs b/src-tauri/src/downloader/tasks/subtitle_task.rs index b9a3d08..66212b6 100644 --- a/src-tauri/src/downloader/tasks/subtitle_task.rs +++ b/src-tauri/src/downloader/tasks/subtitle_task.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use eyre::WrapErr; use serde::{Deserialize, Serialize}; use specta::Type; +use tracing::instrument; use crate::{ downloader::{download_progress::DownloadProgress, download_task::DownloadTask}, @@ -27,6 +28,7 @@ impl SubtitleTask { !self.selected || self.completed } + #[instrument(level = "error", skip_all)] pub async fn process( &self, download_task: &Arc, diff --git a/src-tauri/src/downloader/tasks/video_process_task.rs b/src-tauri/src/downloader/tasks/video_process_task.rs index 32be99b..d7e9851 100644 --- a/src-tauri/src/downloader/tasks/video_process_task.rs +++ b/src-tauri/src/downloader/tasks/video_process_task.rs @@ -4,6 +4,7 @@ use eyre::{WrapErr, eyre}; use serde::{Deserialize, Serialize}; use specta::Type; use tauri::AppHandle; +use tracing::instrument; use crate::{ downloader::{ @@ -38,6 +39,7 @@ impl VideoProcessTask { || self.completed } + #[instrument(level = "error", skip_all)] pub async fn process( &self, download_task: &Arc, @@ -63,6 +65,7 @@ impl VideoProcessTask { Ok(()) } + #[instrument(level = "error", skip_all)] async fn merge_and_embed( &self, download_task: &Arc, @@ -101,7 +104,10 @@ impl VideoProcessTask { let metadata_path_clone = metadata_path.clone(); let output_path_clone = output_path.clone(); - tokio::spawn(async move { + let current_span = tracing::Span::current(); + tauri::async_runtime::spawn_blocking(move || { + let _enter = current_span.enter(); + let mut command = std::process::Command::new(ffmpeg_program); command.arg("-i").arg(video_path_clone); @@ -162,6 +168,7 @@ impl VideoProcessTask { Ok(()) } + #[instrument(level = "error", skip_all)] async fn merge( &self, download_task: &Arc, @@ -190,7 +197,10 @@ impl VideoProcessTask { let audio_path_clone = audio_path.clone(); let output_path_clone = output_path.clone(); + let current_span = tracing::Span::current(); tauri::async_runtime::spawn_blocking(move || { + let _enter = current_span.enter(); + let mut command = std::process::Command::new(ffmpeg_program); command.arg("-i").arg(video_path_clone); @@ -240,6 +250,7 @@ impl VideoProcessTask { Ok(()) } + #[instrument(level = "error", skip_all)] async fn embed( &self, download_task: &Arc, @@ -273,7 +284,10 @@ impl VideoProcessTask { let metadata_path_clone = metadata_path.clone(); let output_path_clone = output_path.clone(); + let current_span = tracing::Span::current(); tauri::async_runtime::spawn_blocking(move || { + let _enter = current_span.enter(); + let mut command = std::process::Command::new(ffmpeg_program); command.arg("-i").arg(video_path_clone); @@ -324,6 +338,7 @@ impl VideoProcessTask { Ok(()) } + #[instrument(level = "error", skip_all)] async fn create_chapter_metadata( &self, app: &AppHandle, diff --git a/src-tauri/src/downloader/tasks/video_task.rs b/src-tauri/src/downloader/tasks/video_task.rs index e532476..0e69b6f 100644 --- a/src-tauri/src/downloader/tasks/video_task.rs +++ b/src-tauri/src/downloader/tasks/video_task.rs @@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize}; use specta::Type; use tauri::AppHandle; use tokio::task::JoinSet; +use tracing::{Instrument, instrument}; use crate::{ config::FileExistAction, @@ -42,6 +43,7 @@ pub struct VideoTask { } impl VideoTask { + #[instrument(level = "error", skip_all)] pub async fn prepare_normal( &mut self, app: &AppHandle, @@ -58,7 +60,7 @@ impl VideoTask { urls.extend_from_slice(&media.backup_url); urls.push(media.base_url.clone()); - join_set.spawn(async move { + let get_url_with_content_length_task = async move { let bili_client = app.get_bili_client(); let url_with_content_length = bili_client.get_url_with_content_length(urls).await; MediaForPrepare { @@ -66,7 +68,9 @@ impl VideoTask { url_with_content_length, codecid, } - }); + }; + + join_set.spawn(get_url_with_content_length_task.in_current_span()); } for durl in &media_url.durl { @@ -78,7 +82,7 @@ impl VideoTask { urls.extend_from_slice(&durl.backup_url); urls.push(durl.url.clone()); - join_set.spawn(async move { + let get_url_with_content_length_task = async move { let bili_client = app.get_bili_client(); let url_with_content_length = bili_client.get_url_with_content_length(urls).await; MediaForPrepare { @@ -86,7 +90,9 @@ impl VideoTask { url_with_content_length, codecid, } - }); + }; + + join_set.spawn(get_url_with_content_length_task.in_current_span()); } let mut medias: Vec = Vec::new(); @@ -106,6 +112,7 @@ impl VideoTask { Ok(()) } + #[instrument(level = "error", skip_all)] pub async fn prepare_bangumi( &mut self, app: &AppHandle, @@ -125,7 +132,7 @@ impl VideoTask { urls.extend_from_slice(&media.backup_url); urls.push(media.base_url.clone()); - join_set.spawn(async move { + let get_url_with_content_length_task = async move { let bili_client = app.get_bili_client(); let url_with_content_length = bili_client.get_url_with_content_length(urls).await; @@ -134,7 +141,9 @@ impl VideoTask { url_with_content_length, codecid, } - }); + }; + + join_set.spawn(get_url_with_content_length_task.in_current_span()); } } @@ -148,7 +157,7 @@ impl VideoTask { urls.extend_from_slice(&media.backup_url); urls.push(media.url.clone()); - join_set.spawn(async move { + let get_url_with_content_length_task = async move { let bili_client = app.get_bili_client(); let url_with_content_length = bili_client.get_url_with_content_length(urls).await; @@ -157,7 +166,9 @@ impl VideoTask { url_with_content_length, codecid, } - }); + }; + + join_set.spawn(get_url_with_content_length_task.in_current_span()); } } @@ -176,6 +187,7 @@ impl VideoTask { Ok(()) } + #[instrument(level = "error", skip_all)] pub async fn prepare_cheese( &mut self, app: &AppHandle, @@ -195,7 +207,7 @@ impl VideoTask { urls.extend_from_slice(&media.backup_url); urls.push(media.base_url.clone()); - join_set.spawn(async move { + let get_url_with_content_length_task = async move { let bili_client = app.get_bili_client(); let url_with_content_length = bili_client.get_url_with_content_length(urls).await; @@ -204,7 +216,9 @@ impl VideoTask { url_with_content_length, codecid, } - }); + }; + + join_set.spawn(get_url_with_content_length_task.in_current_span()); } } @@ -218,7 +232,7 @@ impl VideoTask { urls.extend_from_slice(&media.backup_url); urls.push(media.url.clone()); - join_set.spawn(async move { + let get_url_with_content_length_task = async move { let bili_client = app.get_bili_client(); let url_with_content_length = bili_client.get_url_with_content_length(urls).await; @@ -227,7 +241,9 @@ impl VideoTask { url_with_content_length, codecid, } - }); + }; + + join_set.spawn(get_url_with_content_length_task.in_current_span()); } } @@ -246,6 +262,7 @@ impl VideoTask { Ok(()) } + #[instrument(level = "error", skip_all)] fn prepare(&mut self, app: &AppHandle, medias: &[MediaForPrepare]) -> eyre::Result<()> { if medias.is_empty() { return Err(eyre!("获取视频地址失败,medias为空")); @@ -318,25 +335,19 @@ impl VideoTask { } #[allow(clippy::too_many_lines)] + #[instrument(level = "error", skip_all)] pub async fn process( &self, download_task: &Arc, progress: &DownloadProgress, ) -> eyre::Result<()> { let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); - let (video_task, episode_title, ids_string) = { - let progress = download_task.progress.read(); - ( - progress.video_task.clone(), - progress.episode_title.clone(), - progress.get_ids_string(), - ) - }; + let video_task = download_task.progress.read().video_task.clone(); let mp4_path = episode_dir.join(format!("{filename}.mp4")); let file_exist_action = download_task.app.get_config().read().file_exist_action; if file_exist_action == FileExistAction::Skip && mp4_path.exists() { - tracing::debug!("{ids_string} `{filename}`视频文件已存在,跳过下载"); + tracing::debug!("视频文件已存在,跳过下载"); download_task.update_progress(|p| { p.video_task.skipped = true; p.video_task.completed = true; @@ -370,7 +381,7 @@ impl VideoTask { let chunk_count = video_task.chunks.len(); let mut join_set = JoinSet::new(); - for (i, chunk) in video_task.chunks.iter().enumerate() { + for (chunk_index, chunk) in video_task.chunks.iter().enumerate() { if chunk.completed { continue; } @@ -383,16 +394,16 @@ impl VideoTask { end, url: video_task.url.clone(), file: file.clone(), - chunk_index: i, + chunk_index, }; - let chunk_order = i + 1; - - join_set.spawn(async move { + let chunk_order = chunk_index + 1; + let chunk_task = async move { download_chunk_task.process().await.wrap_err(format!( "分片`{chunk_order}/{chunk_count}`下载失败({start}-{end})" )) - }); + }; + join_set.spawn(chunk_task.in_current_span()); } while let Some(join_result) = join_set.join_next().await { @@ -403,7 +414,7 @@ impl VideoTask { match download_video_result { Ok(i) => download_task.update_progress(|p| p.video_task.chunks[i].completed = true), Err(err) => { - let err_title = format!("{ids_string} `{episode_title}`视频的一个分片下载失败"); + let err_title = "视频的一个分片下载失败"; let message = err.to_message(); tracing::error!(err_title, message); } diff --git a/src-tauri/src/errors.rs b/src-tauri/src/errors.rs index 1b22c3c..b536959 100644 --- a/src-tauri/src/errors.rs +++ b/src-tauri/src/errors.rs @@ -3,6 +3,7 @@ use std::panic::Location; use eyre::EyreHandler; use serde::{Deserialize, Serialize}; use specta::Type; +use tracing::instrument; use tracing_error::SpanTrace; pub type CommandResult = Result; @@ -73,6 +74,7 @@ impl EyreHandler for CustomEyreHandler { } } +#[instrument(level = "error", skip_all)] pub fn install_custom_eyre_handler() -> eyre::Result<()> { eyre::set_hook(Box::new(|_error| { Box::new(CustomEyreHandler { diff --git a/src-tauri/src/extensions.rs b/src-tauri/src/extensions.rs index f7701b9..a77873f 100644 --- a/src-tauri/src/extensions.rs +++ b/src-tauri/src/extensions.rs @@ -1,6 +1,7 @@ use eyre::WrapErr; use parking_lot::RwLock; use tauri::{AppHandle, Manager, State}; +use tracing::instrument; use crate::{ bili_client::BiliClient, @@ -46,6 +47,7 @@ pub trait GetOrInitPlayerInfo { } impl GetOrInitPlayerInfo for Option { + #[instrument(level = "error", skip_all)] async fn get_or_init<'a>( &'a mut self, app: &AppHandle, diff --git a/src-tauri/src/logger.rs b/src-tauri/src/logger.rs index c75482a..88e658a 100644 --- a/src-tauri/src/logger.rs +++ b/src-tauri/src/logger.rs @@ -4,7 +4,7 @@ use eyre::{OptionExt, WrapErr}; use notify::{RecommendedWatcher, Watcher}; use tauri::{AppHandle, Manager}; use tauri_specta::Event; -use tracing::{Level, Subscriber}; +use tracing::{Instrument, Level, Subscriber, instrument}; use tracing_appender::{ non_blocking::WorkerGuard, rolling::{RollingFileAppender, Rotation}, @@ -52,6 +52,7 @@ impl Write for LogEventWriter { static RELOAD_FN: OnceLock eyre::Result<()> + Send + Sync>> = OnceLock::new(); static GUARD: OnceLock>> = OnceLock::new(); +#[instrument(level = "error", skip_all)] pub fn init(app: &AppHandle) -> eyre::Result<()> { let lib_module_path = module_path!(); let lib_target = lib_module_path.split("::").next().ok_or_eyre(format!( @@ -104,10 +105,12 @@ pub fn init(app: &AppHandle) -> eyre::Result<()> { Ok(()) } +#[instrument(level = "error", skip_all)] pub fn reload_file_logger() -> eyre::Result<()> { RELOAD_FN.get().ok_or_eyre("RELOAD_FN未初始化")?() } +#[instrument(level = "error", skip_all)] pub fn disable_file_logger() -> eyre::Result<()> { if let Some(guard) = GUARD.get().ok_or_eyre("GUARD未初始化")?.lock().take() { drop(guard); @@ -115,6 +118,7 @@ pub fn disable_file_logger() -> eyre::Result<()> { Ok(()) } +#[instrument(level = "error", skip_all)] fn create_file_layer( app: &AppHandle, ) -> eyre::Result<(Box + Send + Sync>, Option)> @@ -149,17 +153,21 @@ where Ok((Box::new(file_layer), Some(guard))) } +#[instrument(level = "error", skip_all)] async fn file_log_watcher(app: AppHandle) { let (sender, mut receiver) = tokio::sync::mpsc::channel(1); + let event_handler_span = tracing::error_span!("file_log_watcher_event_handler"); let event_handler = move |res| { - tauri::async_runtime::block_on(async { + let send_event_task = async { if let Err(err) = sender.send(res).await.map_err(eyre::Report::from) { let err_title = "发送日志文件watcher事件失败"; let message = err.to_message(); tracing::error!(err_title, message); } - }); + }; + + tauri::async_runtime::block_on(send_event_task.instrument(event_handler_span.clone())); }; let mut watcher = match RecommendedWatcher::new(event_handler, notify::Config::default()) @@ -214,6 +222,7 @@ async fn file_log_watcher(app: AppHandle) { } } +#[instrument(level = "error", skip_all)] pub fn logs_dir(app: &AppHandle) -> eyre::Result { let app_data_dir = app .path() diff --git a/src-tauri/src/types/bangumi_info.rs b/src-tauri/src/types/bangumi_info.rs index 5d28fe0..cd83ad3 100644 --- a/src-tauri/src/types/bangumi_info.rs +++ b/src-tauri/src/types/bangumi_info.rs @@ -1,6 +1,7 @@ use eyre::{OptionExt, eyre}; use serde::{Deserialize, Serialize}; use specta::Type; +use tracing::instrument; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] #[serde(default)] @@ -55,6 +56,7 @@ pub struct BangumiInfo { impl BangumiInfo { #[allow(clippy::cast_possible_wrap)] + #[instrument(level = "error", skip_all)] pub fn get_episode_with_order(&self, ep_id: i64) -> eyre::Result<(&EpInBangumi, i64)> { let episode_with_order = self .episodes @@ -69,19 +71,19 @@ impl BangumiInfo { } else { // 如果在正片中没有找到对应的ep_id,则在section中查找 let Some(sections) = &self.section else { - return Err(eyre!("找不到对应的ep_id为`{ep_id}`的番剧")); + return Err(eyre!("section为None")); }; let section_index = sections .iter() .position(|s| s.episodes.iter().any(|e| e.id == ep_id)) - .ok_or_eyre(format!("找不到含有ep_id为`{ep_id}`的ep的section"))?; + .ok_or_eyre("找不到含有对应ep_id的section")?; sections[section_index] .episodes .iter() .enumerate() .map(|(i, e)| (e, i as i64 + 1)) .find(|(e, _)| e.id == ep_id) - .ok_or_eyre(format!("在section中找不到ep_id为`{ep_id}`的ep"))? + .ok_or_eyre("在section中找不到ep_id对应的ep")? }; Ok(episode_with_order) diff --git a/src-tauri/src/types/get_bangumi_info_params.rs b/src-tauri/src/types/get_bangumi_info_params.rs index bfcddf9..79ee0b7 100644 --- a/src-tauri/src/types/get_bangumi_info_params.rs +++ b/src-tauri/src/types/get_bangumi_info_params.rs @@ -6,3 +6,19 @@ pub enum GetBangumiInfoParams { EpId(i64), SeasonId(i64), } + +impl GetBangumiInfoParams { + pub fn get_ep_id(&self) -> Option { + match self { + Self::EpId(ep_id) => Some(*ep_id), + Self::SeasonId(_) => None, + } + } + + pub fn get_season_id(&self) -> Option { + match self { + Self::EpId(_) => None, + Self::SeasonId(season_id) => Some(*season_id), + } + } +} diff --git a/src-tauri/src/types/get_cheese_info_params.rs b/src-tauri/src/types/get_cheese_info_params.rs index 2a31745..3372c0d 100644 --- a/src-tauri/src/types/get_cheese_info_params.rs +++ b/src-tauri/src/types/get_cheese_info_params.rs @@ -6,3 +6,19 @@ pub enum GetCheeseInfoParams { EpId(i64), SeasonId(i64), } + +impl GetCheeseInfoParams { + pub fn get_ep_id(&self) -> Option { + match self { + Self::EpId(ep_id) => Some(*ep_id), + Self::SeasonId(_) => None, + } + } + + pub fn get_season_id(&self) -> Option { + match self { + Self::EpId(_) => None, + Self::SeasonId(season_id) => Some(*season_id), + } + } +} diff --git a/src-tauri/src/types/get_normal_info_params.rs b/src-tauri/src/types/get_normal_info_params.rs index 9387677..85e208c 100644 --- a/src-tauri/src/types/get_normal_info_params.rs +++ b/src-tauri/src/types/get_normal_info_params.rs @@ -6,3 +6,19 @@ pub enum GetNormalInfoParams { Bvid(String), Aid(i64), } + +impl GetNormalInfoParams { + pub fn get_bvid(&self) -> Option { + match self { + Self::Bvid(bvid) => Some(bvid.clone()), + Self::Aid(_) => None, + } + } + + pub fn get_aid(&self) -> Option { + match self { + Self::Bvid(_) => None, + Self::Aid(aid) => Some(*aid), + } + } +} diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index 9a4e4df..a3b37e0 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -6,6 +6,7 @@ use std::{ use byteorder::{BigEndian, ReadBytesExt}; use eyre::{OptionExt, WrapErr, eyre}; +use tracing::instrument; use crate::{ danmaku_xml_to_ass::{DamakuXmlDTag, DanmakuXmlITag}, @@ -48,6 +49,7 @@ impl From for BoxSizeField { } } +#[instrument(level = "error", skip_all, fields(file_path = ?file_path))] pub fn is_mp4_complete(file_path: &Path) -> eyre::Result { let file = File::open(file_path).wrap_err(format!("打开文件`{}`失败", file_path.display()))?; let real_size = file @@ -135,6 +137,7 @@ pub trait ToXml { } impl ToXml for Vec { + #[instrument(level = "error", skip_all, fields(cid = cid))] fn to_xml(&self, cid: i64) -> eyre::Result { let elems = self .iter() @@ -177,6 +180,7 @@ pub fn seconds_to_srt_time(seconds: f64) -> String { format!("{h:02}:{m:02}:{s:02},{ms:03}") } +#[instrument(level = "error", skip_all)] pub fn get_ffmpeg_program() -> eyre::Result { let ffmpeg_program = std::env::current_exe() .wrap_err("获取当前可执行文件路径失败")? diff --git a/src-tauri/src/wbi.rs b/src-tauri/src/wbi.rs index fea7e12..d360776 100644 --- a/src-tauri/src/wbi.rs +++ b/src-tauri/src/wbi.rs @@ -3,6 +3,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use eyre::{OptionExt, WrapErr, eyre}; use md5::{Digest, Md5}; use serde::Deserialize; +use tracing::instrument; use crate::bili_client::{BiliClient, BiliResp}; @@ -25,7 +26,8 @@ struct WeiRespData { impl BiliClient { // 为请求参数进行 wbi 签名 - pub(crate) async fn wbi(&self, params: &mut Vec<(&str, String)>) -> eyre::Result<()> { + #[instrument(level = "error", skip_all)] + pub async fn wbi(&self, params: &mut Vec<(&str, String)>) -> eyre::Result<()> { let (img_key, sub_key) = self.get_wbi_keys().await.wrap_err("获取wbi keys失败")?; let mixin_key = get_mixin_key((img_key + &sub_key).as_bytes()); @@ -46,6 +48,7 @@ impl BiliClient { Ok(()) } + #[instrument(level = "error", skip_all)] async fn get_wbi_keys(&self) -> eyre::Result<(String, String)> { let request = self .api_client