Compare commits

...

8 Commits

Author SHA1 Message Date
Tony 41f6274270 docs(log): some builder methods and defaults (#3460)
* docs(log): some builder methods and defaults

* Too much copy pastes

* Merge branch 'v2' into log-docs
2026-06-17 18:38:28 +08:00
Bajoca f08980f123 feat(log): rotate log file on each session (#3445)
* Update lib.rs

* docstring for file_open_strategy

* Create log-file-open-strategy.md

* Update plugins/log/src/lib.rs

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>

---------

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>
2026-06-17 16:25:24 +08:00
renovate[bot] 78df86f810 chore(deps): update dependency vite to v8.0.16 [security] (#3458)
* chore(deps): update dependency vite to v8.0.16 [security]

* Bump esbuild

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Tony <legendmastertony@gmail.com>
2026-06-16 11:18:07 +08:00
nandanpugalia aecec42d5d docs(http): fix connectTimeout option in README (#3451) 2026-06-15 10:08:31 +02:00
renovate[bot] 0767dcbd5c chore(deps): update dependency eslint-plugin-security to v4.0.1 (#3452) 2026-06-15 09:59:15 +02:00
Slava Minamoto 4be7690085 fix(positioner): return error instead of panicking when window has no monitor (#3449) 2026-06-11 21:05:20 +08:00
dependabot[bot] c2b3981248 chore(deps): bump tar in /plugins/updater/tests/updater-migration/v1-app (#3448)
Bumps [tar](https://github.com/composefs/tar-rs) from 0.4.45 to 0.4.46.
- [Release notes](https://github.com/composefs/tar-rs/releases)
- [Commits](https://github.com/composefs/tar-rs/compare/0.4.45...0.4.46)

---
updated-dependencies:
- dependency-name: tar
  dependency-version: 0.4.46
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-11 14:44:45 +08:00
beef 0c23b8ecfe remove unused dependency byte-unit from log plugin (#3446)
* remove unused dependency `byte-unit` from log plugin

* Update .changes/rmdep.md

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>

* Update .changes/rmdep.md

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>

---------

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>
2026-06-11 14:24:37 +08:00
11 changed files with 430 additions and 288 deletions
+6
View File
@@ -0,0 +1,6 @@
---
"log": minor:feat
"log-js": minor
---
Added the `FileOpenStrategy` for log rotation. It defaults to append into existing file if any (previous behaviour), and brings a new feature to create a new file per session: `FileOpenStrategy::Rotate`.
@@ -0,0 +1,6 @@
---
positioner: patch
positioner-js: patch
---
Replaced a panic in `calculate_position` with an error return when the window has no associated monitor (e.g. during display sleep or monitor reconfiguration).
+6
View File
@@ -0,0 +1,6 @@
---
"log": patch
"log-js": patch
---
Removed an unused dependency `byte-unit`.
Generated
-18
View File
@@ -772,17 +772,6 @@ version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "byte-unit"
version = "5.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cd29c3c585209b0cbc7309bfe3ed7efd8c84c21b7af29c8bfae908f8777174"
dependencies = [
"rust_decimal",
"serde",
"utf8-width",
]
[[package]] [[package]]
name = "bytecheck" name = "bytecheck"
version = "0.6.12" version = "0.6.12"
@@ -6657,7 +6646,6 @@ name = "tauri-plugin-log"
version = "2.8.0" version = "2.8.0"
dependencies = [ dependencies = [
"android_logger", "android_logger",
"byte-unit",
"fern", "fern",
"log", "log",
"objc2 0.6.4", "objc2 0.6.4",
@@ -7732,12 +7720,6 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8-width"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3"
[[package]] [[package]]
name = "utf8_iter" name = "utf8_iter"
version = "1.0.4" version = "1.0.4"
+1 -1
View File
@@ -17,7 +17,7 @@
"@rollup/plugin-typescript": "12.3.0", "@rollup/plugin-typescript": "12.3.0",
"eslint": "10.4.0", "eslint": "10.4.0",
"eslint-config-prettier": "10.1.8", "eslint-config-prettier": "10.1.8",
"eslint-plugin-security": "4.0.0", "eslint-plugin-security": "4.0.1",
"prettier": "3.8.3", "prettier": "3.8.3",
"rollup": "4.60.3", "rollup": "4.60.3",
"tslib": "2.8.1", "tslib": "2.8.1",
+1 -1
View File
@@ -62,7 +62,7 @@ Afterwards all the plugin's APIs are available through the JavaScript guest bind
import { fetch } from '@tauri-apps/plugin-http' import { fetch } from '@tauri-apps/plugin-http'
const response = await fetch('http://localhost:3003/users/2', { const response = await fetch('http://localhost:3003/users/2', {
method: 'GET', method: 'GET',
timeout: 30 connectTimeout: 30
}) })
``` ```
-1
View File
@@ -25,7 +25,6 @@ serde_json = { workspace = true }
tauri = { workspace = true } tauri = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
serde_repr = "0.1" serde_repr = "0.1"
byte-unit = "5"
log = { workspace = true, features = ["kv_unstable"] } log = { workspace = true, features = ["kv_unstable"] }
time = { version = "0.3", features = [ time = { version = "0.3", features = [
"formatting", "formatting",
+152 -4
View File
@@ -43,9 +43,10 @@ mod ios {
)); ));
} }
const DEFAULT_MAX_FILE_SIZE: u64 = 40000; const DEFAULT_MAX_FILE_SIZE: u64 = 40_000;
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_FILE_OPEN_STRATEGY: FileOpenStrategy = FileOpenStrategy::Append;
const DEFAULT_LOG_TARGETS: [Target; 2] = [ const DEFAULT_LOG_TARGETS: [Target; 2] = [
Target::new(TargetKind::Stdout), Target::new(TargetKind::Stdout),
Target::new(TargetKind::LogDir { file_name: None }), Target::new(TargetKind::LogDir { file_name: None }),
@@ -146,15 +147,26 @@ impl TimezoneStrategy {
} }
} }
#[derive(Debug, Clone, PartialEq)]
pub enum FileOpenStrategy {
/// Open existing file from last session and append, if any.
Append,
/// Create a new file on each session start, rotating the last session if any.
Rotate,
}
/// A custom log writer that rotates the log file when it exceeds specified size. /// A custom log writer that rotates the log file when it exceeds specified size.
struct RotatingFile { struct RotatingFile {
dir: PathBuf, dir: PathBuf,
file_name: String, file_name: String,
path: PathBuf, path: PathBuf,
/// Maximum file size before rotating in bytes
max_size: u64, max_size: u64,
/// Current file size in bytes
current_size: u64, current_size: u64,
rotation_strategy: RotationStrategy, rotation_strategy: RotationStrategy,
timezone_strategy: TimezoneStrategy, timezone_strategy: TimezoneStrategy,
file_open_strategy: FileOpenStrategy,
inner: Option<File>, inner: Option<File>,
buffer: Vec<u8>, buffer: Vec<u8>,
} }
@@ -166,6 +178,7 @@ impl RotatingFile {
max_size: u64, max_size: u64,
rotation_strategy: RotationStrategy, rotation_strategy: RotationStrategy,
timezone_strategy: TimezoneStrategy, timezone_strategy: TimezoneStrategy,
file_open_strategy: FileOpenStrategy,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let dir = dir.as_ref().to_path_buf(); let dir = dir.as_ref().to_path_buf();
let path = dir.join(&file_name).with_extension("log"); let path = dir.join(&file_name).with_extension("log");
@@ -178,12 +191,15 @@ impl RotatingFile {
current_size: 0, current_size: 0,
rotation_strategy, rotation_strategy,
timezone_strategy, timezone_strategy,
file_open_strategy,
inner: None, inner: None,
buffer: Vec::new(), buffer: Vec::new(),
}; };
rotator.open_file()?; rotator.open_file()?;
if rotator.current_size >= rotator.max_size { if rotator.current_size >= rotator.max_size
|| (rotator.current_size > 0 && rotator.file_open_strategy == FileOpenStrategy::Rotate)
{
rotator.rotate()?; rotator.rotate()?;
} }
if let RotationStrategy::KeepSome(keep_count) = rotator.rotation_strategy { if let RotationStrategy::KeepSome(keep_count) = rotator.rotation_strategy {
@@ -396,6 +412,7 @@ pub struct Builder {
dispatch: fern::Dispatch, dispatch: fern::Dispatch,
rotation_strategy: RotationStrategy, rotation_strategy: RotationStrategy,
timezone_strategy: TimezoneStrategy, timezone_strategy: TimezoneStrategy,
file_open_strategy: FileOpenStrategy,
max_file_size: u128, max_file_size: u128,
targets: Vec<Target>, targets: Vec<Target>,
is_skip_logger: bool, is_skip_logger: bool,
@@ -423,6 +440,7 @@ impl Default for Builder {
dispatch, dispatch,
rotation_strategy: DEFAULT_ROTATION_STRATEGY, rotation_strategy: DEFAULT_ROTATION_STRATEGY,
timezone_strategy: DEFAULT_TIMEZONE_STRATEGY, timezone_strategy: DEFAULT_TIMEZONE_STRATEGY,
file_open_strategy: DEFAULT_FILE_OPEN_STRATEGY,
max_file_size: DEFAULT_MAX_FILE_SIZE as u128, max_file_size: DEFAULT_MAX_FILE_SIZE as u128,
targets: DEFAULT_LOG_TARGETS.into(), targets: DEFAULT_LOG_TARGETS.into(),
is_skip_logger: false, is_skip_logger: false,
@@ -435,11 +453,18 @@ impl Builder {
Default::default() Default::default()
} }
/// Sets the [`RotationStrategy`].
///
/// Default is [`RotationStrategy::KeepOne`]
pub fn rotation_strategy(mut self, rotation_strategy: RotationStrategy) -> Self { pub fn rotation_strategy(mut self, rotation_strategy: RotationStrategy) -> Self {
self.rotation_strategy = rotation_strategy; self.rotation_strategy = rotation_strategy;
self self
} }
/// Sets the [`TimezoneStrategy`].
/// Calling this method overrides the format set in [`Self::format`].
///
/// Default is [`TimezoneStrategy::UseUtc`]
pub fn timezone_strategy(mut self, timezone_strategy: TimezoneStrategy) -> Self { pub fn timezone_strategy(mut self, timezone_strategy: TimezoneStrategy) -> Self {
self.timezone_strategy = timezone_strategy.clone(); self.timezone_strategy = timezone_strategy.clone();
@@ -456,15 +481,28 @@ impl Builder {
self self
} }
/// Sets the maximum file size for log rotation. /// Sets the strategy to open the log file.
/// ///
/// Values larger than `u64::MAX` will be clamped to `u64::MAX`. /// The default is [`FileOpenStrategy::Append`].
pub fn file_open_strategy(mut self, file_open_strategy: FileOpenStrategy) -> Self {
self.file_open_strategy = file_open_strategy;
self
}
/// Sets the maximum file size in bytes for log rotation.
///
/// Values larger than [`u64::MAX`] will be clamped to [`u64::MAX`].
/// In v3, this parameter will be changed to `u64`. /// In v3, this parameter will be changed to `u64`.
///
/// Default is `40_000`
pub fn max_file_size(mut self, max_file_size: u128) -> Self { pub fn max_file_size(mut self, max_file_size: u128) -> Self {
self.max_file_size = max_file_size.min(u64::MAX as u128); self.max_file_size = max_file_size.min(u64::MAX as u128);
self self
} }
/// Clears the format so that only the message is logged.
///
/// e.g. `log::info!("message")` will log out `message`
pub fn clear_format(mut self) -> Self { pub fn clear_format(mut self) -> Self {
self.dispatch = self.dispatch.format(|out, message, _record| { self.dispatch = self.dispatch.format(|out, message, _record| {
out.finish(format_args!("{message}")); out.finish(format_args!("{message}"));
@@ -472,6 +510,37 @@ impl Builder {
self self
} }
/// Sets the formatter of this dispatch. The closure should accept a
/// callback, a message and a log record, and write the resulting
/// format to the writer.
///
/// The log record is passed for completeness, but the `args()` method of
/// the record should be ignored, and the [`std::fmt::Arguments`] given
/// should be used instead. `record.args()` may be used to retrieve the
/// _original_ log message, but in order to allow for true log
/// chaining, formatters should use the given message instead whenever
/// including the message in the output.
///
/// To avoid all allocation of intermediate results, the formatter is
/// "completed" by calling a callback, which then calls the rest of the
/// logging chain with the new formatted message. The callback object keeps
/// track of if it was called or not via a stack boolean as well, so if
/// you don't use `out.finish` the log message will continue down
/// the logger chain unformatted.
///
/// Example usage:
///
/// ```
/// tauri_plugin_log::Builder::new()
/// .format(|out, message, record| {
/// out.finish(format_args!(
/// "[{} {}] {}",
/// record.level(),
/// record.target(),
/// message
/// ))
/// });
/// ```
pub fn format<F>(mut self, formatter: F) -> Self pub fn format<F>(mut self, formatter: F) -> Self
where where
F: Fn(FormatCallback, &Arguments, &Record) + Sync + Send + 'static, F: Fn(FormatCallback, &Arguments, &Record) + Sync + Send + 'static,
@@ -480,16 +549,64 @@ impl Builder {
self self
} }
/// Sets the overarching level filter for this logger.
/// All messages not already filtered by something set by [`Self::level_for`] will be affected.
///
/// All messages filtered will be discarded if less severe than the given level.
///
/// Default level is [`log::LevelFilter::Trace`].
pub fn level(mut self, level_filter: impl Into<LevelFilter>) -> Self { pub fn level(mut self, level_filter: impl Into<LevelFilter>) -> Self {
self.dispatch = self.dispatch.level(level_filter.into()); self.dispatch = self.dispatch.level(level_filter.into());
self self
} }
/// Sets a per-target log level filter. Default target for log messages is
/// `crate_name::module_name` or
/// `crate_name` for logs in the crate root. Targets can also be set with
/// `info!(target: "target-name", ...)`.
///
/// For each log record fern will first try to match the most specific
/// level_for, and then progressively more general ones until either a
/// matching level is found, or the default level is used.
///
/// For example, a log for the target `hyper::http::h1` will first test a
/// level_for for `hyper::http::h1`, then for `hyper::http`, then for
/// `hyper`, then use the default level.
///
/// Examples:
///
/// A program wants to include a lot of debugging output, but the library
/// "hyper" is known to work well, so debug output from it should be
/// excluded:
///
/// ```
/// # fn main() {
/// tauri_plugin_log::Builder::new()
/// .level(log::LevelFilter::Trace)
/// .level_for("hyper", log::LevelFilter::Info)
/// # ;
/// # }
/// ```
pub fn level_for(mut self, module: impl Into<Cow<'static, str>>, level: LevelFilter) -> Self { pub fn level_for(mut self, module: impl Into<Cow<'static, str>>, level: LevelFilter) -> Self {
self.dispatch = self.dispatch.level_for(module, level); self.dispatch = self.dispatch.level_for(module, level);
self self
} }
/// Adds a custom filter which can reject messages passing through this logger.
///
/// [`Self::level`] and [`Self::level_for`] are preferred if applicable.
///
/// Example usage:
///
/// ```
/// # fn main() {
/// tauri_plugin_log::Builder::new()
/// .level(log::LevelFilter::Info)
/// .filter(|metadata| {
/// // Reject messages with the `Error` log level.
/// metadata.level() != log::LevelFilter::Error
/// })
/// # }
pub fn filter<F>(mut self, filter: F) -> Self pub fn filter<F>(mut self, filter: F) -> Self
where where
F: Fn(&log::Metadata) -> bool + Send + Sync + 'static, F: Fn(&log::Metadata) -> bool + Send + Sync + 'static,
@@ -511,6 +628,19 @@ impl Builder {
/// tauri_plugin_log::Builder::new() /// tauri_plugin_log::Builder::new()
/// .target(Target::new(TargetKind::Webview)); /// .target(Target::new(TargetKind::Webview));
/// ``` /// ```
///
/// The default targets are
///
/// ```rust
/// # use tauri_plugin_log::{Target, TargetKind, Builder};
/// # Builder::new()
/// # .targets(
/// [
/// Target::new(TargetKind::Stdout),
/// Target::new(TargetKind::LogDir { file_name: None }),
/// ]
/// # );
/// ```
pub fn target(mut self, target: Target) -> Self { pub fn target(mut self, target: Target) -> Self {
self.targets.push(target); self.targets.push(target);
self self
@@ -543,6 +673,19 @@ impl Builder {
/// Target::new(TargetKind::LogDir { file_name: Some("rust".into()) }).filter(|metadata| !metadata.target().starts_with(WEBVIEW_TARGET)), /// Target::new(TargetKind::LogDir { file_name: Some("rust".into()) }).filter(|metadata| !metadata.target().starts_with(WEBVIEW_TARGET)),
/// ]); /// ]);
/// ``` /// ```
///
/// The default targets are
///
/// ```rust
/// # use tauri_plugin_log::{Target, TargetKind, Builder};
/// # Builder::new()
/// # .targets(
/// [
/// Target::new(TargetKind::Stdout),
/// Target::new(TargetKind::LogDir { file_name: None }),
/// ]
/// # );
/// ```
pub fn targets(mut self, targets: impl IntoIterator<Item = Target>) -> Self { 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
@@ -569,6 +712,7 @@ impl Builder {
mut dispatch: fern::Dispatch, mut dispatch: fern::Dispatch,
rotation_strategy: RotationStrategy, rotation_strategy: RotationStrategy,
timezone_strategy: TimezoneStrategy, timezone_strategy: TimezoneStrategy,
file_open_strategy: FileOpenStrategy,
max_file_size: u64, max_file_size: u64,
targets: Vec<Target>, targets: Vec<Target>,
) -> Result<(log::LevelFilter, Box<dyn log::Log>), Error> { ) -> Result<(log::LevelFilter, Box<dyn log::Log>), Error> {
@@ -621,6 +765,7 @@ impl Builder {
max_file_size, max_file_size,
rotation_strategy.clone(), rotation_strategy.clone(),
timezone_strategy.clone(), timezone_strategy.clone(),
file_open_strategy.clone(),
)?; )?;
fern::Output::writer(Box::new(rotator), "\n") fern::Output::writer(Box::new(rotator), "\n")
} }
@@ -636,6 +781,7 @@ impl Builder {
max_file_size, max_file_size,
rotation_strategy.clone(), rotation_strategy.clone(),
timezone_strategy.clone(), timezone_strategy.clone(),
file_open_strategy.clone(),
)?; )?;
fern::Output::writer(Box::new(rotator), "\n") fern::Output::writer(Box::new(rotator), "\n")
} }
@@ -681,6 +827,7 @@ impl Builder {
self.dispatch, self.dispatch,
self.rotation_strategy, self.rotation_strategy,
self.timezone_strategy, self.timezone_strategy,
self.file_open_strategy,
self.max_file_size as u64, self.max_file_size as u64,
self.targets, self.targets,
)?; )?;
@@ -697,6 +844,7 @@ impl Builder {
self.dispatch, self.dispatch,
self.rotation_strategy, self.rotation_strategy,
self.timezone_strategy, self.timezone_strategy,
self.file_open_strategy,
self.max_file_size as u64, self.max_file_size as u64,
self.targets, self.targets,
)?; )?;
+3 -1
View File
@@ -152,7 +152,9 @@ fn calculate_position<R: Runtime>(
) -> Result<PhysicalPosition<i32>> { ) -> Result<PhysicalPosition<i32>> {
use Position::*; use Position::*;
let screen = window.current_monitor()?.unwrap(); let screen = window.current_monitor()?.ok_or_else(|| {
tauri::Error::Io(std::io::Error::other("No monitor found for the window"))
})?;
// Only use the screen_position for the Tray independent positioning, // Only use the screen_position for the Tray independent positioning,
// because a tray event may not be called on the currently active monitor. // because a tray event may not be called on the currently active monitor.
let screen_position = screen.position(); let screen_position = screen.position();
+2 -2
View File
@@ -2950,9 +2950,9 @@ dependencies = [
[[package]] [[package]]
name = "tar" name = "tar"
version = "0.4.45" version = "0.4.46"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
dependencies = [ dependencies = [
"filetime", "filetime",
"libc", "libc",
+253 -260
View File
File diff suppressed because it is too large Load Diff