add basic crash analytics and very light telemetry to dioxus-cli (#4224)
* wip * wip: basic telemetry * fix merge * keep errors for telemetry * fix upload when queued events are empty * add per session id and keep track of build timing * keep track of what commands are run * capture panics * keep track of panics * better path stripping * better tracing error telemetry * only sync every week and check settings before sending any telemetry * keep track of the args passed to commands * anonymize commands * more error tolerant * wip * wip * wip * merge cnflict * other merg conflict * migrate everything from telemetry to logging * update docs * wip... about to merge trace controller * lil bit more refactor to clean up tracer * more performant log writer * cleanup ci detecting * wire up to posthog * re-uploads work * yoink out the anon from each file * cleanup sig * lil bit cleaner * dont write empty files * wip log handler * fmt * cleanup logging of backtraces * add proper backtrace support * use stable reporter * adhere to posthog format * fix stacktraces * use anyhow for capturing backtraces * I think we're done? * cleanup logging * properly collect! * move out keycode handling * she works * typo * add extra fields * fix cargo doc --------- Co-authored-by: Evan Almloff <evanalmloff@gmail.com>
This commit is contained in:
1 parent
a610aaefc3
commit
e43bf05b69
30 files changed
+1748
-441
No files matched your search
@@ -39,7 +39,7 @@ pub(crate) use update::*;
|
||||
/// - I'd love to be able to configure the CLI while it's running so we can change settings on the fly.
|
||||
/// - I want us to be able to detect a `server_fn` in the project and then upgrade from a static server
|
||||
/// to a dynamic one on the fly.
|
||||
pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) -> Result<()> {
|
||||
pub(crate) async fn serve_all(args: ServeArgs, tracer: &TraceController) -> Result<()> {
|
||||
// Load the args into a plan, resolving all tooling, build dirs, arguments, decoding the multi-target, etc
|
||||
let exit_on_error = args.exit_on_error;
|
||||
let mut builder = AppServer::new(args).await?;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use crate::Result;
|
||||
use crate::{
|
||||
serve::{ansi_buffer::ansi_string_to_line, ServeUpdate, WebServer},
|
||||
BuildId, BuildStage, BuilderUpdate, BundleFormat, TraceContent, TraceMsg, TraceSrc,
|
||||
};
|
||||
use anyhow::{anyhow, bail, Context};
|
||||
use cargo_metadata::diagnostic::Diagnostic;
|
||||
use crossterm::{
|
||||
cursor::{Hide, Show},
|
||||
@@ -104,7 +106,7 @@ impl Output {
|
||||
|
||||
/// Call the startup functions that might mess with the terminal settings.
|
||||
/// This is meant to be paired with "shutdown" to restore the terminal to its original state.
|
||||
fn startup(&mut self) -> io::Result<()> {
|
||||
fn startup(&mut self) -> Result<()> {
|
||||
if self.interactive {
|
||||
// Check if writing the terminal is going to block infinitely.
|
||||
// If it does, we should disable interactive mode. This ensures we work with programs like `bg`
|
||||
@@ -138,7 +140,7 @@ impl Output {
|
||||
///
|
||||
/// This lets us check if writing to tty is going to block forever and then recover, allowing
|
||||
/// interopability with programs like `bg`.
|
||||
fn enable_raw_mode() -> io::Result<()> {
|
||||
fn enable_raw_mode() -> Result<()> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
@@ -152,7 +154,7 @@ impl Output {
|
||||
use std::io::IsTerminal;
|
||||
|
||||
if !stdout().is_terminal() {
|
||||
return io::Result::Err(io::Error::other("Not a terminal"));
|
||||
bail!("Cannot enable raw mode on a non-terminal output");
|
||||
}
|
||||
|
||||
enable_raw_mode()?;
|
||||
@@ -164,7 +166,7 @@ impl Output {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn remote_shutdown(interactive: bool) -> io::Result<()> {
|
||||
pub(crate) fn remote_shutdown(interactive: bool) -> Result<()> {
|
||||
if interactive && crossterm::terminal::is_raw_mode_enabled().unwrap_or(true) {
|
||||
stdout()
|
||||
.execute(Show)?
|
||||
@@ -213,23 +215,42 @@ impl Output {
|
||||
}
|
||||
|
||||
/// Handle an input event, returning `true` if the event should cause the program to restart.
|
||||
fn handle_input(&mut self, input: Event) -> io::Result<Option<ServeUpdate>> {
|
||||
// handle ctrlc
|
||||
if let Event::Key(key) = input {
|
||||
if let KeyCode::Char('c') = key.code {
|
||||
if key.modifiers.contains(KeyModifiers::CONTROL) {
|
||||
return Ok(Some(ServeUpdate::Exit { error: None }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_input(&mut self, input: Event) -> Result<Option<ServeUpdate>> {
|
||||
match input {
|
||||
Event::Key(key) if key.kind == KeyEventKind::Press => self.handle_keypress(key),
|
||||
_ => Ok(Some(ServeUpdate::Redraw)),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_keypress(&mut self, key: KeyEvent) -> io::Result<Option<ServeUpdate>> {
|
||||
fn handle_keypress(&mut self, key: KeyEvent) -> Result<Option<ServeUpdate>> {
|
||||
// Some dev helpers for testing panic propagation and error handling. Remove this eventually.
|
||||
if cfg!(debug_assertions) && std::env::var("DEBUG_PANICS").is_ok() {
|
||||
match key.code {
|
||||
KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
return Ok(Some(ServeUpdate::Exit { error: None }))
|
||||
}
|
||||
KeyCode::Char('Z') => panic!("z pressed so we panic -> {}", uuid::Uuid::new_v4()),
|
||||
KeyCode::Char('X') => bail!("x pressed so we bail -> {}", uuid::Uuid::new_v4()),
|
||||
KeyCode::Char('E') => {
|
||||
Err(anyhow!(
|
||||
"E pressed so we bail with context -> {}",
|
||||
uuid::Uuid::new_v4()
|
||||
))
|
||||
.context("With a message")
|
||||
.context("With a context")?;
|
||||
}
|
||||
KeyCode::Char('C') => {
|
||||
return Ok(Some(ServeUpdate::Exit {
|
||||
error: Some(anyhow!(
|
||||
"C pressed, so exiting with safe error -> {}",
|
||||
uuid::Uuid::new_v4()
|
||||
)),
|
||||
}));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match key.code {
|
||||
KeyCode::Char('r') => return Ok(Some(ServeUpdate::RequestRebuild)),
|
||||
KeyCode::Char('o') => return Ok(Some(ServeUpdate::OpenApp)),
|
||||
@@ -255,7 +276,6 @@ impl Output {
|
||||
id: BuildId::CLIENT,
|
||||
}));
|
||||
}
|
||||
|
||||
KeyCode::Char('c') => {
|
||||
stdout()
|
||||
.execute(Clear(ClearType::All))?
|
||||
@@ -394,7 +414,9 @@ impl Output {
|
||||
};
|
||||
|
||||
// First, dequeue any logs that have built up from event handling
|
||||
_ = self.drain_logs(term);
|
||||
while let Some(log) = self.pending_logs.pop_back() {
|
||||
_ = self.render_log(term, log);
|
||||
}
|
||||
|
||||
// Then, draw the frame, passing along all the state of the TUI so we can render it properly
|
||||
_ = term.draw(|frame| {
|
||||
@@ -844,16 +866,13 @@ impl Output {
|
||||
/// TODO(jon): we could look into implementing scroll regions ourselves, but I think insert_before will
|
||||
/// land in a reasonable amount of time.
|
||||
#[deny(clippy::manual_saturating_arithmetic)]
|
||||
fn drain_logs(
|
||||
fn render_log(
|
||||
&mut self,
|
||||
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
|
||||
) -> io::Result<()> {
|
||||
log: TraceMsg,
|
||||
) -> Result<()> {
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
let Some(log) = self.pending_logs.pop_back() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Only show debug logs if verbose is enabled
|
||||
if log.level == Level::DEBUG && !self.verbose {
|
||||
return Ok(());
|
||||
@@ -1029,3 +1048,33 @@ impl Output {
|
||||
lines
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Drop for Output {
|
||||
fn drop(&mut self) {
|
||||
if !self.interactive {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a handle to the terminal with a different lifetime so we can continue to call &self methods
|
||||
let owned_term = self.term.clone();
|
||||
let mut term = owned_term.borrow_mut();
|
||||
let Some(term) = term.as_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// First, dequeue any logs that have built up from event handling
|
||||
while let Some(log) = self.pending_logs.pop_back() {
|
||||
_ = self.render_log(term, log);
|
||||
}
|
||||
|
||||
// And then lets move the cursor to the bottom of the terminal
|
||||
let frame_rect = term.get_frame().area();
|
||||
_ = term.set_cursor_position(Position {
|
||||
x: 0,
|
||||
y: frame_rect.y + frame_rect.height,
|
||||
});
|
||||
|
||||
// Tear down the TUI if it's active at this point.
|
||||
_ = crate::serve::Output::remote_shutdown(self.interactive);
|
||||
}
|
||||
}
|
||||
@@ -143,15 +143,15 @@ pub(crate) fn proxy_to(
|
||||
|| uri.path().starts_with("/public/")
|
||||
|| uri.path().starts_with("/wasm/")
|
||||
{
|
||||
tracing::trace!(dx_src = ?TraceSrc::Dev, "[{}] {}", res.status().as_u16(), uri);
|
||||
tracing::trace!(dx_src = ?TraceSrc::App(crate::BundleFormat::Server), "[{}] {}", res.status().as_u16(), uri);
|
||||
} else {
|
||||
tracing::info!(dx_src = ?TraceSrc::Dev, "[{}] {}", res.status().as_u16(), uri);
|
||||
tracing::info!(dx_src = ?TraceSrc::App(crate::BundleFormat::Server), "[{}] {}", res.status().as_u16(), uri);
|
||||
}
|
||||
|
||||
Ok(res.into_response())
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!(dx_src = ?TraceSrc::Dev, "[{}] {}", err.status().as_u16(), uri);
|
||||
tracing::error!(dx_src = ?TraceSrc::App(crate::BundleFormat::Server), "[{}] {}", err.status().as_u16(), uri);
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ impl AppServer {
|
||||
|
||||
let open_browser = args
|
||||
.open
|
||||
.unwrap_or_else(|| workspace.settings.always_open_browser.unwrap_or(true))
|
||||
.unwrap_or_else(|| workspace.settings.always_open_browser.unwrap_or(false))
|
||||
&& interactive;
|
||||
|
||||
let wsl_file_poll_interval = args
|
||||
|
||||
Reference in new issue
Block a user