Skip to content

Commit

Permalink
feat: add instrumentation builder
Browse files Browse the repository at this point in the history
  • Loading branch information
CrabNejonas committed Nov 20, 2023
1 parent 627bcde commit 466f908
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 98 deletions.
11 changes: 4 additions & 7 deletions devtools/src/aggregator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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! {
Expand Down Expand Up @@ -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 {
Expand All @@ -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);
Expand Down
142 changes: 142 additions & 0 deletions devtools/src/builder.rs
Original file line number Diff line number Diff line change
@@ -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<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
///
/// ```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<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 = 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()
);
}
90 changes: 6 additions & 84 deletions devtools/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = std::result::Result<T, Error>;

/// 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<R: Runtime>() -> tauri::plugin::TauriPlugin<R> {
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<R: Runtime>() -> 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()?;

// 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`]
Expand Down
4 changes: 1 addition & 3 deletions devtools/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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.
Expand Down
11 changes: 7 additions & 4 deletions devtools/src/tauri_plugin.rs
Original file line number Diff line number Diff line change
@@ -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<R: Runtime>(
addr: SocketAddr,
publish_interval: Duration,
aggregator: Aggregator,
cmd_tx: mpsc::Sender<Command>,
) -> tauri::plugin::TauriPlugin<R> {
Expand Down Expand Up @@ -40,8 +43,8 @@ pub(crate) fn init<R: Runtime>(
.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();
});
});
Expand Down

0 comments on commit 466f908

Please sign in to comment.