diff --git a/Cargo.lock b/Cargo.lock index 82e1b202cb5ac18870bc0cf825847d711835c9f6..6afd9734604e518d290571f82b860b9699cf06e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2853,9 +2853,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" @@ -4040,6 +4040,7 @@ name = "crashes" version = "0.1.0" dependencies = [ "bincode", + "cfg-if", "crash-handler", "log", "mach2 0.5.0", @@ -13502,9 +13503,7 @@ dependencies = [ "askpass", "assistant_tool", "assistant_tools", - "backtrace", "cargo_toml", - "chrono", "clap", "client", "clock", @@ -13549,7 +13548,6 @@ dependencies = [ "shellexpand 2.1.2", "smol", "sysinfo", - "telemetry_events", "thiserror 2.0.12", "toml 0.8.20", "unindent", @@ -20512,7 +20510,6 @@ dependencies = [ "breadcrumbs", "call", "channel", - "chrono", "clap", "cli", "client", @@ -20563,7 +20560,6 @@ dependencies = [ "language_selector", "language_tools", "languages", - "libc", "line_ending_selector", "log", "markdown", diff --git a/Cargo.toml b/Cargo.toml index e1eca763746e59e4d4ef206dafc3e6b6f3c67190..c8983efc7784a483fd7c5c759c7d9c10f1dab680 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -472,6 +472,7 @@ blake3 = "1.5.3" bytes = "1.0" cargo_metadata = "0.19" cargo_toml = "0.21" +cfg-if = "1.0.3" chrono = { version = "0.4", features = ["serde"] } ciborium = "0.2" circular-buffer = "1.0" diff --git a/crates/crashes/Cargo.toml b/crates/crashes/Cargo.toml index 9af416cbb0801c68e1a9a85b37a1b80c52da476c..0f8147038dd8c4dafbfd4f32b97340f44fc292cd 100644 --- a/crates/crashes/Cargo.toml +++ b/crates/crashes/Cargo.toml @@ -7,6 +7,7 @@ license = "GPL-3.0-or-later" [dependencies] bincode.workspace = true +cfg-if.workspace = true crash-handler.workspace = true log.workspace = true minidumper.workspace = true diff --git a/crates/crashes/src/crashes.rs b/crates/crashes/src/crashes.rs index f867f6cbdd6d1e0aa1dab8d7e4cd188295ace480..98db4bfc73f157994f3f7286c0764cfb0778e4a4 100644 --- a/crates/crashes/src/crashes.rs +++ b/crates/crashes/src/crashes.rs @@ -1,4 +1,4 @@ -use crash_handler::CrashHandler; +use crash_handler::{CrashEventResult, CrashHandler}; use log::info; use minidumper::{Client, LoopAction, MinidumpBinary}; use release_channel::{RELEASE_CHANNEL, ReleaseChannel}; @@ -10,7 +10,7 @@ use std::{ env, fs::{self, File}, io, - panic::Location, + panic::{self, PanicHookInfo}, path::{Path, PathBuf}, process::{self, Command}, sync::{ @@ -33,7 +33,16 @@ static PANIC_THREAD_ID: AtomicU32 = AtomicU32::new(0); pub async fn init(crash_init: InitCrashHandler) { if *RELEASE_CHANNEL == ReleaseChannel::Dev && env::var("ZED_GENERATE_MINIDUMPS").is_err() { + let old_hook = panic::take_hook(); + panic::set_hook(Box::new(move |info| { + unsafe { env::set_var("RUST_BACKTRACE", "1") }; + old_hook(info); + // prevent the macOS crash dialog from popping up + std::process::exit(1); + })); return; + } else { + panic::set_hook(Box::new(panic_hook)); } let exe = env::current_exe().expect("unable to find ourselves"); @@ -71,7 +80,7 @@ pub async fn init(crash_init: InitCrashHandler) { .unwrap(); let client = Arc::new(client); - let handler = crash_handler::CrashHandler::attach(unsafe { + let handler = CrashHandler::attach(unsafe { let client = client.clone(); crash_handler::make_crash_event(move |crash_context: &crash_handler::CrashContext| { // only request a minidump once @@ -87,7 +96,7 @@ pub async fn init(crash_init: InitCrashHandler) { } else { true }; - crash_handler::CrashEventResult::Handled(res) + CrashEventResult::Handled(res) }) }) .expect("failed to attach signal handler"); @@ -257,8 +266,16 @@ impl minidumper::ServerHandler for CrashServer { } } -pub fn handle_panic(message: String, span: Option<&Location>) { - let span = span +pub fn panic_hook(info: &PanicHookInfo) { + let message = info + .payload() + .downcast_ref::<&str>() + .map(|s| s.to_string()) + .or_else(|| info.payload().downcast_ref::().cloned()) + .unwrap_or_else(|| "Box".to_string()); + + let span = info + .location() .map(|loc| format!("{}:{}", loc.file(), loc.line())) .unwrap_or_default(); @@ -281,11 +298,15 @@ pub fn handle_panic(message: String, span: Option<&Location>) { Ordering::SeqCst, ); - #[cfg(target_os = "linux")] - CrashHandler.simulate_signal(crash_handler::Signal::Trap as u32); - #[cfg(not(target_os = "linux"))] - CrashHandler.simulate_exception(None); - break; + cfg_if::cfg_if! { + if #[cfg(target_os = "windows")] { + // https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499- + CrashHandler.simulate_exception(Some(234)); // (MORE_DATA_AVAILABLE) + break; + } else { + std::process::abort(); + } + } } thread::sleep(retry_frequency); } diff --git a/crates/proto/proto/app.proto b/crates/proto/proto/app.proto index 66f8da44f2220266834e0535d3a4e2291c079f1c..889086e20090063ee60452bd64ea62f4a08da434 100644 --- a/crates/proto/proto/app.proto +++ b/crates/proto/proto/app.proto @@ -28,7 +28,7 @@ message GetCrashFiles { message GetCrashFilesResponse { repeated CrashReport crashes = 1; - repeated string legacy_panics = 2; + reserved 2; // old panics } message CrashReport { diff --git a/crates/remote_server/Cargo.toml b/crates/remote_server/Cargo.toml index 5dbb9a2771c4e3fda04ed014f993f843b44dd976..59b2af0f410c98385cf13a271833980aacd6c8bc 100644 --- a/crates/remote_server/Cargo.toml +++ b/crates/remote_server/Cargo.toml @@ -24,8 +24,6 @@ test-support = ["fs/test-support"] [dependencies] anyhow.workspace = true askpass.workspace = true -backtrace = "0.3" -chrono.workspace = true clap.workspace = true client.workspace = true dap_adapters.workspace = true @@ -61,7 +59,6 @@ settings.workspace = true shellexpand.workspace = true smol.workspace = true sysinfo.workspace = true -telemetry_events.workspace = true util.workspace = true watch.workspace = true worktree.workspace = true diff --git a/crates/remote_server/src/unix.rs b/crates/remote_server/src/unix.rs index 4aef536f0a45b5ea943f861da2be94ab7c2c21c4..d6b730e6d63ea990e695f71a38028dfcc9f8600f 100644 --- a/crates/remote_server/src/unix.rs +++ b/crates/remote_server/src/unix.rs @@ -1,8 +1,8 @@ use crate::HeadlessProject; use crate::headless_project::HeadlessAppState; use anyhow::{Context as _, Result, anyhow}; -use chrono::Utc; -use client::{ProxySettings, telemetry}; +use client::ProxySettings; +use util::ResultExt; use extension::ExtensionHostProxy; use fs::{Fs, RealFs}; @@ -29,26 +29,23 @@ use reqwest_client::ReqwestClient; use rpc::proto::{self, Envelope, REMOTE_SERVER_PROJECT_ID}; use rpc::{AnyProtoClient, TypedEnvelope}; use settings::{Settings, SettingsStore, watch_config_file}; +use smol::Async; use smol::channel::{Receiver, Sender}; use smol::io::AsyncReadExt; - -use smol::Async; use smol::{net::unix::UnixListener, stream::StreamExt as _}; -use std::ffi::OsStr; -use std::ops::ControlFlow; -use std::process::ExitStatus; -use std::str::FromStr; -use std::sync::LazyLock; -use std::{env, thread}; use std::{ + env, + ffi::OsStr, + fs::File, io::Write, mem, + ops::ControlFlow, path::{Path, PathBuf}, - sync::Arc, + process::ExitStatus, + str::FromStr, + sync::{Arc, LazyLock}, }; -use telemetry_events::LocationData; use thiserror::Error; -use util::ResultExt; pub static VERSION: LazyLock<&str> = LazyLock::new(|| match *RELEASE_CHANNEL { ReleaseChannel::Stable | ReleaseChannel::Preview => env!("ZED_PKG_VERSION"), @@ -71,12 +68,12 @@ fn init_logging_proxy() { fn init_logging_server(log_file_path: PathBuf) -> Result>> { struct MultiWrite { - file: std::fs::File, + file: File, channel: Sender>, buffer: Vec, } - impl std::io::Write for MultiWrite { + impl Write for MultiWrite { fn write(&mut self, buf: &[u8]) -> std::io::Result { let written = self.file.write(buf)?; self.buffer.extend_from_slice(&buf[..written]); @@ -120,87 +117,6 @@ fn init_logging_server(log_file_path: PathBuf) -> Result>> { Ok(rx) } -fn init_panic_hook(session_id: String) { - std::panic::set_hook(Box::new(move |info| { - let payload = info - .payload() - .downcast_ref::<&str>() - .map(|s| s.to_string()) - .or_else(|| info.payload().downcast_ref::().cloned()) - .unwrap_or_else(|| "Box".to_string()); - - crashes::handle_panic(payload.clone(), info.location()); - - let backtrace = backtrace::Backtrace::new(); - let mut backtrace = backtrace - .frames() - .iter() - .flat_map(|frame| { - frame - .symbols() - .iter() - .filter_map(|frame| Some(format!("{:#}", frame.name()?))) - }) - .collect::>(); - - // Strip out leading stack frames for rust panic-handling. - if let Some(ix) = backtrace - .iter() - .position(|name| name == "rust_begin_unwind") - { - backtrace.drain(0..=ix); - } - - let thread = thread::current(); - let thread_name = thread.name().unwrap_or(""); - - log::error!( - "panic occurred: {}\nBacktrace:\n{}", - &payload, - backtrace.join("\n") - ); - - let panic_data = telemetry_events::Panic { - thread: thread_name.into(), - payload, - location_data: info.location().map(|location| LocationData { - file: location.file().into(), - line: location.line(), - }), - app_version: format!("remote-server-{}", *VERSION), - app_commit_sha: option_env!("ZED_COMMIT_SHA").map(|sha| sha.into()), - release_channel: RELEASE_CHANNEL.dev_name().into(), - target: env!("TARGET").to_owned().into(), - os_name: telemetry::os_name(), - os_version: Some(telemetry::os_version()), - architecture: env::consts::ARCH.into(), - panicked_on: Utc::now().timestamp_millis(), - backtrace, - system_id: None, // Set on SSH client - installation_id: None, // Set on SSH client - - // used on this end to associate panics with minidumps, but will be replaced on the SSH client - session_id: session_id.clone(), - }; - - if let Some(panic_data_json) = serde_json::to_string(&panic_data).log_err() { - let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string(); - let panic_file_path = paths::logs_dir().join(format!("zed-{timestamp}.panic")); - let panic_file = std::fs::OpenOptions::new() - .append(true) - .create(true) - .open(&panic_file_path) - .log_err(); - if let Some(mut panic_file) = panic_file { - writeln!(&mut panic_file, "{panic_data_json}").log_err(); - panic_file.flush().log_err(); - } - } - - std::process::abort(); - })); -} - fn handle_crash_files_requests(project: &Entity, client: &AnyProtoClient) { client.add_request_handler( project.downgrade(), @@ -249,10 +165,7 @@ fn handle_crash_files_requests(project: &Entity, client: &AnyPr } } - anyhow::Ok(proto::GetCrashFilesResponse { - crashes, - legacy_panics, - }) + anyhow::Ok(proto::GetCrashFilesResponse { crashes }) }, ); } @@ -434,13 +347,12 @@ pub fn execute_run( let id = std::process::id().to_string(); app.background_executor() .spawn(crashes::init(crashes::InitCrashHandler { - session_id: id.clone(), + session_id: id, zed_version: VERSION.to_owned(), release_channel: release_channel::RELEASE_CHANNEL_NAME.clone(), commit_sha: option_env!("ZED_COMMIT_SHA").unwrap_or("no_sha").to_owned(), })) .detach(); - init_panic_hook(id); let log_rx = init_logging_server(log_file)?; log::info!( "starting up. pid_file: {:?}, stdin_socket: {:?}, stdout_socket: {:?}, stderr_socket: {:?}", @@ -627,13 +539,12 @@ pub(crate) fn execute_proxy( let id = std::process::id().to_string(); smol::spawn(crashes::init(crashes::InitCrashHandler { - session_id: id.clone(), + session_id: id, zed_version: VERSION.to_owned(), release_channel: release_channel::RELEASE_CHANNEL_NAME.clone(), commit_sha: option_env!("ZED_COMMIT_SHA").unwrap_or("no_sha").to_owned(), })) .detach(); - init_panic_hook(id); log::info!("starting proxy process. PID: {}", std::process::id()); diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 7913b46421b1ad5d26d8e5f16c66949500f84898..cc18e50a9c2f81bc6d84e448ee807a792bcd8fe8 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -37,7 +37,6 @@ bincode.workspace = true breadcrumbs.workspace = true call.workspace = true channel.workspace = true -chrono.workspace = true clap.workspace = true cli.workspace = true client.workspace = true @@ -93,7 +92,6 @@ language_selector.workspace = true language_tools.workspace = true languages = { workspace = true, features = ["load-grammars"] } line_ending_selector.workspace = true -libc.workspace = true log.workspace = true markdown.workspace = true markdown_preview.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index b5fc8943eb6116016de14844f719c55e7abe7193..b5dbbba865d0a7e9578f6d0f1b7d193e372cd7af 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -278,13 +278,6 @@ pub fn main() { .unwrap_or_else(|| "no sha".to_owned()), })) .detach(); - reliability::init_panic_hook( - app_version, - app_commit_sha.clone(), - system_id.as_ref().map(|id| id.to_string()), - installation_id.as_ref().map(|id| id.to_string()), - session_id.clone(), - ); let (open_listener, mut open_rx) = OpenListener::new(); @@ -542,8 +535,6 @@ pub fn main() { reliability::init( client.http_client(), system_id.as_ref().map(|id| id.to_string()), - installation_id.clone().map(|id| id.to_string()), - session_id.clone(), cx, ); diff --git a/crates/zed/src/reliability.rs b/crates/zed/src/reliability.rs index 544fac226e3f80042104cfd1575c28e244100b3d..4297f4c1c666d19fd184f03b62db17730c611356 100644 --- a/crates/zed/src/reliability.rs +++ b/crates/zed/src/reliability.rs @@ -1,226 +1,32 @@ -use crate::stdout_is_a_pty; use anyhow::{Context as _, Result}; -use backtrace::{self, Backtrace}; -use chrono::Utc; -use client::{ - TelemetrySettings, - telemetry::{self, MINIDUMP_ENDPOINT}, -}; -use db::kvp::KEY_VALUE_STORE; +use client::{TelemetrySettings, telemetry::MINIDUMP_ENDPOINT}; use futures::AsyncReadExt; -use gpui::{App, AppContext as _, SemanticVersion}; -use http_client::{self, HttpClient, HttpClientWithUrl, HttpRequestExt, Method}; -use paths::{crashes_dir, crashes_retired_dir}; +use gpui::{App, AppContext as _}; +use http_client::{self, HttpClient, HttpClientWithUrl}; use project::Project; use proto::{CrashReport, GetCrashFilesResponse}; -use release_channel::{AppCommitSha, RELEASE_CHANNEL, ReleaseChannel}; use reqwest::multipart::{Form, Part}; use settings::Settings; use smol::stream::StreamExt; -use std::{ - env, - ffi::{OsStr, c_void}, - fs, - io::Write, - panic, - sync::{ - Arc, - atomic::{AtomicU32, Ordering}, - }, - thread, -}; -use telemetry_events::{LocationData, Panic, PanicRequest}; -use url::Url; +use std::{ffi::OsStr, fs, sync::Arc}; use util::ResultExt; -static PANIC_COUNT: AtomicU32 = AtomicU32::new(0); - -pub fn init_panic_hook( - app_version: SemanticVersion, - app_commit_sha: Option, - system_id: Option, - installation_id: Option, - session_id: String, -) { - let is_pty = stdout_is_a_pty(); - - panic::set_hook(Box::new(move |info| { - let prior_panic_count = PANIC_COUNT.fetch_add(1, Ordering::SeqCst); - if prior_panic_count > 0 { - // Give the panic-ing thread time to write the panic file - loop { - thread::yield_now(); - } - } - - let payload = info - .payload() - .downcast_ref::<&str>() - .map(|s| s.to_string()) - .or_else(|| info.payload().downcast_ref::().cloned()) - .unwrap_or_else(|| "Box".to_string()); - - if *release_channel::RELEASE_CHANNEL != ReleaseChannel::Dev - || env::var("ZED_GENERATE_MINIDUMPS").is_ok() - { - crashes::handle_panic(payload.clone(), info.location()); - } - - let thread = thread::current(); - let thread_name = thread.name().unwrap_or(""); - - if *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev { - let location = info.location().unwrap(); - let backtrace = Backtrace::new(); - eprintln!( - "Thread {:?} panicked with {:?} at {}:{}:{}\n{}{:?}", - thread_name, - payload, - location.file(), - location.line(), - location.column(), - match app_commit_sha.as_ref() { - Some(commit_sha) => format!( - "https://github.com/zed-industries/zed/blob/{}/{}#L{} \ - (may not be uploaded, line may be incorrect if files modified)\n", - commit_sha.full(), - location.file(), - location.line() - ), - None => "".to_string(), - }, - backtrace, - ); - if MINIDUMP_ENDPOINT.is_none() { - std::process::exit(-1); - } - } - let main_module_base_address = get_main_module_base_address(); - - let backtrace = Backtrace::new(); - let mut symbols = backtrace - .frames() - .iter() - .flat_map(|frame| { - let base = frame - .module_base_address() - .unwrap_or(main_module_base_address); - frame.symbols().iter().map(move |symbol| { - format!( - "{}+{}", - symbol - .name() - .as_ref() - .map_or("".to_owned(), <_>::to_string), - (frame.ip() as isize).saturating_sub(base as isize) - ) - }) - }) - .collect::>(); - - // Strip out leading stack frames for rust panic-handling. - if let Some(ix) = symbols - .iter() - .position(|name| name == "rust_begin_unwind" || name == "_rust_begin_unwind") - { - symbols.drain(0..=ix); - } - - let panic_data = telemetry_events::Panic { - thread: thread_name.into(), - payload, - location_data: info.location().map(|location| LocationData { - file: location.file().into(), - line: location.line(), - }), - app_version: app_version.to_string(), - app_commit_sha: app_commit_sha.as_ref().map(|sha| sha.full()), - release_channel: RELEASE_CHANNEL.dev_name().into(), - target: env!("TARGET").to_owned().into(), - os_name: telemetry::os_name(), - os_version: Some(telemetry::os_version()), - architecture: env::consts::ARCH.into(), - panicked_on: Utc::now().timestamp_millis(), - backtrace: symbols, - system_id: system_id.clone(), - installation_id: installation_id.clone(), - session_id: session_id.clone(), - }; - - if let Some(panic_data_json) = serde_json::to_string_pretty(&panic_data).log_err() { - log::error!("{}", panic_data_json); - } - zlog::flush(); - - if (!is_pty || MINIDUMP_ENDPOINT.is_some()) - && let Some(panic_data_json) = serde_json::to_string(&panic_data).log_err() - { - let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string(); - let panic_file_path = paths::logs_dir().join(format!("zed-{timestamp}.panic")); - let panic_file = fs::OpenOptions::new() - .write(true) - .create_new(true) - .open(&panic_file_path) - .log_err(); - if let Some(mut panic_file) = panic_file { - writeln!(&mut panic_file, "{panic_data_json}").log_err(); - panic_file.flush().log_err(); - } - } - - std::process::abort(); - })); -} - -#[cfg(not(target_os = "windows"))] -fn get_main_module_base_address() -> *mut c_void { - let mut dl_info = libc::Dl_info { - dli_fname: std::ptr::null(), - dli_fbase: std::ptr::null_mut(), - dli_sname: std::ptr::null(), - dli_saddr: std::ptr::null_mut(), - }; - unsafe { - libc::dladdr(get_main_module_base_address as _, &mut dl_info); - } - dl_info.dli_fbase -} - -#[cfg(target_os = "windows")] -fn get_main_module_base_address() -> *mut c_void { - std::ptr::null_mut() -} - -pub fn init( - http_client: Arc, - system_id: Option, - installation_id: Option, - session_id: String, - cx: &mut App, -) { +pub fn init(http_client: Arc, installation_id: Option, cx: &mut App) { #[cfg(target_os = "macos")] monitor_main_thread_hangs(http_client.clone(), installation_id.clone(), cx); - let Some(panic_report_url) = http_client - .build_zed_api_url("/telemetry/panics", &[]) - .log_err() - else { - return; - }; - - upload_panics_and_crashes( - http_client.clone(), - panic_report_url.clone(), - installation_id.clone(), - cx, - ); + if client::TelemetrySettings::get_global(cx).diagnostics { + let client = http_client.clone(); + let id = installation_id.clone(); + cx.background_spawn(async move { + upload_previous_minidumps(client, id).await.warn_on_err(); + }) + .detach() + } cx.observe_new(move |project: &mut Project, _, cx| { let http_client = http_client.clone(); - let panic_report_url = panic_report_url.clone(); - let session_id = session_id.clone(); let installation_id = installation_id.clone(); - let system_id = system_id.clone(); let Some(remote_client) = project.remote_client() else { return; @@ -231,19 +37,7 @@ pub fn init( } let request = client.proto_client().request(proto::GetCrashFiles {}); cx.background_spawn(async move { - let GetCrashFilesResponse { - legacy_panics, - crashes, - } = request.await?; - - for panic in legacy_panics { - if let Some(mut panic) = serde_json::from_str::(&panic).log_err() { - panic.session_id = session_id.clone(); - panic.system_id = system_id.clone(); - panic.installation_id = installation_id.clone(); - upload_panic(&http_client, &panic_report_url, panic, &mut None).await?; - } - } + let GetCrashFilesResponse { crashes } = request.await?; let Some(endpoint) = MINIDUMP_ENDPOINT.as_ref() else { return Ok(()); @@ -297,6 +91,7 @@ pub fn monitor_main_thread_hangs( use parking_lot::Mutex; use http_client::Method; + use release_channel::ReleaseChannel; use std::{ ffi::c_int, sync::{OnceLock, mpsc}, @@ -440,7 +235,7 @@ pub fn monitor_main_thread_hangs( app_version: Some(app_version), os_name: os_name.clone(), os_version: Some(os_version.clone()), - architecture: env::consts::ARCH.into(), + architecture: std::env::consts::ARCH.into(), installation_id: installation_id.clone(), }; @@ -477,86 +272,6 @@ pub fn monitor_main_thread_hangs( .detach() } -fn upload_panics_and_crashes( - http: Arc, - panic_report_url: Url, - installation_id: Option, - cx: &App, -) { - if !client::TelemetrySettings::get_global(cx).diagnostics { - return; - } - cx.background_spawn(async move { - upload_previous_minidumps(http.clone(), installation_id.clone()) - .await - .warn_on_err(); - let most_recent_panic = upload_previous_panics(http.clone(), &panic_report_url) - .await - .log_err() - .flatten(); - upload_previous_crashes(http, most_recent_panic, installation_id) - .await - .log_err(); - }) - .detach() -} - -/// Uploads panics via `zed.dev`. -async fn upload_previous_panics( - http: Arc, - panic_report_url: &Url, -) -> anyhow::Result> { - let mut children = smol::fs::read_dir(paths::logs_dir()).await?; - - let mut most_recent_panic = None; - - while let Some(child) = children.next().await { - let child = child?; - let child_path = child.path(); - - if child_path.extension() != Some(OsStr::new("panic")) { - continue; - } - let filename = if let Some(filename) = child_path.file_name() { - filename.to_string_lossy() - } else { - continue; - }; - - if !filename.starts_with("zed") { - continue; - } - - let panic_file_content = smol::fs::read_to_string(&child_path) - .await - .context("error reading panic file")?; - - let panic: Option = serde_json::from_str(&panic_file_content) - .log_err() - .or_else(|| { - panic_file_content - .lines() - .next() - .and_then(|line| serde_json::from_str(line).ok()) - }) - .unwrap_or_else(|| { - log::error!("failed to deserialize panic file {:?}", panic_file_content); - None - }); - - if let Some(panic) = panic - && upload_panic(&http, panic_report_url, panic, &mut most_recent_panic).await? - { - // We've done what we can, delete the file - fs::remove_file(child_path) - .context("error removing panic") - .log_err(); - } - } - - Ok(most_recent_panic) -} - pub async fn upload_previous_minidumps( http: Arc, installation_id: Option, @@ -728,109 +443,3 @@ impl FormExt for Form { } } } - -async fn upload_panic( - http: &Arc, - panic_report_url: &Url, - panic: telemetry_events::Panic, - most_recent_panic: &mut Option<(i64, String)>, -) -> Result { - *most_recent_panic = Some((panic.panicked_on, panic.payload.clone())); - - let json_bytes = serde_json::to_vec(&PanicRequest { panic }).unwrap(); - - let Some(checksum) = client::telemetry::calculate_json_checksum(&json_bytes) else { - return Ok(false); - }; - - let Ok(request) = http_client::Request::builder() - .method(Method::POST) - .uri(panic_report_url.as_ref()) - .header("x-zed-checksum", checksum) - .body(json_bytes.into()) - else { - return Ok(false); - }; - - let response = http.send(request).await.context("error sending panic")?; - if !response.status().is_success() { - log::error!("Error uploading panic to server: {}", response.status()); - } - - Ok(true) -} -const LAST_CRASH_UPLOADED: &str = "LAST_CRASH_UPLOADED"; - -/// upload crashes from apple's diagnostic reports to our server. -/// (only if telemetry is enabled) -async fn upload_previous_crashes( - http: Arc, - most_recent_panic: Option<(i64, String)>, - installation_id: Option, -) -> Result<()> { - let last_uploaded = KEY_VALUE_STORE - .read_kvp(LAST_CRASH_UPLOADED)? - .unwrap_or("zed-2024-01-17-221900.ips".to_string()); // don't upload old crash reports from before we had this. - let mut uploaded = last_uploaded.clone(); - - let crash_report_url = http.build_zed_api_url("/telemetry/crashes", &[])?; - - // Crash directories are only set on macOS. - for dir in [crashes_dir(), crashes_retired_dir()] - .iter() - .filter_map(|d| d.as_deref()) - { - let mut children = smol::fs::read_dir(&dir).await?; - while let Some(child) = children.next().await { - let child = child?; - let Some(filename) = child - .path() - .file_name() - .map(|f| f.to_string_lossy().to_lowercase()) - else { - continue; - }; - - if !filename.starts_with("zed-") || !filename.ends_with(".ips") { - continue; - } - - if filename <= last_uploaded { - continue; - } - - let body = smol::fs::read_to_string(&child.path()) - .await - .context("error reading crash file")?; - - let mut request = http_client::Request::post(&crash_report_url.to_string()) - .follow_redirects(http_client::RedirectPolicy::FollowAll) - .header("Content-Type", "text/plain"); - - if let Some((panicked_on, payload)) = most_recent_panic.as_ref() { - request = request - .header("x-zed-panicked-on", format!("{panicked_on}")) - .header("x-zed-panic", payload) - } - if let Some(installation_id) = installation_id.as_ref() { - request = request.header("x-zed-installation-id", installation_id); - } - - let request = request.body(body.into())?; - - let response = http.send(request).await.context("error sending crash")?; - if !response.status().is_success() { - log::error!("Error uploading crash to server: {}", response.status()); - } - - if uploaded < filename { - uploaded.clone_from(&filename); - KEY_VALUE_STORE - .write_kvp(LAST_CRASH_UPLOADED.to_string(), filename) - .await?; - } - } - } - - Ok(()) -}