Browse Source

feat(core): add tracing for vital functionality closes #5204 (#8289)

* feat(core): add tracing for vital functionality

* Update core/tauri-runtime-wry/src/lib.rs [skip ci]

* Update Cargo.toml [skip ci]

* tracing feature

* wry 0.24.6

* add change tag

* add tracing to CI test

* enhance spans for update check

* remove app from debug impl
Lucas Nogueira 1 year ago
parent
commit
5e05236b49

+ 7 - 0
.changes/tracing.md

@@ -0,0 +1,7 @@
+---
+"tauri": patch:enhance
+"tauri-runtime-wry": patch:enhance
+"tauri-macros": patch:enhance
+---
+
+Added tracing for window startup, plugins, `Window::eval`, events, IPC, updater and custom protocol request handlers behind the `tracing` feature flag.

+ 1 - 1
.github/workflows/test-core.yml

@@ -56,7 +56,7 @@ jobs:
               key: api-all
             }
           - {
-              args: --features compression,wry,linux-protocol-headers,isolation,custom-protocol,api-all,cli,updater,system-tray,windows7-compat,http-multipart,test,
+              args: --features tracing,compression,wry,linux-protocol-headers,isolation,custom-protocol,api-all,cli,updater,system-tray,windows7-compat,http-multipart,test,
               key: all
             }
 

+ 2 - 1
core/tauri-macros/Cargo.toml

@@ -16,7 +16,7 @@ readme = "README.md"
 proc-macro = true
 
 [dependencies]
-proc-macro2 = "1"
+proc-macro2 = { version = "1", features = ["span-locations"] }
 quote = "1"
 syn = { version = "1", features = [ "full" ] }
 heck = "0.4"
@@ -30,3 +30,4 @@ isolation = [ "tauri-codegen/isolation" ]
 shell-scope = [ "tauri-codegen/shell-scope" ]
 config-json5 = [ "tauri-codegen/config-json5", "tauri-utils/config-json5" ]
 config-toml = [ "tauri-codegen/config-toml", "tauri-utils/config-toml" ]
+tracing = []

+ 58 - 5
core/tauri-macros/src/command/wrapper.rs

@@ -161,21 +161,51 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
   }
 
   // body to the command wrapper or a `compile_error!` of an error occurred while parsing it.
-  let body = syn::parse::<WrapperAttributes>(attributes)
+  let (body, attributes) = syn::parse::<WrapperAttributes>(attributes)
     .map(|mut attrs| {
       if function.sig.asyncness.is_some() {
         attrs.execution_context = ExecutionContext::Async;
       }
       attrs
     })
-    .and_then(|attrs| match attrs.execution_context {
-      ExecutionContext::Async => body_async(&function, &invoke, attrs.argument_case),
-      ExecutionContext::Blocking => body_blocking(&function, &invoke, attrs.argument_case),
+    .and_then(|attrs| {
+      let body = match attrs.execution_context {
+        ExecutionContext::Async => body_async(&function, &invoke, attrs.argument_case),
+        ExecutionContext::Blocking => body_blocking(&function, &invoke, attrs.argument_case),
+      };
+      body.map(|b| (b, Some(attrs)))
     })
-    .unwrap_or_else(syn::Error::into_compile_error);
+    .unwrap_or_else(|e| (syn::Error::into_compile_error(e), None));
 
   let Invoke { message, resolver } = invoke;
 
+  let kind = match attributes.as_ref().map(|a| &a.execution_context) {
+    Some(ExecutionContext::Async) if function.sig.asyncness.is_none() => "sync_threadpool",
+    Some(ExecutionContext::Async) => "async",
+    Some(ExecutionContext::Blocking) => "sync",
+    _ => "sync",
+  };
+
+  let loc = function.span().start();
+  let line = loc.line;
+  let col = loc.column;
+
+  let maybe_span = if cfg!(feature = "tracing") {
+    quote!({
+      let _span = tracing::debug_span!(
+        "ipc::request::handler",
+        cmd = #message.command(),
+        kind = #kind,
+        loc.line = #line,
+        loc.col = #col,
+        is_internal = false,
+      )
+      .entered();
+    })
+  } else {
+    quote!()
+  };
+
   // Rely on rust 2018 edition to allow importing a macro from a path.
   quote!(
     #async_command_check
@@ -193,6 +223,8 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
           #[allow(unused_variables)]
           let ::tauri::Invoke { message: #message, resolver: #resolver } = $invoke;
 
+          #maybe_span
+
           #body
       }};
     }
@@ -212,6 +244,20 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
 fn body_async(function: &ItemFn, invoke: &Invoke, case: ArgumentCase) -> syn::Result<TokenStream2> {
   let Invoke { message, resolver } = invoke;
   parse_args(function, message, case).map(|args| {
+    #[cfg(feature = "tracing")]
+    quote! {
+      use tracing::Instrument;
+
+      let span = tracing::debug_span!("ipc::request::run");
+      #resolver.respond_async_serialized(async move {
+        let result = $path(#(#args?),*);
+        let kind = (&result).async_kind();
+        kind.future(result).await
+      }
+      .instrument(span));
+    }
+
+    #[cfg(not(feature = "tracing"))]
     quote! {
       #resolver.respond_async_serialized(async move {
         let result = $path(#(#args?),*);
@@ -241,7 +287,14 @@ fn body_blocking(
     Err(err) => return #resolver.invoke_error(err),
   });
 
+  let maybe_span = if cfg!(feature = "tracing") {
+    quote!(let _span = tracing::debug_span!("ipc::request::run").entered();)
+  } else {
+    quote!()
+  };
+
   Ok(quote! {
+    #maybe_span
     let result = $path(#(match #args #match_body),*);
     let kind = (&result).blocking_kind();
     kind.block(result, #resolver);

+ 3 - 1
core/tauri-runtime-wry/Cargo.toml

@@ -13,12 +13,13 @@ exclude = [ "CHANGELOG.md", "/target" ]
 readme = "README.md"
 
 [dependencies]
-wry = { version = "0.24.4", default-features = false, features = [ "file-drop", "protocol" ] }
+wry = { version = "0.24.6", default-features = false, features = [ "file-drop", "protocol" ] }
 tauri-runtime = { version = "0.14.1", path = "../tauri-runtime" }
 tauri-utils = { version = "1.5.0", path = "../tauri-utils" }
 uuid = { version = "1", features = [ "v4" ] }
 rand = "0.8"
 raw-window-handle = "0.5"
+tracing = { version = "0.1", optional = true }
 
 [target."cfg(windows)".dependencies]
 webview2-com = "0.19.1"
@@ -48,3 +49,4 @@ objc-exception = [ "wry/objc-exception" ]
 global-shortcut = [ "tauri-runtime/global-shortcut" ]
 clipboard = [ "tauri-runtime/clipboard" ]
 linux-headers = [ "wry/linux-headers", "webkit2gtk/v2_36" ]
+tracing = [ "dep:tracing", "wry/tracing" ]

+ 107 - 0
core/tauri-runtime-wry/src/lib.rs

@@ -244,6 +244,32 @@ impl<T: UserEvent> Context<T> {
   }
 }
 
+#[cfg(feature = "tracing")]
+#[derive(Debug, Clone, Default)]
+pub struct ActiveTraceSpanStore(Rc<RefCell<Vec<ActiveTracingSpan>>>);
+
+#[cfg(feature = "tracing")]
+impl ActiveTraceSpanStore {
+  pub fn remove_window_draw(&self, window_id: WindowId) {
+    let mut store = self.0.borrow_mut();
+    if let Some(index) = store
+      .iter()
+      .position(|t| matches!(t, ActiveTracingSpan::WindowDraw { id, span: _ } if id == &window_id))
+    {
+      store.remove(index);
+    }
+  }
+}
+
+#[cfg(feature = "tracing")]
+#[derive(Debug)]
+pub enum ActiveTracingSpan {
+  WindowDraw {
+    id: WindowId,
+    span: tracing::span::EnteredSpan,
+  },
+}
+
 #[derive(Debug, Clone)]
 pub struct DispatcherMainThreadContext<T: UserEvent> {
   pub window_target: EventLoopWindowTarget<Message<T>>,
@@ -255,6 +281,8 @@ pub struct DispatcherMainThreadContext<T: UserEvent> {
   pub windows: Rc<RefCell<HashMap<WebviewId, WindowWrapper>>>,
   #[cfg(all(desktop, feature = "system-tray"))]
   system_tray_manager: SystemTrayManager,
+  #[cfg(feature = "tracing")]
+  pub active_tracing_spans: ActiveTraceSpanStore,
 }
 
 // SAFETY: we ensure this type is only used on the main thread.
@@ -1135,7 +1163,10 @@ pub enum WindowMessage {
 
 #[derive(Debug, Clone)]
 pub enum WebviewMessage {
+  #[cfg(not(feature = "tracing"))]
   EvaluateScript(String),
+  #[cfg(feature = "tracing")]
+  EvaluateScript(String, Sender<()>, tracing::Span),
   #[allow(dead_code)]
   WebviewEvent(WebviewEvent),
   Print,
@@ -1651,6 +1682,21 @@ impl<T: UserEvent> Dispatch<T> for WryDispatcher<T> {
     )
   }
 
+  #[cfg(feature = "tracing")]
+  fn eval_script<S: Into<String>>(&self, script: S) -> Result<()> {
+    // use a channel so the EvaluateScript task uses the current span as parent
+    let (tx, rx) = channel();
+    getter!(
+      self,
+      rx,
+      Message::Webview(
+        self.window_id,
+        WebviewMessage::EvaluateScript(script.into(), tx, tracing::Span::current()),
+      )
+    )
+  }
+
+  #[cfg(not(feature = "tracing"))]
   fn eval_script<S: Into<String>>(&self, script: S) -> Result<()> {
     send_user_message(
       &self.context,
@@ -1962,6 +2008,8 @@ impl<T: UserEvent> Wry<T> {
         windows,
         #[cfg(all(desktop, feature = "system-tray"))]
         system_tray_manager,
+        #[cfg(feature = "tracing")]
+        active_tracing_spans: Default::default(),
       },
     };
 
@@ -2165,6 +2213,9 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
     #[cfg(all(desktop, feature = "system-tray"))]
     let system_tray_manager = self.context.main_thread.system_tray_manager.clone();
 
+    #[cfg(feature = "tracing")]
+    let active_tracing_spans = self.context.main_thread.active_tracing_spans.clone();
+
     #[cfg(all(desktop, feature = "global-shortcut"))]
     let global_shortcut_manager = self.context.main_thread.global_shortcut_manager.clone();
     #[cfg(all(desktop, feature = "global-shortcut"))]
@@ -2202,6 +2253,8 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
               clipboard_manager: clipboard_manager.clone(),
               #[cfg(all(desktop, feature = "system-tray"))]
               system_tray_manager: system_tray_manager.clone(),
+              #[cfg(feature = "tracing")]
+              active_tracing_spans: active_tracing_spans.clone(),
             },
             web_context,
           );
@@ -2226,6 +2279,8 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
             clipboard_manager: clipboard_manager.clone(),
             #[cfg(all(desktop, feature = "system-tray"))]
             system_tray_manager: system_tray_manager.clone(),
+            #[cfg(feature = "tracing")]
+            active_tracing_spans: active_tracing_spans.clone(),
           },
           web_context,
         );
@@ -2240,6 +2295,9 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
     let web_context = self.context.main_thread.web_context;
     let mut plugins = self.plugins;
 
+    #[cfg(feature = "tracing")]
+    let active_tracing_spans = self.context.main_thread.active_tracing_spans.clone();
+
     #[cfg(all(desktop, feature = "system-tray"))]
     let system_tray_manager = self.context.main_thread.system_tray_manager;
 
@@ -2272,6 +2330,8 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
             clipboard_manager: clipboard_manager.clone(),
             #[cfg(all(desktop, feature = "system-tray"))]
             system_tray_manager: system_tray_manager.clone(),
+            #[cfg(feature = "tracing")]
+            active_tracing_spans: active_tracing_spans.clone(),
           },
           &web_context,
         );
@@ -2295,6 +2355,8 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
           clipboard_manager: clipboard_manager.clone(),
           #[cfg(all(desktop, feature = "system-tray"))]
           system_tray_manager: system_tray_manager.clone(),
+          #[cfg(feature = "tracing")]
+          active_tracing_spans: active_tracing_spans.clone(),
         },
         &web_context,
       );
@@ -2314,6 +2376,8 @@ pub struct EventLoopIterationContext<'a, T: UserEvent> {
   pub clipboard_manager: Arc<Mutex<Clipboard>>,
   #[cfg(all(desktop, feature = "system-tray"))]
   pub system_tray_manager: SystemTrayManager,
+  #[cfg(feature = "tracing")]
+  pub active_tracing_spans: ActiveTraceSpanStore,
 }
 
 struct UserMessageContext {
@@ -2590,6 +2654,19 @@ fn handle_user_message<T: UserEvent>(
       }
     }
     Message::Webview(id, webview_message) => match webview_message {
+      #[cfg(feature = "tracing")]
+      WebviewMessage::EvaluateScript(script, tx, span) => {
+        let _span = span.entered();
+        if let Some(WindowHandle::Webview { inner: webview, .. }) =
+          windows.borrow().get(&id).and_then(|w| w.inner.as_ref())
+        {
+          if let Err(e) = webview.evaluate_script(&script) {
+            debug_eprintln!("{}", e);
+          }
+        }
+        tx.send(()).unwrap();
+      }
+      #[cfg(not(feature = "tracing"))]
       WebviewMessage::EvaluateScript(script) => {
         if let Some(WindowHandle::Webview { inner: webview, .. }) =
           windows.borrow().get(&id).and_then(|w| w.inner.as_ref())
@@ -2758,6 +2835,8 @@ fn handle_event_loop<T: UserEvent>(
     clipboard_manager,
     #[cfg(all(desktop, feature = "system-tray"))]
     system_tray_manager,
+    #[cfg(feature = "tracing")]
+    active_tracing_spans,
   } = context;
   if *control_flow != ControlFlow::Exit {
     *control_flow = ControlFlow::Wait;
@@ -2780,6 +2859,11 @@ fn handle_event_loop<T: UserEvent>(
       callback(RunEvent::Exit);
     }
 
+    #[cfg(feature = "tracing")]
+    Event::RedrawRequested(id) => {
+      active_tracing_spans.remove_window_draw(id);
+    }
+
     #[cfg(all(desktop, feature = "global-shortcut"))]
     Event::GlobalShortcutEvent(accelerator_id) => {
       for (id, handler) in &*global_shortcut_manager_handle.listeners.lock().unwrap() {
@@ -3123,6 +3207,14 @@ fn create_webview<T: UserEvent>(
   #[cfg(windows)]
   let proxy = context.proxy.clone();
 
+  #[cfg(feature = "tracing")]
+  let _webview_create_span = tracing::debug_span!("wry::webview::create").entered();
+  #[cfg(feature = "tracing")]
+  let window_draw_span = tracing::debug_span!("wry::window::draw").entered();
+  #[cfg(feature = "tracing")]
+  let window_create_span =
+    tracing::debug_span!(parent: &window_draw_span, "wry::window::create").entered();
+
   let window_event_listeners = WindowEventListeners::default();
 
   #[cfg(windows)]
@@ -3157,6 +3249,21 @@ fn create_webview<T: UserEvent>(
   let focused = window_builder.inner.window.focused;
   let window = window_builder.inner.build(event_loop).unwrap();
 
+  #[cfg(feature = "tracing")]
+  {
+    drop(window_create_span);
+
+    context
+      .main_thread
+      .active_tracing_spans
+      .0
+      .borrow_mut()
+      .push(ActiveTracingSpan::WindowDraw {
+        id: window.id(),
+        span: window_draw_span,
+      });
+  }
+
   webview_id_map.insert(window.id(), window_id);
 
   if window_builder.center {

+ 2 - 0
core/tauri/Cargo.toml

@@ -94,6 +94,7 @@ png = { version = "0.17", optional = true }
 ico = { version = "0.2.0", optional = true }
 encoding_rs = "0.8.31"
 sys-locale = { version = "0.2.3", optional = true }
+tracing = { version = "0.1", optional = true }
 
 [target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
 rfd = { version = "0.10", optional = true, features = [ "gtk3", "common-controls-v6" ] }
@@ -135,6 +136,7 @@ cargo_toml = "0.11"
 
 [features]
 default = [ "wry", "compression", "objc-exception" ]
+tracing = [ "dep:tracing", "tauri-macros/tracing", "tauri-runtime-wry/tracing" ]
 test = [ ]
 compression = [ "tauri-macros/compression", "tauri-utils/compression" ]
 wry = [ "tauri-runtime-wry" ]

+ 14 - 21
core/tauri/src/app.rs

@@ -474,26 +474,14 @@ impl<R: Runtime> AppHandle<R> {
   ///     Ok(())
   ///   });
   /// ```
-  pub fn plugin<P: Plugin<R> + 'static>(&self, mut plugin: P) -> crate::Result<()> {
-    plugin
-      .initialize(
-        self,
-        self
-          .config()
-          .plugins
-          .0
-          .get(plugin.name())
-          .cloned()
-          .unwrap_or_default(),
-      )
-      .map_err(|e| crate::Error::PluginInitialization(plugin.name().to_string(), e.to_string()))?;
-    self
-      .manager()
-      .inner
-      .plugins
-      .lock()
-      .unwrap()
-      .register(plugin);
+  #[cfg_attr(feature = "tracing", tracing::instrument(name = "app::plugin::register", skip(plugin), fields(name = plugin.name())))]
+  pub fn plugin<P: Plugin<R> + 'static>(&self, plugin: P) -> crate::Result<()> {
+    let mut plugin = Box::new(plugin) as Box<dyn Plugin<R>>;
+
+    let mut store = self.manager().inner.plugins.lock().unwrap();
+    store.initialize(&mut plugin, self, &self.config().plugins)?;
+    store.register(plugin);
+
     Ok(())
   }
 
@@ -913,6 +901,7 @@ impl<R: Runtime> App<R> {
   /// }
   /// ```
   #[cfg(desktop)]
+  #[cfg_attr(feature = "tracing", tracing::instrument(name = "app::run_iteration"))]
   pub fn run_iteration(&mut self) -> crate::runtime::RunIteration {
     let manager = self.manager.clone();
     let app_handle = self.handle();
@@ -1206,7 +1195,7 @@ impl<R: Runtime> Builder<R> {
   /// ```
   #[must_use]
   pub fn plugin<P: Plugin<R> + 'static>(mut self, plugin: P) -> Self {
-    self.plugins.register(plugin);
+    self.plugins.register(Box::new(plugin));
     self
   }
 
@@ -1557,6 +1546,10 @@ impl<R: Runtime> Builder<R> {
 
   /// Builds the application.
   #[allow(clippy::type_complexity)]
+  #[cfg_attr(
+    feature = "tracing",
+    tracing::instrument(name = "app::build", skip_all)
+  )]
   pub fn build<A: Assets>(mut self, context: Context<A>) -> crate::Result<App<R>> {
     #[cfg(target_os = "macos")]
     if self.menu.is_none() && self.enable_macos_default_menu {

+ 4 - 0
core/tauri/src/command.rs

@@ -52,6 +52,8 @@ impl<'de, D: Deserialize<'de>, R: Runtime> CommandArg<'de, R> for D {
   fn from_command(command: CommandItem<'de, R>) -> Result<D, InvokeError> {
     let name = command.name;
     let arg = command.key;
+    #[cfg(feature = "tracing")]
+    let _span = tracing::trace_span!("ipc::request::deserialize_arg", arg = arg).entered();
     Self::deserialize(command).map_err(|e| crate::Error::InvalidArgs(name, arg, e).into())
   }
 }
@@ -160,6 +162,8 @@ pub mod private {
   use serde::Serialize;
   use serde_json::Value;
   use std::future::Future;
+  #[cfg(feature = "tracing")]
+  pub use tracing;
 
   // ===== impl Serialize =====
 

+ 34 - 5
core/tauri/src/hooks.rs

@@ -11,6 +11,8 @@ use serde::{Deserialize, Serialize};
 use serde_json::Value as JsonValue;
 use serialize_to_javascript::{default_template, Template};
 use std::{future::Future, sync::Arc};
+#[cfg(feature = "tracing")]
+use tracing::Instrument;
 
 use tauri_macros::default_runtime;
 
@@ -181,9 +183,16 @@ impl<R: Runtime> InvokeResolver<R> {
     T: Serialize,
     F: Future<Output = Result<T, InvokeError>> + Send + 'static,
   {
-    crate::async_runtime::spawn(async move {
+    let task = async move {
       Self::return_task(self.window, task, self.callback, self.error).await;
-    });
+    };
+    #[cfg(feature = "tracing")]
+    {
+      let span = tracing::trace_span!("ipc::request::respond");
+      crate::async_runtime::spawn(task.instrument(span));
+    }
+    #[cfg(not(feature = "tracing"))]
+    crate::async_runtime::spawn(task);
   }
 
   /// Reply to the invoke promise with an async task which is already serialized.
@@ -191,27 +200,40 @@ impl<R: Runtime> InvokeResolver<R> {
   where
     F: Future<Output = Result<JsonValue, InvokeError>> + Send + 'static,
   {
-    crate::async_runtime::spawn(async move {
+    let task = async move {
       let response = match task.await {
         Ok(ok) => InvokeResponse::Ok(ok),
         Err(err) => InvokeResponse::Err(err),
       };
       Self::return_result(self.window, response, self.callback, self.error)
-    });
+    };
+    #[cfg(feature = "tracing")]
+    {
+      let span = tracing::trace_span!("ipc::request::respond");
+      crate::async_runtime::spawn(task.instrument(span));
+    }
+    #[cfg(not(feature = "tracing"))]
+    crate::async_runtime::spawn(task);
   }
 
   /// Reply to the invoke promise with a serializable value.
   pub fn respond<T: Serialize>(self, value: Result<T, InvokeError>) {
+    #[cfg(feature = "tracing")]
+    let _span = tracing::trace_span!("ipc::request::respond").entered();
     Self::return_result(self.window, value.into(), self.callback, self.error)
   }
 
   /// Resolve the invoke promise with a value.
   pub fn resolve<T: Serialize>(self, value: T) {
+    #[cfg(feature = "tracing")]
+    let _span = tracing::trace_span!("ipc::request::respond").entered();
     Self::return_result(self.window, Ok(value).into(), self.callback, self.error)
   }
 
   /// Reject the invoke promise with a value.
   pub fn reject<T: Serialize>(self, value: T) {
+    #[cfg(feature = "tracing")]
+    let _span = tracing::trace_span!("ipc::request::respond").entered();
     Self::return_result(
       self.window,
       Result::<(), _>::Err(value.into()).into(),
@@ -222,6 +244,8 @@ impl<R: Runtime> InvokeResolver<R> {
 
   /// Reject the invoke promise with an [`InvokeError`].
   pub fn invoke_error(self, error: InvokeError) {
+    #[cfg(feature = "tracing")]
+    let _span = tracing::trace_span!("ipc::request::respond").entered();
     Self::return_result(self.window, error.into(), self.callback, self.error)
   }
 
@@ -230,7 +254,7 @@ impl<R: Runtime> InvokeResolver<R> {
   ///
   /// If the Result `is_ok()`, the callback will be the `success_callback` function name and the argument will be the Ok value.
   /// If the Result `is_err()`, the callback will be the `error_callback` function name and the argument will be the Err value.
-  pub async fn return_task<T, F>(
+  pub(crate) async fn return_task<T, F>(
     window: Window<R>,
     task: F,
     success_callback: CallbackFn,
@@ -258,6 +282,9 @@ impl<R: Runtime> InvokeResolver<R> {
     success_callback: CallbackFn,
     error_callback: CallbackFn,
   ) {
+    #[cfg(feature = "tracing")]
+    let _span =
+      tracing::trace_span!("ipc::request::response", response = format!("{response:?}")).entered();
     (window.invoke_responder())(window, response, success_callback, error_callback);
   }
 }
@@ -268,6 +295,8 @@ pub fn window_invoke_responder<R: Runtime>(
   success_callback: CallbackFn,
   error_callback: CallbackFn,
 ) {
+  #[cfg(feature = "tracing")]
+  let _span = tracing::trace_span!("ipc::request::eval_response").entered();
   let callback_string =
     match format_callback_result(response.into_result(), success_callback, error_callback) {
       Ok(callback_string) => callback_string,

+ 17 - 0
core/tauri/src/lib.rs

@@ -11,6 +11,7 @@
 //! The following are a list of [Cargo features](https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section) that can be enabled or disabled:
 //!
 //! - **wry** *(enabled by default)*: Enables the [wry](https://github.com/tauri-apps/wry) runtime. Only disable it if you want a custom runtime.
+//! - **tracing**: Enables [`tracing`](https://docs.rs/tracing/latest/tracing) for window startup, plugins, `Window::eval`, events, IPC, updater and custom protocol request handlers.
 //! - **test**: Enables the [`test`] module exposing unit test helpers.
 //! - **dox**: Internal feature to generate Rust documentation without linking on Linux.
 //! - **objc-exception**: Wrap each msg_send! in a @try/@catch and panics if an exception is caught, preventing Objective-C from unwinding into Rust.
@@ -625,6 +626,10 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
   ///   app.emit_all("synchronized", ());
   /// }
   /// ```
+  #[cfg_attr(
+    feature = "tracing",
+    tracing::instrument("app::emit::all", skip(self, payload))
+  )]
   fn emit_all<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()> {
     self.manager().emit_filter(event, None, payload, |_| true)
   }
@@ -641,6 +646,10 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
   ///   app.emit_filter("synchronized", (), |w| w.label().starts_with("foo-"));
   /// }
   /// ```
+  #[cfg_attr(
+    feature = "tracing",
+    tracing::instrument("app::emit::filter", skip(self, payload, filter))
+  )]
   fn emit_filter<S, F>(&self, event: &str, payload: S, filter: F) -> Result<()>
   where
     S: Serialize + Clone,
@@ -664,6 +673,10 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
   ///   }
   /// }
   /// ```
+  #[cfg_attr(
+    feature = "tracing",
+    tracing::instrument("app::emit::to", skip(self, payload))
+  )]
   fn emit_to<S: Serialize + Clone>(&self, label: &str, event: &str, payload: S) -> Result<()> {
     self
       .manager()
@@ -728,6 +741,10 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
   ///   }
   /// }
   /// ```
+  #[cfg_attr(
+    feature = "tracing",
+    tracing::instrument("app::emit::rust", skip(self))
+  )]
   fn trigger_global(&self, event: &str, data: Option<String>) {
     self.manager().trigger(event, None, data)
   }

+ 12 - 1
core/tauri/src/manager.rs

@@ -593,6 +593,10 @@ impl<R: Runtime> WindowManager<R> {
   ) -> WebviewIpcHandler<EventLoopMessage, R> {
     let manager = self.clone();
     Box::new(move |window, #[allow(unused_mut)] mut request| {
+      #[cfg(feature = "tracing")]
+      let _span =
+        tracing::trace_span!("ipc::request", kind = "post-message", request = request).entered();
+
       let window = Window::new(manager.clone(), window, app_handle.clone());
 
       #[cfg(feature = "isolation")]
@@ -614,9 +618,14 @@ impl<R: Runtime> WindowManager<R> {
 
       match serde_json::from_str::<InvokePayload>(&request) {
         Ok(message) => {
+          #[cfg(feature = "tracing")]
+          let _span = tracing::trace_span!("ipc::request::handle", cmd = message.cmd).entered();
+
           let _ = window.on_message(message);
         }
         Err(e) => {
+          #[cfg(feature = "tracing")]
+          tracing::trace!("ipc::request::error {}", e);
           let error: crate::Error = e.into();
           let _ = window.eval(&format!(
             r#"console.error({})"#,
@@ -958,7 +967,7 @@ impl<R: Runtime> WindowManager<R> {
       .plugins
       .lock()
       .expect("poisoned plugin store")
-      .initialize(app, &self.inner.config.plugins)
+      .initialize_all(app, &self.inner.config.plugins)
   }
 
   pub fn prepare_window(
@@ -1148,6 +1157,8 @@ impl<R: Runtime> WindowManager<R> {
     S: Serialize + Clone,
     F: Fn(&Window<R>) -> bool,
   {
+    #[cfg(feature = "tracing")]
+    let _span = tracing::debug_span!("emit::run").entered();
     let emit_args = WindowEmitArgs::from(event, source_window_label, payload)?;
     assert_event_name_is_valid(event);
     self

+ 44 - 19
core/tauri/src/plugin.rs

@@ -575,8 +575,8 @@ impl<R: Runtime> PluginStore<R> {
   /// Adds a plugin to the store.
   ///
   /// Returns `true` if a plugin with the same name is already in the store.
-  pub fn register<P: Plugin<R> + 'static>(&mut self, plugin: P) -> bool {
-    self.store.insert(plugin.name(), Box::new(plugin)).is_some()
+  pub fn register(&mut self, plugin: Box<dyn Plugin<R>>) -> bool {
+    self.store.insert(plugin.name(), plugin).is_some()
   }
 
   /// Removes the plugin with the given name from the store.
@@ -584,20 +584,26 @@ impl<R: Runtime> PluginStore<R> {
     self.store.remove(plugin).is_some()
   }
 
-  /// Initializes all plugins in the store.
+  /// Initializes the given plugin.
   pub(crate) fn initialize(
+    &self,
+    plugin: &mut Box<dyn Plugin<R>>,
+    app: &AppHandle<R>,
+    config: &PluginConfig,
+  ) -> crate::Result<()> {
+    initialize(plugin, app, config)
+  }
+
+  /// Initializes all plugins in the store.
+  pub(crate) fn initialize_all(
     &mut self,
     app: &AppHandle<R>,
     config: &PluginConfig,
   ) -> crate::Result<()> {
-    self.store.values_mut().try_for_each(|plugin| {
-      plugin
-        .initialize(
-          app,
-          config.0.get(plugin.name()).cloned().unwrap_or_default(),
-        )
-        .map_err(|e| crate::Error::PluginInitialization(plugin.name().to_string(), e.to_string()))
-    })
+    self
+      .store
+      .values_mut()
+      .try_for_each(|plugin| initialize(plugin, app, config))
   }
 
   /// Generates an initialization script from all plugins in the store.
@@ -613,18 +619,21 @@ impl<R: Runtime> PluginStore<R> {
 
   /// Runs the created hook for all plugins in the store.
   pub(crate) fn created(&mut self, window: Window<R>) {
-    self
-      .store
-      .values_mut()
-      .for_each(|plugin| plugin.created(window.clone()))
+    self.store.values_mut().for_each(|plugin| {
+      #[cfg(feature = "tracing")]
+      let _span = tracing::trace_span!("plugin::hooks::created", name = plugin.name()).entered();
+      plugin.created(window.clone())
+    })
   }
 
   /// Runs the on_page_load hook for all plugins in the store.
   pub(crate) fn on_page_load(&mut self, window: Window<R>, payload: PageLoadPayload) {
-    self
-      .store
-      .values_mut()
-      .for_each(|plugin| plugin.on_page_load(window.clone(), payload.clone()))
+    self.store.values_mut().for_each(|plugin| {
+      #[cfg(feature = "tracing")]
+      let _span =
+        tracing::trace_span!("plugin::hooks::on_page_load", name = plugin.name()).entered();
+      plugin.on_page_load(window.clone(), payload.clone())
+    })
   }
 
   /// Runs the on_event hook for all plugins in the store.
@@ -646,9 +655,25 @@ impl<R: Runtime> PluginStore<R> {
         .next()
         .map(|c| c.to_string())
         .unwrap_or_else(String::new);
+      #[cfg(feature = "tracing")]
+      let _span = tracing::trace_span!("plugin::hooks::ipc", name = plugin.name()).entered();
       plugin.extend_api(invoke);
     } else {
       invoke.resolver.reject(format!("plugin {target} not found"));
     }
   }
 }
+
+#[cfg_attr(feature = "tracing", tracing::instrument(name = "plugin::hooks::initialize", skip(plugin), fields(name = plugin.name())))]
+fn initialize<R: Runtime>(
+  plugin: &mut Box<dyn Plugin<R>>,
+  app: &AppHandle<R>,
+  config: &PluginConfig,
+) -> crate::Result<()> {
+  plugin
+    .initialize(
+      app,
+      config.0.get(plugin.name()).cloned().unwrap_or_default(),
+    )
+    .map_err(|e| crate::Error::PluginInitialization(plugin.name().to_string(), e.to_string()))
+}

+ 136 - 40
core/tauri/src/updater/core.rs

@@ -19,6 +19,8 @@ use semver::Version;
 use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize};
 use tauri_utils::{platform::current_exe, Env};
 use time::OffsetDateTime;
+#[cfg(feature = "tracing")]
+use tracing::Instrument;
 use url::Url;
 
 #[cfg(desktop)]
@@ -312,6 +314,10 @@ impl<R: Runtime> UpdateBuilder<R> {
     Ok(self)
   }
 
+  #[cfg_attr(
+    feature = "tracing",
+    tracing::instrument("updater::check", skip_all, fields(arch, target), ret, err)
+  )]
   pub async fn build(mut self) -> Result<Update<R>> {
     let mut remote_release: Option<RemoteRelease> = None;
 
@@ -335,6 +341,12 @@ impl<R: Runtime> UpdateBuilder<R> {
       (target.to_string(), format!("{target}-{arch}"))
     };
 
+    #[cfg(feature = "tracing")]
+    {
+      tracing::Span::current().record("arch", arch);
+      tracing::Span::current().record("target", &target);
+    }
+
     // Get the extract_path from the provided executable_path
     let extract_path = extract_path_from_executable(&self.app.state::<Env>(), &executable_path);
 
@@ -370,38 +382,75 @@ impl<R: Runtime> UpdateBuilder<R> {
         .replace("{{target}}", &target)
         .replace("{{arch}}", arch);
 
-      let mut request = HttpRequestBuilder::new("GET", &fixed_link)?.headers(headers.clone());
-      if let Some(timeout) = self.timeout {
-        request = request.timeout(timeout);
-      }
-      let resp = ClientBuilder::new().build()?.send(request).await;
-
-      // If we got a success, we stop the loop
-      // and we set our remote_release variable
-      if let Ok(res) = resp {
-        let status = res.status();
-        // got status code 2XX
-        if status.is_success() {
-          // if we got 204
-          if status == StatusCode::NO_CONTENT {
-            // return with `UpToDate` error
-            // we should catch on the client
-            return Err(Error::UpToDate);
-          };
-          let res = res.read().await?;
-          // Convert the remote result to our local struct
-          let built_release = serde_json::from_value(res.data).map_err(Into::into);
-          // make sure all went well and the remote data is compatible
-          // with what we need locally
-          match built_release {
-            Ok(release) => {
-              last_error = None;
-              remote_release = Some(release);
-              break;
+      let task = async {
+        #[cfg(feature = "tracing")]
+        tracing::debug!("checking if there is an update via {}", url);
+
+        let mut request = HttpRequestBuilder::new("GET", &fixed_link)?.headers(headers.clone());
+        if let Some(timeout) = self.timeout {
+          request = request.timeout(timeout);
+        }
+        let resp = ClientBuilder::new().build()?.send(request).await;
+
+        // If we got a success, we stop the loop
+        // and we set our remote_release variable
+        if let Ok(res) = resp {
+          let status = res.status();
+          // got status code 2XX
+          if status.is_success() {
+            // if we got 204
+            if status == StatusCode::NO_CONTENT {
+              #[cfg(feature = "tracing")]
+              tracing::event!(tracing::Level::DEBUG, kind = "result", data = "no content");
+              // return with `UpToDate` error
+              // we should catch on the client
+              return Err(Error::UpToDate);
+            };
+            let res = res.read().await?;
+
+            // Convert the remote result to our local struct
+            let built_release: Result<RemoteRelease> =
+              serde_json::from_value(res.data).map_err(Into::into);
+
+            // make sure all went well and the remote data is compatible
+            // with what we need locally
+            match built_release {
+              Ok(release) => {
+                #[cfg(feature = "tracing")]
+                tracing::event!(
+                  tracing::Level::DEBUG,
+                  kind = "result",
+                  data = tracing::field::debug(&release)
+                );
+                last_error = None;
+                return Ok(Some(release));
+              }
+              Err(err) => {
+                #[cfg(feature = "tracing")]
+                tracing::event!(
+                  tracing::Level::ERROR,
+                  kind = "error",
+                  error = err.to_string()
+                );
+                last_error = Some(err)
+              }
             }
-            Err(err) => last_error = Some(err),
-          }
-        } // if status code is not 2XX we keep loopin' our urls
+          } // if status code is not 2XX we keep loopin' our urls
+        }
+
+        Ok(None)
+      };
+
+      #[cfg(feature = "tracing")]
+      let found_release = {
+        let span = tracing::info_span!("updater::check::fetch", url = &fixed_link,);
+        task.instrument(span).await?
+      };
+      #[cfg(not(feature = "tracing"))]
+      let found_release = task.await?;
+      if let Some(release) = found_release {
+        remote_release.replace(release);
+        break;
       }
     }
 
@@ -447,7 +496,6 @@ pub(crate) fn builder<R: Runtime>(app: AppHandle<R>) -> UpdateBuilder<R> {
   UpdateBuilder::new(app)
 }
 
-#[derive(Debug)]
 pub(crate) struct Update<R: Runtime> {
   /// Application handle.
   pub app: AppHandle<R>,
@@ -480,6 +528,29 @@ pub(crate) struct Update<R: Runtime> {
   headers: HeaderMap,
 }
 
+impl<R: Runtime> fmt::Debug for Update<R> {
+  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+    let mut s = f.debug_struct("Update");
+
+    s.field("current_version", &self.current_version)
+      .field("version", &self.version)
+      .field("date", &self.date)
+      .field("should_update", &self.should_update)
+      .field("body", &self.body)
+      .field("target", &self.target)
+      .field("extract_path", &self.extract_path)
+      .field("download_url", &self.download_url)
+      .field("signature", &self.signature)
+      .field("timeout", &self.timeout)
+      .field("headers", &self.headers);
+
+    #[cfg(target_os = "windows")]
+    s.field("with_elevated_task", &self.with_elevated_task);
+
+    s.finish()
+  }
+}
+
 impl<R: Runtime> Clone for Update<R> {
   fn clone(&self) -> Self {
     Self {
@@ -527,6 +598,7 @@ impl<R: Runtime> Update<R> {
 
   // Download and install our update
   // @todo(lemarier): Split into download and install (two step) but need to be thread safe
+  #[cfg_attr(feature = "tracing", tracing::instrument("updater::download_and_install", skip_all, fields(url = %self.download_url), ret, err))]
   pub async fn download_and_install<C: Fn(usize, Option<u64>), D: FnOnce()>(
     &self,
     pub_key: String,
@@ -540,6 +612,10 @@ impl<R: Runtime> Update<R> {
     // anything with it yet
     #[cfg(target_os = "linux")]
     if self.app.state::<Env>().appimage.is_none() {
+      #[cfg(feature = "tracing")]
+      tracing::error!(
+        "app is not a supported Linux package. Currently only AppImages are supported"
+      );
       return Err(Error::UnsupportedLinuxPackage);
     }
 
@@ -561,10 +637,14 @@ impl<R: Runtime> Update<R> {
       req = req.timeout(timeout);
     }
 
+    #[cfg(feature = "tracing")]
+    tracing::info!("Downloading update");
     let response = client.send(req).await?;
 
     // make sure it's success
     if !response.status().is_success() {
+      #[cfg(feature = "tracing")]
+      tracing::error!("Failed to download update");
       return Err(Error::Network(format!(
         "Download request failed with status: {}",
         response.status()
@@ -577,17 +657,31 @@ impl<R: Runtime> Update<R> {
       .and_then(|value| value.to_str().ok())
       .and_then(|value| value.parse().ok());
 
-    let mut buffer = Vec::new();
-    {
+    let buffer = {
       use futures_util::StreamExt;
       let mut stream = response.bytes_stream();
-      while let Some(chunk) = stream.next().await {
-        let chunk = chunk?;
-        let bytes = chunk.as_ref().to_vec();
-        on_chunk(bytes.len(), content_length);
-        buffer.extend(bytes);
+
+      let task = async move {
+        let mut buffer = Vec::new();
+        while let Some(chunk) = stream.next().await {
+          let chunk = chunk?;
+          let bytes = chunk.as_ref().to_vec();
+          on_chunk(bytes.len(), content_length);
+          buffer.extend(bytes);
+        }
+        Result::Ok(buffer)
+      };
+
+      #[cfg(feature = "tracing")]
+      {
+        let span = tracing::info_span!("updater::download_and_install::stream");
+        task.instrument(span).await
       }
-    }
+      #[cfg(not(feature = "tracing"))]
+      {
+        task.await
+      }
+    }?;
 
     on_download_finish();
 
@@ -601,6 +695,8 @@ impl<R: Runtime> Update<R> {
     // TODO: implement updater in mobile
     #[cfg(desktop)]
     {
+      #[cfg(feature = "tracing")]
+      tracing::info_span!("updater::download_and_install::install");
       // we copy the files depending of the operating system
       // we run the setup, appimage re-install or overwrite the
       // macos .app

+ 29 - 0
core/tauri/src/window.rs

@@ -68,6 +68,8 @@ impl WindowEmitArgs {
     source_window_label: Option<&str>,
     payload: S,
   ) -> crate::Result<Self> {
+    #[cfg(feature = "tracing")]
+    let _span = tracing::debug_span!("window::emit::serialize").entered();
     Ok(WindowEmitArgs {
       event: serde_json::to_string(event)?,
       source_window_label: serde_json::to_string(&source_window_label)?,
@@ -321,6 +323,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
   }
 
   /// Creates a new webview window.
+  #[cfg_attr(feature = "tracing", tracing::instrument(name = "window::create"))]
   pub fn build(mut self) -> crate::Result<Window<R>> {
     let mut pending = PendingWindow::new(
       self.window_builder.clone(),
@@ -776,6 +779,10 @@ impl<R: Runtime> PartialEq for Window<R> {
 }
 
 impl<R: Runtime> Manager<R> for Window<R> {
+  #[cfg_attr(
+    feature = "tracing",
+    tracing::instrument("window::emit::to", skip(self, payload))
+  )]
   fn emit_to<S: Serialize + Clone>(
     &self,
     label: &str,
@@ -787,12 +794,17 @@ impl<R: Runtime> Manager<R> for Window<R> {
       .emit_filter(event, Some(self.label()), payload, |w| label == w.label())
   }
 
+  #[cfg_attr(
+    feature = "tracing",
+    tracing::instrument("window::emit::all", skip(self, payload))
+  )]
   fn emit_all<S: Serialize + Clone>(&self, event: &str, payload: S) -> crate::Result<()> {
     self
       .manager()
       .emit_filter(event, Some(self.label()), payload, |_| true)
   }
 }
+
 impl<R: Runtime> ManagerBase<R> for Window<R> {
   fn manager(&self) -> &WindowManager<R> {
     &self.manager
@@ -1790,6 +1802,15 @@ impl<R: Runtime> Window<R> {
     self.emit(event, payload)
   }
 
+  #[cfg_attr(feature = "tracing", tracing::instrument(
+    "window::emit::eval",
+    skip(emit_args),
+    fields(
+      event = emit_args.event,
+      source_window = emit_args.source_window_label,
+      payload = emit_args.payload
+    ))
+  )]
   pub(crate) fn emit_internal(&self, emit_args: &WindowEmitArgs) -> crate::Result<()> {
     self.eval(&format!(
       "(function () {{ const fn = window['{}']; fn && fn({{event: {}, windowLabel: {}, payload: {}}}) }})()",
@@ -1816,6 +1837,10 @@ impl<R: Runtime> Window<R> {
   ///   }
   /// }
   /// ```
+  #[cfg_attr(
+    feature = "tracing",
+    tracing::instrument("window::emit", skip(self, payload))
+  )]
   pub fn emit<S: Serialize + Clone>(&self, event: &str, payload: S) -> crate::Result<()> {
     self
       .manager
@@ -1908,6 +1933,10 @@ impl<R: Runtime> Window<R> {
   ///   }
   /// }
   /// ```
+  #[cfg_attr(
+    feature = "tracing",
+    tracing::instrument("window::trigger", skip(self))
+  )]
   pub fn trigger(&self, event: &str, data: Option<String>) {
     let label = self.window.label.clone();
     self.manager.trigger(event, Some(label), data)

+ 112 - 20
examples/api/src-tauri/Cargo.lock

@@ -712,12 +712,12 @@ dependencies = [
 
 [[package]]
 name = "ctor"
-version = "0.1.26"
+version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
+checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583"
 dependencies = [
  "quote",
- "syn 1.0.109",
+ "syn 2.0.18",
 ]
 
 [[package]]
@@ -1724,9 +1724,9 @@ dependencies = [
 
 [[package]]
 name = "infer"
-version = "0.12.0"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a898e4b7951673fce96614ce5751d13c40fc5674bc2d759288e46c3ab62598b3"
+checksum = "f551f8c3a39f68f986517db0d1759de85881894fdc7db798bd2a9df9cb04b7fc"
 dependencies = [
  "cfb",
 ]
@@ -1846,9 +1846,9 @@ dependencies = [
 
 [[package]]
 name = "json-patch"
-version = "1.0.0"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f54898088ccb91df1b492cc80029a6fdf1c48ca0db7c6822a8babad69c94658"
+checksum = "55ff1e1486799e3f64129f8ccad108b38290df9cd7015cd31bed17239f0789d6"
 dependencies = [
  "serde",
  "serde_json",
@@ -2514,9 +2514,17 @@ version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
 dependencies = [
- "phf_macros 0.10.0",
  "phf_shared 0.10.0",
- "proc-macro-hack",
+]
+
+[[package]]
+name = "phf"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
+dependencies = [
+ "phf_macros 0.11.2",
+ "phf_shared 0.11.2",
 ]
 
 [[package]]
@@ -2559,6 +2567,16 @@ dependencies = [
  "rand 0.8.5",
 ]
 
+[[package]]
+name = "phf_generator"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
+dependencies = [
+ "phf_shared 0.11.2",
+ "rand 0.8.5",
+]
+
 [[package]]
 name = "phf_macros"
 version = "0.8.0"
@@ -2575,16 +2593,15 @@ dependencies = [
 
 [[package]]
 name = "phf_macros"
-version = "0.10.0"
+version = "0.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0"
+checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
 dependencies = [
- "phf_generator 0.10.0",
- "phf_shared 0.10.0",
- "proc-macro-hack",
+ "phf_generator 0.11.2",
+ "phf_shared 0.11.2",
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn 2.0.18",
 ]
 
 [[package]]
@@ -2605,6 +2622,15 @@ dependencies = [
  "siphasher",
 ]
 
+[[package]]
+name = "phf_shared"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
+dependencies = [
+ "siphasher",
+]
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.9"
@@ -3693,12 +3719,12 @@ dependencies = [
  "glob",
  "heck 0.4.1",
  "html5ever 0.26.0",
- "infer 0.12.0",
+ "infer 0.13.0",
  "json-patch",
  "kuchikiki",
  "log",
  "memchr",
- "phf 0.10.1",
+ "phf 0.11.2",
  "proc-macro2",
  "quote",
  "semver",
@@ -3709,7 +3735,7 @@ dependencies = [
  "thiserror",
  "url",
  "walkdir",
- "windows 0.39.0",
+ "windows-version",
 ]
 
 [[package]]
@@ -4557,12 +4583,36 @@ dependencies = [
  "windows_x86_64_msvc 0.48.0",
 ]
 
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
+]
+
 [[package]]
 name = "windows-tokens"
 version = "0.39.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597"
 
+[[package]]
+name = "windows-version"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75aa004c988e080ad34aff5739c39d0312f4684699d6d71fc8a198d057b8b9b4"
+dependencies = [
+ "windows-targets 0.52.0",
+]
+
 [[package]]
 name = "windows_aarch64_gnullvm"
 version = "0.42.2"
@@ -4575,6 +4625,12 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
 
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.37.0"
@@ -4599,6 +4655,12 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
 
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
 [[package]]
 name = "windows_i686_gnu"
 version = "0.37.0"
@@ -4623,6 +4685,12 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
 
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
 [[package]]
 name = "windows_i686_msvc"
 version = "0.37.0"
@@ -4647,6 +4715,12 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
 
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.37.0"
@@ -4671,6 +4745,12 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
 
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
 [[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.42.2"
@@ -4683,6 +4763,12 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
 
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.37.0"
@@ -4707,6 +4793,12 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
 
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+
 [[package]]
 name = "winnow"
 version = "0.4.7"
@@ -4737,9 +4829,9 @@ dependencies = [
 
 [[package]]
 name = "wry"
-version = "0.24.4"
+version = "0.24.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88ef04bdad49eba2e01f06e53688c8413bd6a87b0bc14b72284465cf96e3578e"
+checksum = "64a70547e8f9d85da0f5af609143f7bde3ac7457a6e1073104d9b73d6c5ac744"
 dependencies = [
  "base64 0.13.1",
  "block",