From 466f908d15542e6eb908cf695f2d978aca2e29c5 Mon Sep 17 00:00:00 2001 From: Jonas Kruckenberg <118265418+CrabNejonas@users.noreply.github.com> Date: Mon, 20 Nov 2023 16:16:15 +0100 Subject: [PATCH] feat: add instrumentation builder --- devtools/src/aggregator.rs | 11 +-- devtools/src/builder.rs | 142 +++++++++++++++++++++++++++++++++++ devtools/src/lib.rs | 90 ++-------------------- devtools/src/server.rs | 4 +- devtools/src/tauri_plugin.rs | 11 ++- 5 files changed, 160 insertions(+), 98 deletions(-) create mode 100644 devtools/src/builder.rs diff --git a/devtools/src/aggregator.rs b/devtools/src/aggregator.rs index d6fab331..1ebe2cc7 100644 --- a/devtools/src/aggregator.rs +++ b/devtools/src/aggregator.rs @@ -12,9 +12,6 @@ use tauri_devtools_wire_format::spans::SpanEvent; use tauri_devtools_wire_format::{instrument, logs, spans, NewMetadata}; use tokio::sync::mpsc; -/// How often to send updates to all connected clients -const BROADCAST_INTERVAL: Duration = Duration::from_millis(200); // TODO find good value for this - /// The event aggregator /// /// This is the heart of the instrumentation, it receives events from the @@ -77,8 +74,8 @@ impl Aggregator { } } - pub async fn run(mut self) { - let mut interval = tokio::time::interval(BROADCAST_INTERVAL); + pub async fn run(mut self, publish_interval: Duration) { + let mut interval = tokio::time::interval(publish_interval); loop { let should_publish = tokio::select! { @@ -364,7 +361,7 @@ mod test { .unwrap(); drop(cmd_tx); - mf.run().await; // run the aggregators event loop to completion + mf.run(Duration::from_millis(10)).await; // run the aggregators event loop to completion let mut out = Vec::new(); while let Some(Ok(update)) = client_rx.recv().await { @@ -387,7 +384,7 @@ mod test { .unwrap(); drop(cmd_tx); // drop the cmd_tx connection here, this will stop the aggregator - let (maybe_update, _) = futures::join!(client_rx.recv(), mf.run()); + let (maybe_update, _) = futures::join!(client_rx.recv(), mf.run(Duration::from_millis(10))); let update = maybe_update.unwrap().unwrap(); assert_eq!(update.logs_update.unwrap().log_events.len(), 0); assert_eq!(update.spans_update.unwrap().span_events.len(), 0); diff --git a/devtools/src/builder.rs b/devtools/src/builder.rs new file mode 100644 index 00000000..ccc4a5ab --- /dev/null +++ b/devtools/src/builder.rs @@ -0,0 +1,142 @@ +use crate::aggregator::Aggregator; +use crate::layer::Layer; +use crate::{tauri_plugin, Shared}; +use colored::Colorize; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::sync::Arc; +use std::time::Duration; +use tauri::Runtime; +use tokio::sync::mpsc; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::Layer as _; + +/// URL of the web-based devtool +/// The server host is added automatically eg: `127.0.0.1:56609`. +const DEVTOOL_URL: &str = "http://localhost:5173/dash/"; + +pub struct Builder { + host: IpAddr, + port: u16, + publish_interval: Duration, +} + +impl Default for Builder { + fn default() -> Self { + Self { + host: IpAddr::V4(Ipv4Addr::LOCALHOST), + port: 3000, + publish_interval: Duration::from_millis(200), + } + } +} + +impl Builder { + pub fn host(&mut self, host: IpAddr) -> &mut Self { + self.host = host; + self + } + + pub fn port(&mut self, port: u16) -> &mut Self { + self.port = port; + self + } + + pub fn publish_interval(&mut self, interval: Duration) -> &mut Self { + self.publish_interval = interval; + self + } + + /// Initializes the global tracing subscriber. + /// + /// This should be called as early in the execution of the app as possible. + /// Any events that occur before initialization will be ignored. + /// + /// This function returns a [`tauri::plugin::TauriPlugin`] that needs to be added to the + /// Tauri app in order to properly instrument it. + /// + /// # Example + /// + /// ```ignore + /// fn main() { + /// let devtools = tauri_devtools::Builder::default().init(); + /// + /// tauri::Builder::default() + /// .plugin(devtools) + /// .run(tauri::generate_context!()) + /// .expect("error while running tauri application"); + /// } + /// ``` + /// + /// # Panics + /// + /// This function will panic if it is called more than once, or if another library has already initialized a global tracing subscriber. + #[must_use = "This function returns a TauriPlugin that needs to be added to the Tauri app in order to properly instrument it."] + pub fn init(self) -> tauri::plugin::TauriPlugin { + self.try_init().unwrap() + } + + /// Initializes the global tracing subscriber. + /// + /// This should be called as early in the execution of the app as possible. + /// Any events that occur before initialization will be ignored. + /// + /// This function returns a [`tauri::plugin::TauriPlugin`] that needs to be added to the + /// Tauri app in order to properly instrument it. + /// + /// # Example + /// + /// ```ignore + /// fn main() { + /// let devtools = tauri_devtools::Builder::default().init(); + /// + /// tauri::Builder::default() + /// .plugin(devtools) + /// .run(tauri::generate_context!("../examples/tauri/tauri.conf.json")) + /// .expect("error while running tauri application"); + /// } + /// ``` + /// + /// # Errors + /// + /// This function will fail if it is called more than once, or if another library has already initialized a global tracing subscriber. + #[must_use = "This function returns a TauriPlugin that needs to be added to the Tauri app in order to properly instrument it."] + pub fn try_init(self) -> crate::Result> { + // set up data channels & shared data + let shared = Arc::new(Shared::default()); + let (event_tx, event_rx) = mpsc::channel(512); + let (cmd_tx, cmd_rx) = mpsc::channel(256); + + // set up components + let layer = Layer::new(shared.clone(), event_tx); + let aggregator = Aggregator::new(shared, event_rx, cmd_rx); + + // initialize early so we don't miss any spans + tracing_subscriber::registry() + .with(layer.with_filter(tracing_subscriber::filter::LevelFilter::TRACE)) + .try_init()?; + + let addr = SocketAddr::new(self.host, self.port); + + print_link(&addr); + + let plugin = tauri_plugin::init(addr, self.publish_interval, aggregator, cmd_tx); + Ok(plugin) + } +} + +// This is pretty ugly code I know, but it looks nice in the terminal soo ¯\_(ツ)_/¯ +fn print_link(addr: &SocketAddr) { + let url = format!("{DEVTOOL_URL}{}/{}", addr.ip(), addr.port()); + println!( + r#" + {} {}{} + {} Local: {} +"#, + "Tauri Devtools".bright_purple(), + "v".purple(), + env!("CARGO_PKG_VERSION").purple(), + "→".bright_purple(), + url.underline().blue() + ); +} diff --git a/devtools/src/lib.rs b/devtools/src/lib.rs index e31ac6db..cb851337 100644 --- a/devtools/src/lib.rs +++ b/devtools/src/lib.rs @@ -27,123 +27,45 @@ //! ``` mod aggregator; +mod builder; mod error; mod layer; mod server; mod tauri_plugin; mod visitors; -use crate::aggregator::Aggregator; -use crate::layer::Layer; -use colored::Colorize; +pub use builder::Builder; pub use error::Error; -use server::DEFAULT_ADDRESS; use std::sync::atomic::AtomicUsize; -use std::sync::Arc; use std::time::Instant; use tauri::Runtime; use tauri_devtools_wire_format::{instrument, Field}; use tokio::sync::mpsc; -use tracing_subscriber::layer::SubscriberExt; -use tracing_subscriber::util::SubscriberInitExt; -use tracing_subscriber::Layer as _; pub(crate) type Result = std::result::Result; -/// URL of the web-based devtool -/// The server host is added automatically eg: `127.0.0.1:56609`. -const DEVTOOL_URL: &str = "http://localhost:5173/dash/"; -// const DEVTOOL_URL: &str = "https://crabnebula.dev/debug/#"; - /// Initializes the global tracing subscriber. /// -/// This should be called as early in the execution of the app as possible. -/// Any events that occur before initialization will be ignored. -/// -/// This function returns a [`tauri::plugin::TauriPlugin`] that needs to be added to the -/// Tauri app in order to properly instrument it. -/// -/// # Example -/// -/// ```ignore -/// fn main() { -/// let devtools = tauri_devtools::init(); -/// -/// tauri::Builder::default() -/// .plugin(devtools) -/// .run(tauri::generate_context!()) -/// .expect("error while running tauri application"); -/// } -/// ``` +/// See [`Builder::init`] for details and documentation. /// /// # Panics /// /// This function will panic if it is called more than once, or if another library has already initialized a global tracing subscriber. #[must_use = "This function returns a TauriPlugin that needs to be added to the Tauri app in order to properly instrument it."] pub fn init() -> tauri::plugin::TauriPlugin { - try_init().unwrap() + Builder::default().init() } /// Initializes the global tracing subscriber. /// -/// This should be called as early in the execution of the app as possible. -/// Any events that occur before initialization will be ignored. -/// -/// This function returns a [`tauri::plugin::TauriPlugin`] that needs to be added to the -/// Tauri app in order to properly instrument it. -/// -/// # Example -/// -/// ```ignore -/// fn main() { -/// let devtools = tauri_devtools::init(); -/// -/// tauri::Builder::default() -/// .plugin(devtools) -/// .run(tauri::generate_context!("../examples/tauri/tauri.conf.json")) -/// .expect("error while running tauri application"); -/// } -/// ``` +/// See [`Builder::try_init`] for details and documentation. /// /// # Errors /// /// This function will fail if it is called more than once, or if another library has already initialized a global tracing subscriber. #[must_use = "This function returns a TauriPlugin that needs to be added to the Tauri app in order to properly instrument it."] pub fn try_init() -> Result> { - // set up data channels & shared data - let shared = Arc::new(Shared::default()); - let (event_tx, event_rx) = mpsc::channel(512); - let (cmd_tx, cmd_rx) = mpsc::channel(256); - - // set up components - let layer = Layer::new(shared.clone(), event_tx); - let aggregator = Aggregator::new(shared, event_rx, cmd_rx); - - // initialize early so we don't miss any spans - tracing_subscriber::registry() - .with(layer.with_filter(tracing_subscriber::filter::LevelFilter::TRACE)) - .try_init()?; - - // This is pretty ugly code I know, but it looks nice in the terminal soo ¯\_(ツ)_/¯ - let url = format!( - "{DEVTOOL_URL}{}/{}", - DEFAULT_ADDRESS.ip(), - DEFAULT_ADDRESS.port() - ); - println!( - r#" - {} {}{} - {} Local: {} -"#, - "Tauri Devtools".bright_purple(), - "v".purple(), - env!("CARGO_PKG_VERSION").purple(), - "→".bright_purple(), - url.underline().blue() - ); - - let plugin = tauri_plugin::init(aggregator, cmd_tx); - Ok(plugin) + Builder::default().try_init() } /// Shared data between the [`Layer`] and the [`Aggregator`] diff --git a/devtools/src/server.rs b/devtools/src/server.rs index 1f71b67e..2db8d845 100644 --- a/devtools/src/server.rs +++ b/devtools/src/server.rs @@ -2,7 +2,7 @@ use crate::{Command, Watcher}; use async_stream::try_stream; use bytes::BytesMut; use futures::{FutureExt, Stream, TryStreamExt}; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::net::SocketAddr; use std::path::PathBuf; use std::sync::Arc; use tauri::{AppHandle, Runtime}; @@ -35,8 +35,6 @@ use tower_http::cors::{AllowHeaders, CorsLayer}; /// and may be disconnected. const DEFAULT_CLIENT_BUFFER_CAPACITY: usize = 1024 * 4; -pub const DEFAULT_ADDRESS: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 3000); - /// The `gRPC` server that exposes the instrumenting API /// This is made up of 3 services: /// - [`InstrumentService`]: Instrumentation related functionality, such as logs, spans etc. diff --git a/devtools/src/tauri_plugin.rs b/devtools/src/tauri_plugin.rs index 6f023df8..982f9199 100644 --- a/devtools/src/tauri_plugin.rs +++ b/devtools/src/tauri_plugin.rs @@ -1,15 +1,18 @@ use crate::aggregator::Aggregator; -use crate::server::{Server, DEFAULT_ADDRESS}; +use crate::server::Server; use crate::Command; +use std::net::SocketAddr; use std::sync::Arc; use std::thread; -use std::time::Instant; use std::time::SystemTime; +use std::time::{Duration, Instant}; use tauri::{RunEvent, Runtime}; use tauri_devtools_wire_format::tauri::Metrics; use tokio::sync::{mpsc, RwLock}; pub(crate) fn init( + addr: SocketAddr, + publish_interval: Duration, aggregator: Aggregator, cmd_tx: mpsc::Sender, ) -> tauri::plugin::TauriPlugin { @@ -40,8 +43,8 @@ pub(crate) fn init( .unwrap(); rt.block_on(async move { - let aggregator = tokio::spawn(aggregator.run()); - server.run(DEFAULT_ADDRESS).await.unwrap(); + let aggregator = tokio::spawn(aggregator.run(publish_interval)); + server.run(addr).await.unwrap(); aggregator.abort(); }); });