1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
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 _;
/// The builder can be use to customize the instrumentation.
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 {
/// Specify which IP addresses the instrumentation server should listen on.
///
/// You can set this to [`Ipv4Addr::UNSPECIFIED`] to listen on all addresses, including LAN and public ones.
///
/// **default:** [`Ipv4Addr::LOCALHOST`]
pub fn host(&mut self, host: IpAddr) -> &mut Self {
self.host = host;
self
}
/// Specify the instrumentation server port.
///
/// Currently `devtools` **does not** pick a random free port if the configured one
/// is already taken, so you will need to configure a different one manually.
///
/// **default:** `3000`
pub fn port(&mut self, port: u16) -> &mut Self {
self.port = port;
self
}
/// The interval in which updates are sent to the connected UI.
///
/// You can tweak this setting to reduce the time between updates, when for example your app
/// is generating a lot of events, the buffer might fill up and cause some events to get lost.
///
/// **default:** `200ms`
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
///
/// Make sure to check out the `examples` sub folder for a fully working setup.
///
/// ```no_run
/// fn main() {
/// let devtools_plugin = devtools::Builder::default().init();
///
/// tauri::Builder::default()
/// .plugin(devtools_plugin)
/// // ... the rest of the tauri setup code
/// # .run(tauri::test::mock_context(tauri::test::noop_assets()))
/// # .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<R: Runtime>(self) -> tauri::plugin::TauriPlugin<R> {
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
///
/// Make sure to check out the `examples` sub folder for a fully working setup.
///
/// ```no_run
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let devtools_plugin = devtools::Builder::default().try_init()?;
///
/// tauri::Builder::default()
/// .plugin(devtools_plugin)
/// // ... the rest of the tauri setup code
/// # .run(tauri::test::mock_context(tauri::test::noop_assets()))
/// # .expect("error while running tauri application");
///
/// Ok(())
/// }
/// ```
///
/// # 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<R: Runtime>(self) -> crate::Result<tauri::plugin::TauriPlugin<R>> {
// 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 = if option_env!("__DEVTOOLS_LOCAL_DEVELOPMENT").is_some() {
"http://localhost:5173/dash/"
} else {
"https://devtools.crabnebula.dev/dash/"
};
let url = format!("{url}{}/{}", addr.ip(), addr.port());
println!(
r#"
{} {}{}
{} Local: {}
"#,
"Tauri Devtools".bright_purple(),
"v".purple(),
env!("CARGO_PKG_VERSION").purple(),
"→".bright_purple(),
url.underline().blue()
);
}