mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-19 10:29:35 +03:00
Capture crash reports and upload them the next time Zed launches
This commit is contained in:
parent
39c7b1fd51
commit
09a8b8e675
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -2499,16 +2499,6 @@ dependencies = [
|
|||||||
"value-bag",
|
"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]]
|
[[package]]
|
||||||
name = "loom"
|
name = "loom"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -5859,8 +5849,10 @@ dependencies = [
|
|||||||
"async-recursion",
|
"async-recursion",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"auto_update",
|
"auto_update",
|
||||||
|
"backtrace",
|
||||||
"breadcrumbs",
|
"breadcrumbs",
|
||||||
"chat_panel",
|
"chat_panel",
|
||||||
|
"chrono",
|
||||||
"cli",
|
"cli",
|
||||||
"client",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
@ -5889,7 +5881,6 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"log-panics",
|
|
||||||
"lsp",
|
"lsp",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"outline",
|
"outline",
|
||||||
|
@ -16,6 +16,7 @@ use axum::{
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tower::ServiceBuilder;
|
use tower::ServiceBuilder;
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
pub fn routes(state: Arc<AppState>) -> Router<Body> {
|
pub fn routes(state: Arc<AppState>) -> Router<Body> {
|
||||||
Router::new()
|
Router::new()
|
||||||
@ -25,6 +26,7 @@ pub fn routes(state: Arc<AppState>) -> Router<Body> {
|
|||||||
put(update_user).delete(destroy_user).get(get_user),
|
put(update_user).delete(destroy_user).get(get_user),
|
||||||
)
|
)
|
||||||
.route("/users/:id/access_tokens", post(create_access_token))
|
.route("/users/:id/access_tokens", post(create_access_token))
|
||||||
|
.route("/crash", post(trace_crash))
|
||||||
.layer(
|
.layer(
|
||||||
ServiceBuilder::new()
|
ServiceBuilder::new()
|
||||||
.layer(Extension(state))
|
.layer(Extension(state))
|
||||||
@ -129,6 +131,18 @@ async fn get_user(
|
|||||||
Ok(Json(user))
|
Ok(Json(user))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Crash {
|
||||||
|
version: String,
|
||||||
|
text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument]
|
||||||
|
async fn trace_crash(crash: Json<Crash>) -> Result<()> {
|
||||||
|
tracing::error!(version = %crash.version, text = %crash.text, "crash report");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct CreateAccessTokenQueryParams {
|
struct CreateAccessTokenQueryParams {
|
||||||
public_key: String,
|
public_key: String,
|
||||||
|
@ -24,6 +24,7 @@ use postage::oneshot;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
|
fmt::{self, Display},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
str::FromStr,
|
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)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum RasterizationOptions {
|
pub enum RasterizationOptions {
|
||||||
Alpha,
|
Alpha,
|
||||||
|
@ -53,6 +53,8 @@ anyhow = "1.0.38"
|
|||||||
async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
|
async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
|
||||||
async-recursion = "0.3"
|
async-recursion = "0.3"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
|
backtrace = "0.3"
|
||||||
|
chrono = "0.4"
|
||||||
ctor = "0.1.20"
|
ctor = "0.1.20"
|
||||||
dirs = "3.0"
|
dirs = "3.0"
|
||||||
easy-parallel = "3.1.0"
|
easy-parallel = "3.1.0"
|
||||||
@ -66,7 +68,6 @@ isahc = "1.7"
|
|||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
log-panics = { version = "2.0", features = ["with-backtrace"] }
|
|
||||||
num_cpus = "1.13.0"
|
num_cpus = "1.13.0"
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||||
|
@ -3,25 +3,41 @@
|
|||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use assets::Assets;
|
use assets::Assets;
|
||||||
|
use auto_update::ZED_APP_VERSION;
|
||||||
|
use backtrace::Backtrace;
|
||||||
use cli::{
|
use cli::{
|
||||||
ipc::{self, IpcSender},
|
ipc::{self, IpcSender},
|
||||||
CliRequest, CliResponse, IpcHandshake,
|
CliRequest, CliResponse, IpcHandshake,
|
||||||
};
|
};
|
||||||
use client::{self, http, ChannelList, UserStore};
|
use client::{
|
||||||
|
self,
|
||||||
|
http::{self, HttpClient},
|
||||||
|
ChannelList, UserStore,
|
||||||
|
};
|
||||||
use fs::OpenOptions;
|
use fs::OpenOptions;
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::{mpsc, oneshot},
|
channel::{mpsc, oneshot},
|
||||||
FutureExt, SinkExt, StreamExt,
|
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 log::LevelFilter;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::Fs;
|
use project::Fs;
|
||||||
|
use serde_json::json;
|
||||||
use settings::{self, KeymapFileContent, Settings, SettingsFileContent};
|
use settings::{self, KeymapFileContent, Settings, SettingsFileContent};
|
||||||
use smol::process::Command;
|
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 theme::{ThemeRegistry, DEFAULT_THEME_NAME};
|
||||||
use util::ResultExt;
|
use util::{ResultExt, TryFutureExt};
|
||||||
use workspace::{self, AppState, OpenNew, OpenPaths};
|
use workspace::{self, AppState, OpenNew, OpenPaths};
|
||||||
use zed::{
|
use zed::{
|
||||||
self, build_window_options, build_workspace,
|
self, build_window_options, build_workspace,
|
||||||
@ -31,9 +47,16 @@ use zed::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn main() {
|
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();
|
let mut app = gpui::App::new(Assets).unwrap();
|
||||||
|
init_crash_handler(logs_dir_path, http.clone(), app.background());
|
||||||
|
|
||||||
load_embedded_fonts(&app);
|
load_embedded_fonts(&app);
|
||||||
|
|
||||||
let fs = Arc::new(RealFs);
|
let fs = Arc::new(RealFs);
|
||||||
@ -107,7 +130,6 @@ fn main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.run(move |cx| {
|
app.run(move |cx| {
|
||||||
let http = http::client();
|
|
||||||
let client = client::Client::new(http.clone());
|
let client = client::Client::new(http.clone());
|
||||||
let mut languages = languages::build_language_registry(login_shell_env_loaded);
|
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));
|
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() {
|
if stdout_is_a_pty() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
} else {
|
} else {
|
||||||
let level = LevelFilter::Info;
|
let level = LevelFilter::Info;
|
||||||
let log_dir_path = dirs::home_dir()
|
let log_file_path = logs_dir_path.join("Zed.log");
|
||||||
.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 = OpenOptions::new()
|
let log_file = OpenOptions::new()
|
||||||
.create(true)
|
.create(true)
|
||||||
.append(true)
|
.append(true)
|
||||||
@ -236,10 +254,103 @@ fn init_logger() {
|
|||||||
.expect("could not open logfile");
|
.expect("could not open logfile");
|
||||||
simplelog::WriteLogger::init(level, simplelog::Config::default(), log_file)
|
simplelog::WriteLogger::init(level, simplelog::Config::default(), log_file)
|
||||||
.expect("could not initialize logger");
|
.expect("could not initialize logger");
|
||||||
log_panics::init();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn init_crash_handler(
|
||||||
|
logs_dir_path: PathBuf,
|
||||||
|
http: Arc<dyn HttpClient>,
|
||||||
|
background: Arc<Background>,
|
||||||
|
) {
|
||||||
|
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("<unnamed>");
|
||||||
|
|
||||||
|
let payload = match info.payload().downcast_ref::<&'static str>() {
|
||||||
|
Some(s) => *s,
|
||||||
|
None => match info.payload().downcast_ref::<String>() {
|
||||||
|
Some(s) => &**s,
|
||||||
|
None => "Box<Any>",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
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<()> {
|
async fn load_login_shell_environment() -> Result<()> {
|
||||||
let marker = "ZED_LOGIN_SHELL_START";
|
let marker = "ZED_LOGIN_SHELL_START";
|
||||||
let shell = env::var("SHELL").context(
|
let shell = env::var("SHELL").context(
|
||||||
|
Loading…
Reference in New Issue
Block a user