From 3c8577bc9aed3c5ed3a0adf42be3f8b16696fdc4 Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Mon, 22 May 2023 15:04:34 -0700 Subject: [PATCH] refactor(log): extend target with filter fn and file name, closes #123 (#386) --- plugins/log/src/lib.rs | 128 ++++++++++++++++++++++++++++++++--------- 1 file changed, 102 insertions(+), 26 deletions(-) diff --git a/plugins/log/src/lib.rs b/plugins/log/src/lib.rs index 64489f46..e8d548c0 100644 --- a/plugins/log/src/lib.rs +++ b/plugins/log/src/lib.rs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use fern::FormatCallback; +//! Logging for Tauri applications. + +use fern::{Filter, FormatCallback}; use log::{logger, RecordBuilder}; use log::{LevelFilter, Record}; use serde::Serialize; @@ -23,6 +25,8 @@ use tauri::{ pub use fern; use time::OffsetDateTime; +pub const WEBVIEW_TARGET: &str = "Webview"; + #[cfg(target_os = "ios")] mod ios { use cocoa::base::id; @@ -58,7 +62,10 @@ mod ios { const DEFAULT_MAX_FILE_SIZE: u128 = 40000; const DEFAULT_ROTATION_STRATEGY: RotationStrategy = RotationStrategy::KeepOne; const DEFAULT_TIMEZONE_STRATEGY: TimezoneStrategy = TimezoneStrategy::UseUtc; -const DEFAULT_LOG_TARGETS: [LogTarget; 2] = [LogTarget::Stdout, LogTarget::LogDir]; +const DEFAULT_LOG_TARGETS: [Target; 2] = [ + Target::new(TargetKind::Stdout), + Target::new(TargetKind::LogDir { file_name: None }), +]; /// An enum representing the available verbosity levels of the logger. /// @@ -141,7 +148,7 @@ struct RecordPayload { } /// An enum representing the available targets of the logger. -pub enum LogTarget { +pub enum TargetKind { /// Print logs to stdout. Stdout, /// Print logs to stderr. @@ -149,7 +156,10 @@ pub enum LogTarget { /// Write logs to the given directory. /// /// The plugin will ensure the directory exists before writing logs. - Folder(PathBuf), + Folder { + path: PathBuf, + file_name: Option, + }, /// Write logs to the OS specififc logs directory. /// /// ### Platform-specific @@ -159,13 +169,38 @@ pub enum LogTarget { /// | Linux | `{configDir}/{bundleIdentifier}` | `/home/alice/.config/com.tauri.dev` | /// | macOS | `{homeDir}/Library/Logs/{bundleIdentifier}` | `/Users/Alice/Library/Logs/com.tauri.dev` | /// | Windows | `{configDir}/{bundleIdentifier}` | `C:\Users\Alice\AppData\Roaming\com.tauri.dev` | - LogDir, + LogDir { file_name: Option }, /// Forward logs to the webview (via the `log://log` event). /// /// This requires the webview to subscribe to log events, via this plugins `attachConsole` function. Webview, } +/// A log target. +pub struct Target { + kind: TargetKind, + filters: Vec>, +} + +impl Target { + #[inline] + pub const fn new(kind: TargetKind) -> Self { + Self { + kind, + filters: Vec::new(), + } + } + + #[inline] + pub fn filter(mut self, filter: F) -> Self + where + F: Fn(&log::Metadata) -> bool + Send + Sync + 'static, + { + self.filters.push(Box::new(filter)); + self + } +} + #[tauri::command] fn log( level: LogLevel, @@ -176,9 +211,18 @@ fn log( key_values: Option>, ) { let location = location.unwrap_or("webview"); + + let level = log::Level::from(level); + + let metadata = log::MetadataBuilder::new() + .level(level) + .target(WEBVIEW_TARGET) + .build(); + let mut builder = RecordBuilder::new(); builder - .level(level.into()) + .level(level) + .metadata(metadata) .target(location) .file(file) .line(line); @@ -198,7 +242,7 @@ pub struct Builder { rotation_strategy: RotationStrategy, timezone_strategy: TimezoneStrategy, max_file_size: u128, - targets: Vec, + targets: Vec, } impl Default for Builder { @@ -290,12 +334,37 @@ impl Builder { self } - pub fn target(mut self, target: LogTarget) -> Self { + /// Removes all targets. Useful to ignore the default targets and reconfigure them. + pub fn clear_targets(mut self) -> Self { + self.targets.clear(); + self + } + + /// Adds a log target to the logger. + /// + /// ```rust + /// use tauri_plugin_log::{Target, TargetKind}; + /// tauri_plugin_log::Builder::new() + /// .target(Target::new(TargetKind::Webview)); + /// ``` + pub fn target(mut self, target: Target) -> Self { self.targets.push(target); self } - pub fn targets(mut self, targets: impl IntoIterator) -> Self { + /// Adds a collection of targets to the logger. + /// + /// ```rust + /// use tauri_plugin_log::{Target, TargetKind, WEBVIEW_TARGET}; + /// tauri_plugin_log::Builder::new() + /// .clear_targets() + /// .targets([ + /// Target::new(TargetKind::Webview), + /// Target::new(TargetKind::LogDir { file_name: Some("webview".into()) }).filter(|metadata| metadata.target() == WEBVIEW_TARGET), + /// Target::new(TargetKind::LogDir { file_name: Some("rust".into()) }).filter(|metadata| metadata.target() != WEBVIEW_TARGET), + /// ]); + /// ``` + pub fn targets(mut self, targets: impl IntoIterator) -> Self { self.targets = Vec::from_iter(targets); self } @@ -325,14 +394,19 @@ impl Builder { let app_name = &app_handle.package_info().name; // setup targets - for target in &self.targets { - let logger = match target { + 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")] - LogTarget::Stdout | LogTarget::Stderr => { + TargetKind::Stdout | TargetKind::Stderr => { fern::Output::call(android_logger::log) } #[cfg(target_os = "ios")] - LogTarget::Stdout | LogTarget::Stderr => { + TargetKind::Stdout | TargetKind::Stderr => { fern::Output::call(move |record| { let message = format!("{}", record.args()); unsafe { @@ -348,17 +422,17 @@ impl Builder { }) } #[cfg(desktop)] - LogTarget::Stdout => std::io::stdout().into(), + TargetKind::Stdout => std::io::stdout().into(), #[cfg(desktop)] - LogTarget::Stderr => std::io::stderr().into(), - LogTarget::Folder(path) => { + TargetKind::Stderr => std::io::stderr().into(), + TargetKind::Folder { path, file_name } => { if !path.exists() { - fs::create_dir_all(path).unwrap(); + fs::create_dir_all(&path).unwrap(); } fern::log_file(get_log_file_path( &path, - app_name, + file_name.as_deref().unwrap_or(app_name), &self.rotation_strategy, &self.timezone_strategy, self.max_file_size, @@ -366,9 +440,9 @@ impl Builder { .into() } #[cfg(mobile)] - LogTarget::LogDir => continue, + TargetKind::LogDir { .. } => continue, #[cfg(desktop)] - LogTarget::LogDir => { + TargetKind::LogDir { file_name } => { let path = app_handle.path().app_log_dir().unwrap(); if !path.exists() { fs::create_dir_all(&path).unwrap(); @@ -376,14 +450,14 @@ impl Builder { fern::log_file(get_log_file_path( &path, - app_name, + file_name.as_deref().unwrap_or(app_name), &self.rotation_strategy, &self.timezone_strategy, self.max_file_size, )?)? .into() } - LogTarget::Webview => { + TargetKind::Webview => { let app_handle = app_handle.clone(); fern::Output::call(move |record| { @@ -398,7 +472,9 @@ impl Builder { }) } }; - self.dispatch = self.dispatch.chain(logger); + target_dispatch = target_dispatch.chain(logger); + + self.dispatch = self.dispatch.chain(target_dispatch); } self.dispatch.apply()?; @@ -411,12 +487,12 @@ impl Builder { fn get_log_file_path( dir: &impl AsRef, - app_name: &str, + file_name: &str, rotation_strategy: &RotationStrategy, timezone_strategy: &TimezoneStrategy, max_file_size: u128, ) -> plugin::Result { - let path = dir.as_ref().join(format!("{app_name}.log")); + let path = dir.as_ref().join(format!("{file_name}.log")); if path.exists() { let log_size = File::open(&path)?.metadata()?.len() as u128; @@ -425,7 +501,7 @@ fn get_log_file_path( RotationStrategy::KeepAll => { let to = dir.as_ref().join(format!( "{}_{}.log", - app_name, + file_name, timezone_strategy .get_now() .format(