refactor(log): extend target with filter fn and file name, closes #123 (#386)

pull/393/head
Lucas Fernandes Nogueira 2 years ago committed by GitHub
parent a0133cb5b3
commit 3c8577bc9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2,7 +2,9 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use fern::FormatCallback; //! Logging for Tauri applications.
use fern::{Filter, FormatCallback};
use log::{logger, RecordBuilder}; use log::{logger, RecordBuilder};
use log::{LevelFilter, Record}; use log::{LevelFilter, Record};
use serde::Serialize; use serde::Serialize;
@ -23,6 +25,8 @@ use tauri::{
pub use fern; pub use fern;
use time::OffsetDateTime; use time::OffsetDateTime;
pub const WEBVIEW_TARGET: &str = "Webview";
#[cfg(target_os = "ios")] #[cfg(target_os = "ios")]
mod ios { mod ios {
use cocoa::base::id; use cocoa::base::id;
@ -58,7 +62,10 @@ mod ios {
const DEFAULT_MAX_FILE_SIZE: u128 = 40000; const DEFAULT_MAX_FILE_SIZE: u128 = 40000;
const DEFAULT_ROTATION_STRATEGY: RotationStrategy = RotationStrategy::KeepOne; const DEFAULT_ROTATION_STRATEGY: RotationStrategy = RotationStrategy::KeepOne;
const DEFAULT_TIMEZONE_STRATEGY: TimezoneStrategy = TimezoneStrategy::UseUtc; 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. /// 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. /// An enum representing the available targets of the logger.
pub enum LogTarget { pub enum TargetKind {
/// Print logs to stdout. /// Print logs to stdout.
Stdout, Stdout,
/// Print logs to stderr. /// Print logs to stderr.
@ -149,7 +156,10 @@ pub enum LogTarget {
/// Write logs to the given directory. /// Write logs to the given directory.
/// ///
/// The plugin will ensure the directory exists before writing logs. /// The plugin will ensure the directory exists before writing logs.
Folder(PathBuf), Folder {
path: PathBuf,
file_name: Option<String>,
},
/// Write logs to the OS specififc logs directory. /// Write logs to the OS specififc logs directory.
/// ///
/// ### Platform-specific /// ### Platform-specific
@ -159,13 +169,38 @@ pub enum LogTarget {
/// | Linux | `{configDir}/{bundleIdentifier}` | `/home/alice/.config/com.tauri.dev` | /// | Linux | `{configDir}/{bundleIdentifier}` | `/home/alice/.config/com.tauri.dev` |
/// | macOS | `{homeDir}/Library/Logs/{bundleIdentifier}` | `/Users/Alice/Library/Logs/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` | /// | Windows | `{configDir}/{bundleIdentifier}` | `C:\Users\Alice\AppData\Roaming\com.tauri.dev` |
LogDir, LogDir { file_name: Option<String> },
/// Forward logs to the webview (via the `log://log` event). /// Forward logs to the webview (via the `log://log` event).
/// ///
/// This requires the webview to subscribe to log events, via this plugins `attachConsole` function. /// This requires the webview to subscribe to log events, via this plugins `attachConsole` function.
Webview, Webview,
} }
/// A log target.
pub struct Target {
kind: TargetKind,
filters: Vec<Box<Filter>>,
}
impl Target {
#[inline]
pub const fn new(kind: TargetKind) -> Self {
Self {
kind,
filters: Vec::new(),
}
}
#[inline]
pub fn filter<F>(mut self, filter: F) -> Self
where
F: Fn(&log::Metadata) -> bool + Send + Sync + 'static,
{
self.filters.push(Box::new(filter));
self
}
}
#[tauri::command] #[tauri::command]
fn log( fn log(
level: LogLevel, level: LogLevel,
@ -176,9 +211,18 @@ fn log(
key_values: Option<HashMap<String, String>>, key_values: Option<HashMap<String, String>>,
) { ) {
let location = location.unwrap_or("webview"); 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(); let mut builder = RecordBuilder::new();
builder builder
.level(level.into()) .level(level)
.metadata(metadata)
.target(location) .target(location)
.file(file) .file(file)
.line(line); .line(line);
@ -198,7 +242,7 @@ pub struct Builder {
rotation_strategy: RotationStrategy, rotation_strategy: RotationStrategy,
timezone_strategy: TimezoneStrategy, timezone_strategy: TimezoneStrategy,
max_file_size: u128, max_file_size: u128,
targets: Vec<LogTarget>, targets: Vec<Target>,
} }
impl Default for Builder { impl Default for Builder {
@ -290,12 +334,37 @@ impl Builder {
self 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.targets.push(target);
self self
} }
pub fn targets(mut self, targets: impl IntoIterator<Item = LogTarget>) -> 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<Item = Target>) -> Self {
self.targets = Vec::from_iter(targets); self.targets = Vec::from_iter(targets);
self self
} }
@ -325,14 +394,19 @@ impl Builder {
let app_name = &app_handle.package_info().name; let app_name = &app_handle.package_info().name;
// setup targets // setup targets
for target in &self.targets { for target in self.targets {
let logger = match target { 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")] #[cfg(target_os = "android")]
LogTarget::Stdout | LogTarget::Stderr => { TargetKind::Stdout | TargetKind::Stderr => {
fern::Output::call(android_logger::log) fern::Output::call(android_logger::log)
} }
#[cfg(target_os = "ios")] #[cfg(target_os = "ios")]
LogTarget::Stdout | LogTarget::Stderr => { TargetKind::Stdout | TargetKind::Stderr => {
fern::Output::call(move |record| { fern::Output::call(move |record| {
let message = format!("{}", record.args()); let message = format!("{}", record.args());
unsafe { unsafe {
@ -348,17 +422,17 @@ impl Builder {
}) })
} }
#[cfg(desktop)] #[cfg(desktop)]
LogTarget::Stdout => std::io::stdout().into(), TargetKind::Stdout => std::io::stdout().into(),
#[cfg(desktop)] #[cfg(desktop)]
LogTarget::Stderr => std::io::stderr().into(), TargetKind::Stderr => std::io::stderr().into(),
LogTarget::Folder(path) => { TargetKind::Folder { path, file_name } => {
if !path.exists() { if !path.exists() {
fs::create_dir_all(path).unwrap(); fs::create_dir_all(&path).unwrap();
} }
fern::log_file(get_log_file_path( fern::log_file(get_log_file_path(
&path, &path,
app_name, file_name.as_deref().unwrap_or(app_name),
&self.rotation_strategy, &self.rotation_strategy,
&self.timezone_strategy, &self.timezone_strategy,
self.max_file_size, self.max_file_size,
@ -366,9 +440,9 @@ impl Builder {
.into() .into()
} }
#[cfg(mobile)] #[cfg(mobile)]
LogTarget::LogDir => continue, TargetKind::LogDir { .. } => continue,
#[cfg(desktop)] #[cfg(desktop)]
LogTarget::LogDir => { TargetKind::LogDir { file_name } => {
let path = app_handle.path().app_log_dir().unwrap(); let path = app_handle.path().app_log_dir().unwrap();
if !path.exists() { if !path.exists() {
fs::create_dir_all(&path).unwrap(); fs::create_dir_all(&path).unwrap();
@ -376,14 +450,14 @@ impl Builder {
fern::log_file(get_log_file_path( fern::log_file(get_log_file_path(
&path, &path,
app_name, file_name.as_deref().unwrap_or(app_name),
&self.rotation_strategy, &self.rotation_strategy,
&self.timezone_strategy, &self.timezone_strategy,
self.max_file_size, self.max_file_size,
)?)? )?)?
.into() .into()
} }
LogTarget::Webview => { TargetKind::Webview => {
let app_handle = app_handle.clone(); let app_handle = app_handle.clone();
fern::Output::call(move |record| { 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()?; self.dispatch.apply()?;
@ -411,12 +487,12 @@ impl Builder {
fn get_log_file_path( fn get_log_file_path(
dir: &impl AsRef<Path>, dir: &impl AsRef<Path>,
app_name: &str, file_name: &str,
rotation_strategy: &RotationStrategy, rotation_strategy: &RotationStrategy,
timezone_strategy: &TimezoneStrategy, timezone_strategy: &TimezoneStrategy,
max_file_size: u128, max_file_size: u128,
) -> plugin::Result<PathBuf> { ) -> plugin::Result<PathBuf> {
let path = dir.as_ref().join(format!("{app_name}.log")); let path = dir.as_ref().join(format!("{file_name}.log"));
if path.exists() { if path.exists() {
let log_size = File::open(&path)?.metadata()?.len() as u128; let log_size = File::open(&path)?.metadata()?.len() as u128;
@ -425,7 +501,7 @@ fn get_log_file_path(
RotationStrategy::KeepAll => { RotationStrategy::KeepAll => {
let to = dir.as_ref().join(format!( let to = dir.as_ref().join(format!(
"{}_{}.log", "{}_{}.log",
app_name, file_name,
timezone_strategy timezone_strategy
.get_now() .get_now()
.format( .format(

Loading…
Cancel
Save