Skip to content

Commit 20a1d24

Browse files
authored
feat(log): add Builder::split to get the raw logger implementation (#1579)
* feat(log): add Builder::split to get the raw logger implementation This function lets you split the Builder to return the raw logger implementation along the TauriPlugin to be registered. Useful to pipe the logger to other implementations such as multi_log or tauri-plugin-devtools, allowing the plugin to be used along other logging systems. * clippy * covector
1 parent fa27573 commit 20a1d24

File tree

4 files changed

+158
-92
lines changed

4 files changed

+158
-92
lines changed

.changes/log-split.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"log-plugin": patch
3+
---
4+
5+
Added `Builder::split` which returns the raw logger implementation so you can pipe to other loggers such as `multi_log` or `tauri-plugin-devtools`.

Cargo.lock

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/log/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ byte-unit = "5"
2525
log = { workspace = true, features = [ "kv_unstable" ] }
2626
time = { version = "0.3", features = [ "formatting", "local-offset" ] }
2727
fern = "0.6"
28+
thiserror = "1"
2829

2930
[target."cfg(target_os = \"android\")".dependencies]
3031
android_logger = "0.14"

plugins/log/src/lib.rs

Lines changed: 147 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ use std::{
2424
iter::FromIterator,
2525
path::{Path, PathBuf},
2626
};
27-
use tauri::Emitter;
2827
use tauri::{
2928
plugin::{self, TauriPlugin},
3029
Manager, Runtime,
3130
};
31+
use tauri::{AppHandle, Emitter};
3232

3333
pub use fern;
3434
use time::OffsetDateTime;
@@ -75,6 +75,18 @@ const DEFAULT_LOG_TARGETS: [Target; 2] = [
7575
Target::new(TargetKind::LogDir { file_name: None }),
7676
];
7777

78+
#[derive(Debug, thiserror::Error)]
79+
pub enum Error {
80+
#[error(transparent)]
81+
Tauri(#[from] tauri::Error),
82+
#[error(transparent)]
83+
Io(#[from] std::io::Error),
84+
#[error(transparent)]
85+
TimeFormat(#[from] time::error::Format),
86+
#[error(transparent)]
87+
InvalidFormatDescription(#[from] time::error::InvalidFormatDescription),
88+
}
89+
7890
/// An enum representing the available verbosity levels of the logger.
7991
///
8092
/// It is very similar to the [`log::Level`], but serializes to unsigned ints instead of strings.
@@ -395,111 +407,158 @@ impl Builder {
395407
})
396408
}
397409

398-
pub fn build<R: Runtime>(mut self) -> TauriPlugin<R> {
399-
plugin::Builder::new("log")
400-
.invoke_handler(tauri::generate_handler![log])
401-
.setup(move |app_handle, _api| {
402-
let app_name = &app_handle.package_info().name;
410+
fn acquire_logger<R: Runtime>(
411+
app_handle: &AppHandle<R>,
412+
mut dispatch: fern::Dispatch,
413+
rotation_strategy: RotationStrategy,
414+
timezone_strategy: TimezoneStrategy,
415+
max_file_size: u128,
416+
targets: Vec<Target>,
417+
) -> Result<(log::LevelFilter, Box<dyn log::Log>), Error> {
418+
let app_name = &app_handle.package_info().name;
419+
420+
// setup targets
421+
for target in targets {
422+
let mut target_dispatch = fern::Dispatch::new();
423+
for filter in target.filters {
424+
target_dispatch = target_dispatch.filter(filter);
425+
}
403426

404-
// setup targets
405-
for target in self.targets {
406-
let mut target_dispatch = fern::Dispatch::new();
407-
for filter in target.filters {
408-
target_dispatch = target_dispatch.filter(filter);
427+
let logger = match target.kind {
428+
#[cfg(target_os = "android")]
429+
TargetKind::Stdout | TargetKind::Stderr => fern::Output::call(android_logger::log),
430+
#[cfg(target_os = "ios")]
431+
TargetKind::Stdout | TargetKind::Stderr => fern::Output::call(move |record| {
432+
let message = format!("{}", record.args());
433+
unsafe {
434+
ios::tauri_log(
435+
match record.level() {
436+
log::Level::Trace | log::Level::Debug => 1,
437+
log::Level::Info => 2,
438+
log::Level::Warn | log::Level::Error => 3,
439+
},
440+
ios::NSString::new(message.as_str()).0 as _,
441+
);
442+
}
443+
}),
444+
#[cfg(desktop)]
445+
TargetKind::Stdout => std::io::stdout().into(),
446+
#[cfg(desktop)]
447+
TargetKind::Stderr => std::io::stderr().into(),
448+
TargetKind::Folder { path, file_name } => {
449+
if !path.exists() {
450+
fs::create_dir_all(&path)?;
409451
}
410452

411-
let logger = match target.kind {
412-
#[cfg(target_os = "android")]
413-
TargetKind::Stdout | TargetKind::Stderr => {
414-
fern::Output::call(android_logger::log)
415-
}
416-
#[cfg(target_os = "ios")]
417-
TargetKind::Stdout | TargetKind::Stderr => {
418-
fern::Output::call(move |record| {
419-
let message = format!("{}", record.args());
420-
unsafe {
421-
ios::tauri_log(
422-
match record.level() {
423-
log::Level::Trace | log::Level::Debug => 1,
424-
log::Level::Info => 2,
425-
log::Level::Warn | log::Level::Error => 3,
426-
},
427-
ios::NSString::new(message.as_str()).0 as _,
428-
);
429-
}
430-
})
431-
}
432-
#[cfg(desktop)]
433-
TargetKind::Stdout => std::io::stdout().into(),
434-
#[cfg(desktop)]
435-
TargetKind::Stderr => std::io::stderr().into(),
436-
TargetKind::Folder { path, file_name } => {
437-
if !path.exists() {
438-
fs::create_dir_all(&path)?;
439-
}
440-
441-
fern::log_file(get_log_file_path(
442-
&path,
443-
file_name.as_deref().unwrap_or(app_name),
444-
&self.rotation_strategy,
445-
&self.timezone_strategy,
446-
self.max_file_size,
447-
)?)?
448-
.into()
449-
}
450-
#[cfg(mobile)]
451-
TargetKind::LogDir { .. } => continue,
452-
#[cfg(desktop)]
453-
TargetKind::LogDir { file_name } => {
454-
let path = app_handle.path().app_log_dir()?;
455-
if !path.exists() {
456-
fs::create_dir_all(&path)?;
457-
}
458-
459-
fern::log_file(get_log_file_path(
460-
&path,
461-
file_name.as_deref().unwrap_or(app_name),
462-
&self.rotation_strategy,
463-
&self.timezone_strategy,
464-
self.max_file_size,
465-
)?)?
466-
.into()
467-
}
468-
TargetKind::Webview => {
469-
let app_handle = app_handle.clone();
470-
471-
fern::Output::call(move |record| {
472-
let payload = RecordPayload {
473-
message: record.args().to_string(),
474-
level: record.level().into(),
475-
};
476-
let app_handle = app_handle.clone();
477-
tauri::async_runtime::spawn(async move {
478-
let _ = app_handle.emit("log://log", payload);
479-
});
480-
})
481-
}
482-
};
483-
target_dispatch = target_dispatch.chain(logger);
484-
485-
self.dispatch = self.dispatch.chain(target_dispatch);
453+
fern::log_file(get_log_file_path(
454+
&path,
455+
file_name.as_deref().unwrap_or(app_name),
456+
&rotation_strategy,
457+
&timezone_strategy,
458+
max_file_size,
459+
)?)?
460+
.into()
486461
}
462+
#[cfg(mobile)]
463+
TargetKind::LogDir { .. } => continue,
464+
#[cfg(desktop)]
465+
TargetKind::LogDir { file_name } => {
466+
let path = app_handle.path().app_log_dir()?;
467+
if !path.exists() {
468+
fs::create_dir_all(&path)?;
469+
}
487470

488-
self.dispatch.apply()?;
471+
fern::log_file(get_log_file_path(
472+
&path,
473+
file_name.as_deref().unwrap_or(app_name),
474+
&rotation_strategy,
475+
&timezone_strategy,
476+
max_file_size,
477+
)?)?
478+
.into()
479+
}
480+
TargetKind::Webview => {
481+
let app_handle = app_handle.clone();
482+
483+
fern::Output::call(move |record| {
484+
let payload = RecordPayload {
485+
message: record.args().to_string(),
486+
level: record.level().into(),
487+
};
488+
let app_handle = app_handle.clone();
489+
tauri::async_runtime::spawn(async move {
490+
let _ = app_handle.emit("log://log", payload);
491+
});
492+
})
493+
}
494+
};
495+
target_dispatch = target_dispatch.chain(logger);
496+
497+
dispatch = dispatch.chain(target_dispatch);
498+
}
499+
500+
Ok(dispatch.into_log())
501+
}
502+
503+
fn plugin_builder<R: Runtime>() -> plugin::Builder<R> {
504+
plugin::Builder::new("log").invoke_handler(tauri::generate_handler![log])
505+
}
506+
507+
#[allow(clippy::type_complexity)]
508+
pub fn split<R: Runtime>(
509+
self,
510+
app_handle: &AppHandle<R>,
511+
) -> Result<(TauriPlugin<R>, log::LevelFilter, Box<dyn log::Log>), Error> {
512+
let plugin = Self::plugin_builder();
513+
let (max_level, log) = Self::acquire_logger(
514+
app_handle,
515+
self.dispatch,
516+
self.rotation_strategy,
517+
self.timezone_strategy,
518+
self.max_file_size,
519+
self.targets,
520+
)?;
521+
522+
Ok((plugin.build(), max_level, log))
523+
}
524+
525+
pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
526+
Self::plugin_builder()
527+
.setup(move |app_handle, _api| {
528+
let (max_level, log) = Self::acquire_logger(
529+
app_handle,
530+
self.dispatch,
531+
self.rotation_strategy,
532+
self.timezone_strategy,
533+
self.max_file_size,
534+
self.targets,
535+
)?;
536+
537+
attach_logger(max_level, log)?;
489538

490539
Ok(())
491540
})
492541
.build()
493542
}
494543
}
495544

545+
/// Attaches the given logger
546+
pub fn attach_logger(
547+
max_level: log::LevelFilter,
548+
log: Box<dyn log::Log>,
549+
) -> Result<(), log::SetLoggerError> {
550+
log::set_boxed_logger(log)?;
551+
log::set_max_level(max_level);
552+
Ok(())
553+
}
554+
496555
fn get_log_file_path(
497556
dir: &impl AsRef<Path>,
498557
file_name: &str,
499558
rotation_strategy: &RotationStrategy,
500559
timezone_strategy: &TimezoneStrategy,
501560
max_file_size: u128,
502-
) -> Result<PathBuf, Box<dyn std::error::Error>> {
561+
) -> Result<PathBuf, Error> {
503562
let path = dir.as_ref().join(format!("{file_name}.log"));
504563

505564
if path.exists() {

0 commit comments

Comments
 (0)