Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(log): add Builder::split to get the raw logger implementation #1579

Merged
merged 3 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/log-split.md
Original file line number Diff line number Diff line change
@@ -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`.
9 changes: 5 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions plugins/log/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
235 changes: 147 additions & 88 deletions plugins/log/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -395,111 +407,158 @@ impl Builder {
})
}

pub fn build<R: Runtime>(mut self) -> TauriPlugin<R> {
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<R: Runtime>(
app_handle: &AppHandle<R>,
mut dispatch: fern::Dispatch,
rotation_strategy: RotationStrategy,
timezone_strategy: TimezoneStrategy,
max_file_size: u128,
targets: Vec<Target>,
) -> Result<(log::LevelFilter, Box<dyn log::Log>), 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<R: Runtime>() -> plugin::Builder<R> {
plugin::Builder::new("log").invoke_handler(tauri::generate_handler![log])
}

#[allow(clippy::type_complexity)]
pub fn split<R: Runtime>(
self,
app_handle: &AppHandle<R>,
) -> Result<(TauriPlugin<R>, log::LevelFilter, Box<dyn log::Log>), 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<R: Runtime>(self) -> TauriPlugin<R> {
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(())
})
.build()
}
}

/// Attaches the given logger
pub fn attach_logger(
max_level: log::LevelFilter,
log: Box<dyn log::Log>,
) -> Result<(), log::SetLoggerError> {
log::set_boxed_logger(log)?;
log::set_max_level(max_level);
Ok(())
}

fn get_log_file_path(
dir: &impl AsRef<Path>,
file_name: &str,
rotation_strategy: &RotationStrategy,
timezone_strategy: &TimezoneStrategy,
max_file_size: u128,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
) -> Result<PathBuf, Error> {
let path = dir.as_ref().join(format!("{file_name}.log"));

if path.exists() {
Expand Down
Loading