diff --git a/.changes/log-split.md b/.changes/log-split.md new file mode 100644 index 000000000..9b9a9e48f --- /dev/null +++ b/.changes/log-split.md @@ -0,0 +1,5 @@ +--- +"log-plugin": patch +--- + +Added `Builder::split` which returns the raw logger implementation so you can pipe to other loggers such as `multi_log` or `tauri-plugin-devtools`. diff --git a/Cargo.lock b/Cargo.lock index a5c532b36..74622341f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,7 +200,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "api" -version = "2.0.0-beta.13" +version = "2.0.0-beta.14" dependencies = [ "log", "serde", @@ -6603,6 +6603,7 @@ dependencies = [ "swift-rs", "tauri", "tauri-plugin", + "thiserror", "time", ] @@ -6621,7 +6622,7 @@ dependencies = [ [[package]] name = "tauri-plugin-notification" -version = "2.0.0-beta.10" +version = "2.0.0-beta.11" dependencies = [ "color-backtrace", "ctor", @@ -6727,7 +6728,7 @@ dependencies = [ [[package]] name = "tauri-plugin-sql" -version = "2.0.0-beta.9" +version = "2.0.0-beta.10" dependencies = [ "futures-core", "indexmap 2.2.6", @@ -6778,7 +6779,7 @@ dependencies = [ [[package]] name = "tauri-plugin-updater" -version = "2.0.0-beta.10" +version = "2.0.0-beta.11" dependencies = [ "base64 0.22.1", "dirs 5.0.1", diff --git a/plugins/log/Cargo.toml b/plugins/log/Cargo.toml index 6b975548e..c62e5f259 100644 --- a/plugins/log/Cargo.toml +++ b/plugins/log/Cargo.toml @@ -25,6 +25,7 @@ byte-unit = "5" log = { workspace = true, features = [ "kv_unstable" ] } time = { version = "0.3", features = [ "formatting", "local-offset" ] } fern = "0.6" +thiserror = "1" [target."cfg(target_os = \"android\")".dependencies] android_logger = "0.14" diff --git a/plugins/log/src/lib.rs b/plugins/log/src/lib.rs index b9eb4baae..2691c55fe 100644 --- a/plugins/log/src/lib.rs +++ b/plugins/log/src/lib.rs @@ -24,11 +24,11 @@ use std::{ iter::FromIterator, path::{Path, PathBuf}, }; -use tauri::Emitter; use tauri::{ plugin::{self, TauriPlugin}, Manager, Runtime, }; +use tauri::{AppHandle, Emitter}; pub use fern; use time::OffsetDateTime; @@ -75,6 +75,18 @@ const DEFAULT_LOG_TARGETS: [Target; 2] = [ Target::new(TargetKind::LogDir { file_name: None }), ]; +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Tauri(#[from] tauri::Error), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + TimeFormat(#[from] time::error::Format), + #[error(transparent)] + InvalidFormatDescription(#[from] time::error::InvalidFormatDescription), +} + /// An enum representing the available verbosity levels of the logger. /// /// It is very similar to the [`log::Level`], but serializes to unsigned ints instead of strings. @@ -395,97 +407,134 @@ impl Builder { }) } - pub fn build(mut self) -> TauriPlugin { - plugin::Builder::new("log") - .invoke_handler(tauri::generate_handler![log]) - .setup(move |app_handle, _api| { - let app_name = &app_handle.package_info().name; + fn acquire_logger( + app_handle: &AppHandle, + mut dispatch: fern::Dispatch, + rotation_strategy: RotationStrategy, + timezone_strategy: TimezoneStrategy, + max_file_size: u128, + targets: Vec, + ) -> Result<(log::LevelFilter, Box), Error> { + let app_name = &app_handle.package_info().name; + + // setup targets + for target in targets { + let mut target_dispatch = fern::Dispatch::new(); + for filter in target.filters { + target_dispatch = target_dispatch.filter(filter); + } - // setup targets - for target in self.targets { - let mut target_dispatch = fern::Dispatch::new(); - for filter in target.filters { - target_dispatch = target_dispatch.filter(filter); + let logger = match target.kind { + #[cfg(target_os = "android")] + TargetKind::Stdout | TargetKind::Stderr => fern::Output::call(android_logger::log), + #[cfg(target_os = "ios")] + TargetKind::Stdout | TargetKind::Stderr => fern::Output::call(move |record| { + let message = format!("{}", record.args()); + unsafe { + ios::tauri_log( + match record.level() { + log::Level::Trace | log::Level::Debug => 1, + log::Level::Info => 2, + log::Level::Warn | log::Level::Error => 3, + }, + ios::NSString::new(message.as_str()).0 as _, + ); + } + }), + #[cfg(desktop)] + TargetKind::Stdout => std::io::stdout().into(), + #[cfg(desktop)] + TargetKind::Stderr => std::io::stderr().into(), + TargetKind::Folder { path, file_name } => { + if !path.exists() { + fs::create_dir_all(&path)?; } - let logger = match target.kind { - #[cfg(target_os = "android")] - TargetKind::Stdout | TargetKind::Stderr => { - fern::Output::call(android_logger::log) - } - #[cfg(target_os = "ios")] - TargetKind::Stdout | TargetKind::Stderr => { - fern::Output::call(move |record| { - let message = format!("{}", record.args()); - unsafe { - ios::tauri_log( - match record.level() { - log::Level::Trace | log::Level::Debug => 1, - log::Level::Info => 2, - log::Level::Warn | log::Level::Error => 3, - }, - ios::NSString::new(message.as_str()).0 as _, - ); - } - }) - } - #[cfg(desktop)] - TargetKind::Stdout => std::io::stdout().into(), - #[cfg(desktop)] - TargetKind::Stderr => std::io::stderr().into(), - TargetKind::Folder { path, file_name } => { - if !path.exists() { - fs::create_dir_all(&path)?; - } - - fern::log_file(get_log_file_path( - &path, - file_name.as_deref().unwrap_or(app_name), - &self.rotation_strategy, - &self.timezone_strategy, - self.max_file_size, - )?)? - .into() - } - #[cfg(mobile)] - TargetKind::LogDir { .. } => continue, - #[cfg(desktop)] - TargetKind::LogDir { file_name } => { - let path = app_handle.path().app_log_dir()?; - if !path.exists() { - fs::create_dir_all(&path)?; - } - - fern::log_file(get_log_file_path( - &path, - file_name.as_deref().unwrap_or(app_name), - &self.rotation_strategy, - &self.timezone_strategy, - self.max_file_size, - )?)? - .into() - } - TargetKind::Webview => { - let app_handle = app_handle.clone(); - - fern::Output::call(move |record| { - let payload = RecordPayload { - message: record.args().to_string(), - level: record.level().into(), - }; - let app_handle = app_handle.clone(); - tauri::async_runtime::spawn(async move { - let _ = app_handle.emit("log://log", payload); - }); - }) - } - }; - target_dispatch = target_dispatch.chain(logger); - - self.dispatch = self.dispatch.chain(target_dispatch); + fern::log_file(get_log_file_path( + &path, + file_name.as_deref().unwrap_or(app_name), + &rotation_strategy, + &timezone_strategy, + max_file_size, + )?)? + .into() } + #[cfg(mobile)] + TargetKind::LogDir { .. } => continue, + #[cfg(desktop)] + TargetKind::LogDir { file_name } => { + let path = app_handle.path().app_log_dir()?; + if !path.exists() { + fs::create_dir_all(&path)?; + } - self.dispatch.apply()?; + fern::log_file(get_log_file_path( + &path, + file_name.as_deref().unwrap_or(app_name), + &rotation_strategy, + &timezone_strategy, + max_file_size, + )?)? + .into() + } + TargetKind::Webview => { + let app_handle = app_handle.clone(); + + fern::Output::call(move |record| { + let payload = RecordPayload { + message: record.args().to_string(), + level: record.level().into(), + }; + let app_handle = app_handle.clone(); + tauri::async_runtime::spawn(async move { + let _ = app_handle.emit("log://log", payload); + }); + }) + } + }; + target_dispatch = target_dispatch.chain(logger); + + dispatch = dispatch.chain(target_dispatch); + } + + Ok(dispatch.into_log()) + } + + fn plugin_builder() -> plugin::Builder { + plugin::Builder::new("log").invoke_handler(tauri::generate_handler![log]) + } + + #[allow(clippy::type_complexity)] + pub fn split( + self, + app_handle: &AppHandle, + ) -> Result<(TauriPlugin, log::LevelFilter, Box), Error> { + let plugin = Self::plugin_builder(); + let (max_level, log) = Self::acquire_logger( + app_handle, + self.dispatch, + self.rotation_strategy, + self.timezone_strategy, + self.max_file_size, + self.targets, + )?; + + Ok((plugin.build(), max_level, log)) + } + + pub fn build(self) -> TauriPlugin { + Self::plugin_builder() + .setup(move |app_handle, _api| { + let (max_level, log) = Self::acquire_logger( + app_handle, + self.dispatch, + self.rotation_strategy, + self.timezone_strategy, + self.max_file_size, + self.targets, + )?; + + attach_logger(max_level, log)?; Ok(()) }) @@ -493,13 +542,23 @@ impl Builder { } } +/// Attaches the given logger +pub fn attach_logger( + max_level: log::LevelFilter, + log: Box, +) -> Result<(), log::SetLoggerError> { + log::set_boxed_logger(log)?; + log::set_max_level(max_level); + Ok(()) +} + fn get_log_file_path( dir: &impl AsRef, file_name: &str, rotation_strategy: &RotationStrategy, timezone_strategy: &TimezoneStrategy, max_file_size: u128, -) -> Result> { +) -> Result { let path = dir.as_ref().join(format!("{file_name}.log")); if path.exists() {