From 09a8b8e6754ed8b469302dc0a0541907a90a0c41 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 2 May 2022 15:17:43 +0200 Subject: [PATCH] Capture crash reports and upload them the next time Zed launches --- Cargo.lock | 13 +--- crates/collab/src/api.rs | 14 ++++ crates/gpui/src/platform.rs | 7 ++ crates/zed/Cargo.toml | 3 +- crates/zed/src/main.rs | 137 ++++++++++++++++++++++++++++++++---- 5 files changed, 149 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1966cf4d95..23a87887a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2499,16 +2499,6 @@ dependencies = [ "value-bag", ] -[[package]] -name = "log-panics" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae0136257df209261daa18d6c16394757c63e032e27aafd8b07788b051082bef" -dependencies = [ - "backtrace", - "log", -] - [[package]] name = "loom" version = "0.4.0" @@ -5859,8 +5849,10 @@ dependencies = [ "async-recursion", "async-trait", "auto_update", + "backtrace", "breadcrumbs", "chat_panel", + "chrono", "cli", "client", "clock", @@ -5889,7 +5881,6 @@ dependencies = [ "lazy_static", "libc", "log", - "log-panics", "lsp", "num_cpus", "outline", diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index 818e92316c..5249e01ab0 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -16,6 +16,7 @@ use axum::{ use serde::{Deserialize, Serialize}; use std::sync::Arc; use tower::ServiceBuilder; +use tracing::instrument; pub fn routes(state: Arc) -> Router { Router::new() @@ -25,6 +26,7 @@ pub fn routes(state: Arc) -> Router { put(update_user).delete(destroy_user).get(get_user), ) .route("/users/:id/access_tokens", post(create_access_token)) + .route("/crash", post(trace_crash)) .layer( ServiceBuilder::new() .layer(Extension(state)) @@ -129,6 +131,18 @@ async fn get_user( Ok(Json(user)) } +#[derive(Debug, Deserialize)] +struct Crash { + version: String, + text: String, +} + +#[instrument] +async fn trace_crash(crash: Json) -> Result<()> { + tracing::error!(version = %crash.version, text = %crash.text, "crash report"); + Ok(()) +} + #[derive(Deserialize)] struct CreateAccessTokenQueryParams { public_key: String, diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 5e4e639bc8..d851e195be 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -24,6 +24,7 @@ use postage::oneshot; use serde::Deserialize; use std::{ any::Any, + fmt::{self, Display}, path::{Path, PathBuf}, rc::Rc, str::FromStr, @@ -167,6 +168,12 @@ impl FromStr for AppVersion { } } +impl Display for AppVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}.{}.{}", self.major, self.minor, self.patch) + } +} + #[derive(Copy, Clone, Debug)] pub enum RasterizationOptions { Alpha, diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index d82acbd13c..3a818b2de5 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -53,6 +53,8 @@ anyhow = "1.0.38" async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] } async-recursion = "0.3" async-trait = "0.1" +backtrace = "0.3" +chrono = "0.4" ctor = "0.1.20" dirs = "3.0" easy-parallel = "3.1.0" @@ -66,7 +68,6 @@ isahc = "1.7" lazy_static = "1.4.0" libc = "0.2" log = { version = "0.4.16", features = ["kv_unstable_serde"] } -log-panics = { version = "2.0", features = ["with-backtrace"] } num_cpus = "1.13.0" parking_lot = "0.11.1" postage = { version = "0.4.1", features = ["futures-traits"] } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index b730677c70..cc63d36f68 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -3,25 +3,41 @@ use anyhow::{anyhow, Context, Result}; use assets::Assets; +use auto_update::ZED_APP_VERSION; +use backtrace::Backtrace; use cli::{ ipc::{self, IpcSender}, CliRequest, CliResponse, IpcHandshake, }; -use client::{self, http, ChannelList, UserStore}; +use client::{ + self, + http::{self, HttpClient}, + ChannelList, UserStore, +}; use fs::OpenOptions; use futures::{ channel::{mpsc, oneshot}, FutureExt, SinkExt, StreamExt, }; -use gpui::{App, AssetSource, AsyncAppContext, Task}; +use gpui::{executor::Background, App, AssetSource, AsyncAppContext, Task}; +use isahc::{config::Configurable, AsyncBody, Request}; use log::LevelFilter; use parking_lot::Mutex; use project::Fs; +use serde_json::json; use settings::{self, KeymapFileContent, Settings, SettingsFileContent}; use smol::process::Command; -use std::{env, fs, path::PathBuf, sync::Arc, thread, time::Duration}; +use std::{ + env, + ffi::OsStr, + fs, panic, + path::{Path, PathBuf}, + sync::Arc, + thread, + time::Duration, +}; use theme::{ThemeRegistry, DEFAULT_THEME_NAME}; -use util::ResultExt; +use util::{ResultExt, TryFutureExt}; use workspace::{self, AppState, OpenNew, OpenPaths}; use zed::{ self, build_window_options, build_workspace, @@ -31,9 +47,16 @@ use zed::{ }; fn main() { - init_logger(); + let http = http::client(); + let logs_dir_path = dirs::home_dir() + .expect("could not find home dir") + .join("Library/Logs/Zed"); + fs::create_dir_all(&logs_dir_path).expect("could not create logs path"); + init_logger(&logs_dir_path); let mut app = gpui::App::new(Assets).unwrap(); + init_crash_handler(logs_dir_path, http.clone(), app.background()); + load_embedded_fonts(&app); let fs = Arc::new(RealFs); @@ -107,7 +130,6 @@ fn main() { }); app.run(move |cx| { - let http = http::client(); let client = client::Client::new(http.clone()); let mut languages = languages::build_language_registry(login_shell_env_loaded); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); @@ -219,16 +241,12 @@ fn main() { }); } -fn init_logger() { +fn init_logger(logs_dir_path: &Path) { if stdout_is_a_pty() { env_logger::init(); } else { let level = LevelFilter::Info; - let log_dir_path = dirs::home_dir() - .expect("could not locate home directory for logging") - .join("Library/Logs/"); - let log_file_path = log_dir_path.join("Zed.log"); - fs::create_dir_all(&log_dir_path).expect("could not create log directory"); + let log_file_path = logs_dir_path.join("Zed.log"); let log_file = OpenOptions::new() .create(true) .append(true) @@ -236,10 +254,103 @@ fn init_logger() { .expect("could not open logfile"); simplelog::WriteLogger::init(level, simplelog::Config::default(), log_file) .expect("could not initialize logger"); - log_panics::init(); } } +fn init_crash_handler( + logs_dir_path: PathBuf, + http: Arc, + background: Arc, +) { + background + .spawn({ + let logs_dir_path = logs_dir_path.clone(); + let app_version = ZED_APP_VERSION.map_or("dev".to_string(), |v| v.to_string()); + async move { + let crash_report_url = format!("{}/api/crash", &*client::ZED_SERVER_URL); + let mut children = smol::fs::read_dir(&logs_dir_path).await?; + while let Some(child) = children.next().await { + let child = child?; + let child_path = child.path(); + if child_path.extension() == Some(OsStr::new("crash")) { + let text = smol::fs::read_to_string(&child_path) + .await + .context("error reading crash file")?; + let body = serde_json::to_string(&json!({ + "text": text, + "version": app_version + })) + .unwrap(); + let request = Request::builder() + .uri(&crash_report_url) + .method(http::Method::POST) + .redirect_policy(isahc::config::RedirectPolicy::Follow) + .header("Content-Type", "application/json") + .body(AsyncBody::from(body))?; + let response = http.send(request).await.context("error sending crash")?; + if response.status().is_success() { + fs::remove_file(child_path) + .context("error removing crash after sending it successfully") + .log_err(); + } else { + log::error!("{:?}", response); + } + } + } + Ok::<_, anyhow::Error>(()) + } + .log_err() + }) + .detach(); + + let is_pty = stdout_is_a_pty(); + panic::set_hook(Box::new(move |info| { + let backtrace = Backtrace::new(); + + let thread = thread::current(); + let thread = thread.name().unwrap_or(""); + + let payload = match info.payload().downcast_ref::<&'static str>() { + Some(s) => *s, + None => match info.payload().downcast_ref::() { + Some(s) => &**s, + None => "Box", + }, + }; + + let message = match info.location() { + Some(location) => { + format!( + "thread '{}' panicked at '{}': {}:{}{:?}", + thread, + payload, + location.file(), + location.line(), + backtrace + ) + } + None => format!( + "thread '{}' panicked at '{}'{:?}", + thread, payload, backtrace + ), + }; + + let crash_filename = chrono::Utc::now().format("%Y-%m-%d-%H_%M_%S").to_string(); + fs::write( + logs_dir_path.join(format!("zed-{}.crash", crash_filename)), + &message, + ) + .context("error writing panic to disk") + .log_err(); + + if is_pty { + eprintln!("{}", message); + } else { + log::error!(target: "panic", "{}", message); + } + })); +} + async fn load_login_shell_environment() -> Result<()> { let marker = "ZED_LOGIN_SHELL_START"; let shell = env::var("SHELL").context(