mirror of
https://github.com/lanyeeee/bilibili-video-downloader.git
synced 2026-09-07 16:46:50 +08:00
feat: 后端支持输出日志
This commit is contained in:
Generated
+196
@@ -268,6 +268,7 @@ name = "bilibili-video-downloader"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"notify",
|
||||
"parking_lot",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -277,6 +278,10 @@ dependencies = [
|
||||
"tauri-build",
|
||||
"tauri-plugin-opener",
|
||||
"tauri-specta",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1011,6 +1016,15 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futf"
|
||||
version = "0.1.5"
|
||||
@@ -1721,6 +1735,26 @@ dependencies = [
|
||||
"cfb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.7.8"
|
||||
@@ -1861,6 +1895,26 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
|
||||
dependencies = [
|
||||
"kqueue-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue-sys"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kuchikiki"
|
||||
version = "0.8.8-speedreader"
|
||||
@@ -2032,6 +2086,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
@@ -2112,6 +2167,40 @@ version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "8.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3163f59cd3fa0e9ef8c32f242966a7b9994fd7378366099593e0e73077cd8c97"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"mio",
|
||||
"notify-types",
|
||||
"walkdir",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify-types"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
@@ -2149,6 +2238,15 @@ dependencies = [
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc-sys"
|
||||
version = "0.3.5"
|
||||
@@ -2406,6 +2504,12 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.18.3"
|
||||
@@ -3288,6 +3392,15 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
@@ -3944,6 +4057,15 @@ dependencies = [
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.41"
|
||||
@@ -3952,7 +4074,9 @@ checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"libc",
|
||||
"num-conv",
|
||||
"num_threads",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
@@ -3996,12 +4120,26 @@ dependencies = [
|
||||
"io-uring",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.15"
|
||||
@@ -4134,6 +4272,18 @@ dependencies = [
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-appender"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"thiserror 1.0.69",
|
||||
"time",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.30"
|
||||
@@ -4152,6 +4302,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-serde"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"time",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
"tracing-serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4306,6 +4496,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.2.0"
|
||||
|
||||
@@ -29,6 +29,11 @@ specta-typescript = { version = "=0.0.7" }
|
||||
|
||||
anyhow = { version = "1.0.98" }
|
||||
parking_lot = { version = "0.12.4", features = ["send_guard"] }
|
||||
tracing = { version = "0.1.41" }
|
||||
tracing-subscriber = { version = "0.3.19", features = ["json", "time", "local-time"] }
|
||||
tracing-appender = { version = "0.2.3" }
|
||||
notify = { version = "8.0.0" }
|
||||
tokio = { version = "1.46.0", features = ["full"] }
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
|
||||
@@ -8,6 +8,7 @@ use tauri::{AppHandle, Manager};
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Config {
|
||||
pub download_dir: PathBuf,
|
||||
pub enable_file_logger: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -64,6 +65,7 @@ impl Config {
|
||||
fn default(app_data_dir: &Path) -> Config {
|
||||
Config {
|
||||
download_dir: app_data_dir.join("视频下载"),
|
||||
enable_file_logger: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specta::Type;
|
||||
|
||||
use crate::extensions::AnyhowErrorToStringChain;
|
||||
|
||||
pub type CommandResult<T> = Result<T, CommandError>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
|
||||
pub struct CommandError {
|
||||
pub err_title: String,
|
||||
pub err_message: String,
|
||||
}
|
||||
|
||||
impl CommandError {
|
||||
pub fn from<E>(err_title: &str, err: E) -> Self
|
||||
where
|
||||
E: Into<anyhow::Error>,
|
||||
{
|
||||
let string_chain = err.into().to_string_chain();
|
||||
tracing::error!(err_title, message = string_chain);
|
||||
Self {
|
||||
err_title: err_title.to_string(),
|
||||
err_message: string_chain,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specta::Type;
|
||||
use tauri_specta::Event;
|
||||
|
||||
use crate::types::LogLevel;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LogEvent {
|
||||
pub timestamp: String,
|
||||
pub level: LogLevel,
|
||||
pub fields: HashMap<String, serde_json::Value>,
|
||||
pub target: String,
|
||||
pub filename: String,
|
||||
#[serde(rename = "line_number")]
|
||||
pub line_number: i64,
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
use parking_lot::RwLock;
|
||||
use tauri::{Manager, State};
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
pub trait AnyhowErrorToStringChain {
|
||||
/// 将 `anyhow::Error` 转换为chain格式
|
||||
/// # Example
|
||||
@@ -18,3 +23,13 @@ impl AnyhowErrorToStringChain for anyhow::Error {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AppHandleExt {
|
||||
fn get_config(&self) -> State<RwLock<Config>>;
|
||||
}
|
||||
|
||||
impl AppHandleExt for tauri::AppHandle {
|
||||
fn get_config(&self) -> State<RwLock<Config>> {
|
||||
self.state::<RwLock<Config>>()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
mod commands;
|
||||
mod config;
|
||||
mod errors;
|
||||
mod events;
|
||||
mod extensions;
|
||||
mod logger;
|
||||
mod types;
|
||||
mod utils;
|
||||
|
||||
use anyhow::Context;
|
||||
@@ -9,6 +13,8 @@ use config::Config;
|
||||
use parking_lot::RwLock;
|
||||
use tauri::{Manager, Wry};
|
||||
|
||||
use crate::events::LogEvent;
|
||||
|
||||
fn generate_context() -> tauri::Context<Wry> {
|
||||
tauri::generate_context!()
|
||||
}
|
||||
@@ -17,7 +23,7 @@ fn generate_context() -> tauri::Context<Wry> {
|
||||
pub fn run() {
|
||||
let builder = tauri_specta::Builder::<Wry>::new()
|
||||
.commands(tauri_specta::collect_commands![greet, get_config])
|
||||
.events(tauri_specta::collect_events![]);
|
||||
.events(tauri_specta::collect_events![LogEvent]);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
builder
|
||||
@@ -47,6 +53,8 @@ pub fn run() {
|
||||
let config = RwLock::new(Config::new(app.handle())?);
|
||||
app.manage(config);
|
||||
|
||||
logger::init(app.handle())?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.run(generate_context())
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
use std::{io::Write, sync::OnceLock};
|
||||
|
||||
use anyhow::Context;
|
||||
use notify::{RecommendedWatcher, Watcher};
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tauri_specta::Event;
|
||||
use tracing::{Level, Subscriber};
|
||||
use tracing_appender::{
|
||||
non_blocking::WorkerGuard,
|
||||
rolling::{RollingFileAppender, Rotation},
|
||||
};
|
||||
use tracing_subscriber::{
|
||||
filter::{filter_fn, FilterExt, Targets},
|
||||
fmt::{layer, time::LocalTime},
|
||||
layer::SubscriberExt,
|
||||
registry::LookupSpan,
|
||||
util::SubscriberInitExt,
|
||||
Layer, Registry,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
events::LogEvent,
|
||||
extensions::{AnyhowErrorToStringChain, AppHandleExt},
|
||||
};
|
||||
|
||||
struct LogEventWriter {
|
||||
app: AppHandle,
|
||||
}
|
||||
|
||||
impl Write for LogEventWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let log_string = String::from_utf8_lossy(buf);
|
||||
match serde_json::from_str::<LogEvent>(&log_string) {
|
||||
Ok(log_event) => {
|
||||
let _ = log_event.emit(&self.app);
|
||||
}
|
||||
Err(err) => {
|
||||
let log_string = log_string.to_string();
|
||||
let err_msg = err.to_string();
|
||||
tracing::error!(log_string, err_msg, "将日志字符串解析为LogEvent失败");
|
||||
}
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
static RELOAD_FN: OnceLock<Box<dyn Fn() -> anyhow::Result<()> + Send + Sync>> = OnceLock::new();
|
||||
static GUARD: OnceLock<parking_lot::Mutex<Option<WorkerGuard>>> = OnceLock::new();
|
||||
|
||||
pub fn init(app: &AppHandle) -> anyhow::Result<()> {
|
||||
let lib_module_path = module_path!();
|
||||
let lib_target = lib_module_path.split("::").next().context(format!(
|
||||
"解析lib_target失败: lib_module_path={lib_module_path}"
|
||||
))?;
|
||||
// 过滤掉来自其他库的日志
|
||||
let target_filter = Targets::new().with_target(lib_target, Level::TRACE);
|
||||
// 输出到文件
|
||||
let (file_layer, guard) = create_file_layer(app)?;
|
||||
let (reloadable_file_layer, reload_handle) = tracing_subscriber::reload::Layer::new(file_layer);
|
||||
// 输出到控制台
|
||||
let console_layer = layer()
|
||||
.with_writer(std::io::stdout)
|
||||
.with_timer(LocalTime::rfc_3339())
|
||||
.with_file(true)
|
||||
.with_line_number(true);
|
||||
// 发送到前端
|
||||
let log_event_writer = std::sync::Mutex::new(LogEventWriter { app: app.clone() });
|
||||
let log_event_layer = layer()
|
||||
.with_writer(log_event_writer)
|
||||
.with_timer(LocalTime::rfc_3339())
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.json()
|
||||
// 过滤掉来自这个文件的日志(LogEvent解析失败的日志),避免无限递归
|
||||
.with_filter(target_filter.clone().and(filter_fn(|metadata| {
|
||||
metadata.module_path() != Some(lib_module_path)
|
||||
})));
|
||||
|
||||
Registry::default()
|
||||
.with(target_filter)
|
||||
.with(reloadable_file_layer)
|
||||
.with(console_layer)
|
||||
.with(log_event_layer)
|
||||
.init();
|
||||
|
||||
GUARD.get_or_init(|| parking_lot::Mutex::new(guard));
|
||||
RELOAD_FN.get_or_init(move || {
|
||||
let app = app.clone();
|
||||
Box::new(move || {
|
||||
let (file_layer, guard) = create_file_layer(&app)?;
|
||||
reload_handle.reload(file_layer).context("reload失败")?;
|
||||
*GUARD.get().context("GUARD未初始化")?.lock() = guard;
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
tauri::async_runtime::spawn(file_log_watcher(app.clone()));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reload_file_logger() -> anyhow::Result<()> {
|
||||
RELOAD_FN.get().context("RELOAD_FN未初始化")?()
|
||||
}
|
||||
|
||||
pub fn disable_file_logger() -> anyhow::Result<()> {
|
||||
if let Some(guard) = GUARD.get().context("GUARD未初始化")?.lock().take() {
|
||||
drop(guard);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_file_layer<S>(
|
||||
app: &AppHandle,
|
||||
) -> anyhow::Result<(Box<dyn Layer<S> + Send + Sync>, Option<WorkerGuard>)>
|
||||
where
|
||||
S: Subscriber + for<'a> LookupSpan<'a>,
|
||||
{
|
||||
let enable_file_logger = app.get_config().read().enable_file_logger;
|
||||
// 如果不启用文件日志,则返回一个占位用的sink layer,不创建也不输出日志文件
|
||||
if !enable_file_logger {
|
||||
let sink_layer = layer()
|
||||
.with_writer(std::io::sink)
|
||||
.with_timer(LocalTime::rfc_3339())
|
||||
.with_ansi(false)
|
||||
.with_file(true)
|
||||
.with_line_number(true);
|
||||
return Ok((Box::new(sink_layer), None));
|
||||
}
|
||||
let logs_dir = logs_dir(app).context("获取日志目录失败")?;
|
||||
let file_appender = RollingFileAppender::builder()
|
||||
.filename_prefix("bilibili-video-downloader")
|
||||
.filename_suffix("log")
|
||||
.rotation(Rotation::DAILY)
|
||||
.build(&logs_dir)
|
||||
.context("创建RollingFileAppender失败")?;
|
||||
let (non_blocking_appender, guard) = tracing_appender::non_blocking(file_appender);
|
||||
let file_layer = layer()
|
||||
.with_writer(non_blocking_appender)
|
||||
.with_timer(LocalTime::rfc_3339())
|
||||
.with_ansi(false)
|
||||
.with_file(true)
|
||||
.with_line_number(true);
|
||||
Ok((Box::new(file_layer), Some(guard)))
|
||||
}
|
||||
|
||||
async fn file_log_watcher(app: AppHandle) {
|
||||
let (sender, mut receiver) = tokio::sync::mpsc::channel(1);
|
||||
|
||||
let event_handler = move |res| {
|
||||
tauri::async_runtime::block_on(async {
|
||||
if let Err(err) = sender.send(res).await.map_err(anyhow::Error::from) {
|
||||
let err_title = "发送日志文件watcher事件失败";
|
||||
let string_chain = err.to_string_chain();
|
||||
tracing::error!(err_title, message = string_chain);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let mut watcher = match RecommendedWatcher::new(event_handler, notify::Config::default())
|
||||
.map_err(anyhow::Error::from)
|
||||
{
|
||||
Ok(watcher) => watcher,
|
||||
Err(err) => {
|
||||
let err_title = "创建日志文件watcher失败";
|
||||
let string_chain = err.to_string_chain();
|
||||
tracing::error!(err_title, message = string_chain);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let logs_dir = match logs_dir(&app) {
|
||||
Ok(logs_dir) => logs_dir,
|
||||
Err(err) => {
|
||||
let err_title = "日志文件watcher获取日志目录失败";
|
||||
let string_chain = err.to_string_chain();
|
||||
tracing::error!(err_title, message = string_chain);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = watcher
|
||||
.watch(&logs_dir, notify::RecursiveMode::NonRecursive)
|
||||
.map_err(anyhow::Error::from)
|
||||
{
|
||||
let err_title = "日志文件watcher监听日志目录失败";
|
||||
let string_chain = err.to_string_chain();
|
||||
tracing::error!(err_title, message = string_chain);
|
||||
return;
|
||||
}
|
||||
|
||||
while let Some(res) = receiver.recv().await {
|
||||
match res.map_err(anyhow::Error::from) {
|
||||
Ok(event) => {
|
||||
if let notify::EventKind::Remove(_) = event.kind {
|
||||
if let Err(err) = reload_file_logger() {
|
||||
let err_title = "重置日志文件失败";
|
||||
let string_chain = err.to_string_chain();
|
||||
tracing::error!(err_title, message = string_chain);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
let err_title = "接收日志文件watcher事件失败";
|
||||
let string_chain = err.to_string_chain();
|
||||
tracing::error!(err_title, message = string_chain);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn logs_dir(app: &AppHandle) -> anyhow::Result<std::path::PathBuf> {
|
||||
let app_data_dir = app
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.context("获取app_data_dir目录失败")?;
|
||||
Ok(app_data_dir.join("日志"))
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specta::Type;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
|
||||
pub enum LogLevel {
|
||||
#[serde(rename = "TRACE")]
|
||||
Trace,
|
||||
#[serde(rename = "DEBUG")]
|
||||
Debug,
|
||||
#[serde(rename = "INFO")]
|
||||
Info,
|
||||
#[serde(rename = "WARN")]
|
||||
Warn,
|
||||
#[serde(rename = "ERROR")]
|
||||
Error,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
mod log_level;
|
||||
|
||||
pub use log_level::*;
|
||||
+9
-1
@@ -16,6 +16,11 @@ async getConfig() : Promise<Config> {
|
||||
/** user-defined events **/
|
||||
|
||||
|
||||
export const events = __makeEvents__<{
|
||||
logEvent: LogEvent
|
||||
}>({
|
||||
logEvent: "log-event"
|
||||
})
|
||||
|
||||
/** user-defined constants **/
|
||||
|
||||
@@ -23,7 +28,10 @@ async getConfig() : Promise<Config> {
|
||||
|
||||
/** user-defined types **/
|
||||
|
||||
export type Config = { downloadDir: string }
|
||||
export type Config = { downloadDir: string; enableFileLogger: boolean }
|
||||
export type JsonValue = null | boolean | number | string | JsonValue[] | { [key in string]: JsonValue }
|
||||
export type LogEvent = { timestamp: string; level: LogLevel; fields: { [key in string]: JsonValue }; target: string; filename: string; line_number: number }
|
||||
export type LogLevel = "TRACE" | "DEBUG" | "INFO" | "WARN" | "ERROR"
|
||||
|
||||
/** tauri-specta globals **/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user