Separate minidump crashes from panics (#36267)
The minidump-based crash reporting is now entirely separate from our legacy panic_hook-based reporting. This should improve the association of minidumps with their metadata and give us more consistent crash reports. Release Notes: - N/A --------- Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
parent
f5f14111ef
commit
7784fac288
8 changed files with 315 additions and 248 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -4038,6 +4038,8 @@ dependencies = [
|
||||||
"minidumper",
|
"minidumper",
|
||||||
"paths",
|
"paths",
|
||||||
"release_channel",
|
"release_channel",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"smol",
|
"smol",
|
||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
]
|
]
|
||||||
|
|
|
@ -12,6 +12,8 @@ minidumper.workspace = true
|
||||||
paths.workspace = true
|
paths.workspace = true
|
||||||
release_channel.workspace = true
|
release_channel.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|
|
@ -2,15 +2,17 @@ use crash_handler::CrashHandler;
|
||||||
use log::info;
|
use log::info;
|
||||||
use minidumper::{Client, LoopAction, MinidumpBinary};
|
use minidumper::{Client, LoopAction, MinidumpBinary};
|
||||||
use release_channel::{RELEASE_CHANNEL, ReleaseChannel};
|
use release_channel::{RELEASE_CHANNEL, ReleaseChannel};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
fs::File,
|
fs::{self, File},
|
||||||
io,
|
io,
|
||||||
|
panic::Location,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::{self, Command},
|
process::{self, Command},
|
||||||
sync::{
|
sync::{
|
||||||
LazyLock, OnceLock,
|
Arc, OnceLock,
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
},
|
},
|
||||||
thread,
|
thread,
|
||||||
|
@ -18,19 +20,17 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
// set once the crash handler has initialized and the client has connected to it
|
// set once the crash handler has initialized and the client has connected to it
|
||||||
pub static CRASH_HANDLER: AtomicBool = AtomicBool::new(false);
|
pub static CRASH_HANDLER: OnceLock<Arc<Client>> = OnceLock::new();
|
||||||
// set when the first minidump request is made to avoid generating duplicate crash reports
|
// set when the first minidump request is made to avoid generating duplicate crash reports
|
||||||
pub static REQUESTED_MINIDUMP: AtomicBool = AtomicBool::new(false);
|
pub static REQUESTED_MINIDUMP: AtomicBool = AtomicBool::new(false);
|
||||||
const CRASH_HANDLER_TIMEOUT: Duration = Duration::from_secs(60);
|
const CRASH_HANDLER_PING_TIMEOUT: Duration = Duration::from_secs(60);
|
||||||
|
const CRASH_HANDLER_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
|
|
||||||
pub static GENERATE_MINIDUMPS: LazyLock<bool> = LazyLock::new(|| {
|
pub async fn init(crash_init: InitCrashHandler) {
|
||||||
*RELEASE_CHANNEL != ReleaseChannel::Dev || env::var("ZED_GENERATE_MINIDUMPS").is_ok()
|
if *RELEASE_CHANNEL == ReleaseChannel::Dev && env::var("ZED_GENERATE_MINIDUMPS").is_err() {
|
||||||
});
|
|
||||||
|
|
||||||
pub async fn init(id: String) {
|
|
||||||
if !*GENERATE_MINIDUMPS {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let exe = env::current_exe().expect("unable to find ourselves");
|
let exe = env::current_exe().expect("unable to find ourselves");
|
||||||
let zed_pid = process::id();
|
let zed_pid = process::id();
|
||||||
// TODO: we should be able to get away with using 1 crash-handler process per machine,
|
// TODO: we should be able to get away with using 1 crash-handler process per machine,
|
||||||
|
@ -61,9 +61,11 @@ pub async fn init(id: String) {
|
||||||
smol::Timer::after(retry_frequency).await;
|
smol::Timer::after(retry_frequency).await;
|
||||||
}
|
}
|
||||||
let client = maybe_client.unwrap();
|
let client = maybe_client.unwrap();
|
||||||
client.send_message(1, id).unwrap(); // set session id on the server
|
client
|
||||||
|
.send_message(1, serde_json::to_vec(&crash_init).unwrap())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let client = std::sync::Arc::new(client);
|
let client = Arc::new(client);
|
||||||
let handler = crash_handler::CrashHandler::attach(unsafe {
|
let handler = crash_handler::CrashHandler::attach(unsafe {
|
||||||
let client = client.clone();
|
let client = client.clone();
|
||||||
crash_handler::make_crash_event(move |crash_context: &crash_handler::CrashContext| {
|
crash_handler::make_crash_event(move |crash_context: &crash_handler::CrashContext| {
|
||||||
|
@ -72,7 +74,6 @@ pub async fn init(id: String) {
|
||||||
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
|
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
client.send_message(2, "mistakes were made").unwrap();
|
|
||||||
client.ping().unwrap();
|
client.ping().unwrap();
|
||||||
client.request_dump(crash_context).is_ok()
|
client.request_dump(crash_context).is_ok()
|
||||||
} else {
|
} else {
|
||||||
|
@ -87,7 +88,7 @@ pub async fn init(id: String) {
|
||||||
{
|
{
|
||||||
handler.set_ptracer(Some(server_pid));
|
handler.set_ptracer(Some(server_pid));
|
||||||
}
|
}
|
||||||
CRASH_HANDLER.store(true, Ordering::Release);
|
CRASH_HANDLER.set(client.clone()).ok();
|
||||||
std::mem::forget(handler);
|
std::mem::forget(handler);
|
||||||
info!("crash handler registered");
|
info!("crash handler registered");
|
||||||
|
|
||||||
|
@ -98,14 +99,43 @@ pub async fn init(id: String) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CrashServer {
|
pub struct CrashServer {
|
||||||
session_id: OnceLock<String>,
|
initialization_params: OnceLock<InitCrashHandler>,
|
||||||
|
panic_info: OnceLock<CrashPanic>,
|
||||||
|
has_connection: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct CrashInfo {
|
||||||
|
pub init: InitCrashHandler,
|
||||||
|
pub panic: Option<CrashPanic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct InitCrashHandler {
|
||||||
|
pub session_id: String,
|
||||||
|
pub zed_version: String,
|
||||||
|
pub release_channel: String,
|
||||||
|
pub commit_sha: String,
|
||||||
|
// pub gpu: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
|
pub struct CrashPanic {
|
||||||
|
pub message: String,
|
||||||
|
pub span: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl minidumper::ServerHandler for CrashServer {
|
impl minidumper::ServerHandler for CrashServer {
|
||||||
fn create_minidump_file(&self) -> Result<(File, PathBuf), io::Error> {
|
fn create_minidump_file(&self) -> Result<(File, PathBuf), io::Error> {
|
||||||
let err_message = "Need to send a message with the ID upon starting the crash handler";
|
let err_message = "Missing initialization data";
|
||||||
let dump_path = paths::logs_dir()
|
let dump_path = paths::logs_dir()
|
||||||
.join(self.session_id.get().expect(err_message))
|
.join(
|
||||||
|
&self
|
||||||
|
.initialization_params
|
||||||
|
.get()
|
||||||
|
.expect(err_message)
|
||||||
|
.session_id,
|
||||||
|
)
|
||||||
.with_extension("dmp");
|
.with_extension("dmp");
|
||||||
let file = File::create(&dump_path)?;
|
let file = File::create(&dump_path)?;
|
||||||
Ok((file, dump_path))
|
Ok((file, dump_path))
|
||||||
|
@ -122,38 +152,71 @@ impl minidumper::ServerHandler for CrashServer {
|
||||||
info!("failed to write minidump: {:#}", e);
|
info!("failed to write minidump: {:#}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let crash_info = CrashInfo {
|
||||||
|
init: self
|
||||||
|
.initialization_params
|
||||||
|
.get()
|
||||||
|
.expect("not initialized")
|
||||||
|
.clone(),
|
||||||
|
panic: self.panic_info.get().cloned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let crash_data_path = paths::logs_dir()
|
||||||
|
.join(&crash_info.init.session_id)
|
||||||
|
.with_extension("json");
|
||||||
|
|
||||||
|
fs::write(crash_data_path, serde_json::to_vec(&crash_info).unwrap()).ok();
|
||||||
|
|
||||||
LoopAction::Exit
|
LoopAction::Exit
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_message(&self, kind: u32, buffer: Vec<u8>) {
|
fn on_message(&self, kind: u32, buffer: Vec<u8>) {
|
||||||
let message = String::from_utf8(buffer).expect("invalid utf-8");
|
match kind {
|
||||||
info!("kind: {kind}, message: {message}",);
|
1 => {
|
||||||
if kind == 1 {
|
let init_data =
|
||||||
self.session_id
|
serde_json::from_slice::<InitCrashHandler>(&buffer).expect("invalid init data");
|
||||||
.set(message)
|
self.initialization_params
|
||||||
.expect("session id already initialized");
|
.set(init_data)
|
||||||
|
.expect("already initialized");
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
let panic_data =
|
||||||
|
serde_json::from_slice::<CrashPanic>(&buffer).expect("invalid panic data");
|
||||||
|
self.panic_info.set(panic_data).expect("already panicked");
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("invalid message kind");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_client_disconnected(&self, clients: usize) -> LoopAction {
|
fn on_client_disconnected(&self, _clients: usize) -> LoopAction {
|
||||||
info!("client disconnected, {clients} remaining");
|
LoopAction::Exit
|
||||||
if clients == 0 {
|
}
|
||||||
LoopAction::Exit
|
|
||||||
} else {
|
fn on_client_connected(&self, _clients: usize) -> LoopAction {
|
||||||
LoopAction::Continue
|
self.has_connection.store(true, Ordering::SeqCst);
|
||||||
}
|
LoopAction::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_panic() {
|
pub fn handle_panic(message: String, span: Option<&Location>) {
|
||||||
if !*GENERATE_MINIDUMPS {
|
let span = span
|
||||||
return;
|
.map(|loc| format!("{}:{}", loc.file(), loc.line()))
|
||||||
}
|
.unwrap_or_default();
|
||||||
|
|
||||||
// wait 500ms for the crash handler process to start up
|
// wait 500ms for the crash handler process to start up
|
||||||
// if it's still not there just write panic info and no minidump
|
// if it's still not there just write panic info and no minidump
|
||||||
let retry_frequency = Duration::from_millis(100);
|
let retry_frequency = Duration::from_millis(100);
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
if CRASH_HANDLER.load(Ordering::Acquire) {
|
if let Some(client) = CRASH_HANDLER.get() {
|
||||||
|
client
|
||||||
|
.send_message(
|
||||||
|
2,
|
||||||
|
serde_json::to_vec(&CrashPanic { message, span }).unwrap(),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
log::error!("triggering a crash to generate a minidump...");
|
log::error!("triggering a crash to generate a minidump...");
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
CrashHandler.simulate_signal(crash_handler::Signal::Trap as u32);
|
CrashHandler.simulate_signal(crash_handler::Signal::Trap as u32);
|
||||||
|
@ -170,14 +233,30 @@ pub fn crash_server(socket: &Path) {
|
||||||
log::info!("Couldn't create socket, there may already be a running crash server");
|
log::info!("Couldn't create socket, there may already be a running crash server");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let ab = AtomicBool::new(false);
|
|
||||||
|
let shutdown = Arc::new(AtomicBool::new(false));
|
||||||
|
let has_connection = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
|
std::thread::spawn({
|
||||||
|
let shutdown = shutdown.clone();
|
||||||
|
let has_connection = has_connection.clone();
|
||||||
|
move || {
|
||||||
|
std::thread::sleep(CRASH_HANDLER_CONNECT_TIMEOUT);
|
||||||
|
if !has_connection.load(Ordering::SeqCst) {
|
||||||
|
shutdown.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server
|
server
|
||||||
.run(
|
.run(
|
||||||
Box::new(CrashServer {
|
Box::new(CrashServer {
|
||||||
session_id: OnceLock::new(),
|
initialization_params: OnceLock::new(),
|
||||||
|
panic_info: OnceLock::new(),
|
||||||
|
has_connection,
|
||||||
}),
|
}),
|
||||||
&ab,
|
&shutdown,
|
||||||
Some(CRASH_HANDLER_TIMEOUT),
|
Some(CRASH_HANDLER_PING_TIMEOUT),
|
||||||
)
|
)
|
||||||
.expect("failed to run server");
|
.expect("failed to run server");
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,11 +28,13 @@ message GetCrashFiles {
|
||||||
|
|
||||||
message GetCrashFilesResponse {
|
message GetCrashFilesResponse {
|
||||||
repeated CrashReport crashes = 1;
|
repeated CrashReport crashes = 1;
|
||||||
|
repeated string legacy_panics = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CrashReport {
|
message CrashReport {
|
||||||
optional string panic_contents = 1;
|
reserved 1, 2;
|
||||||
optional bytes minidump_contents = 2;
|
string metadata = 3;
|
||||||
|
bytes minidump_contents = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Extension {
|
message Extension {
|
||||||
|
|
|
@ -1490,20 +1490,17 @@ impl RemoteConnection for SshRemoteConnection {
|
||||||
identifier = &unique_identifier,
|
identifier = &unique_identifier,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(rust_log) = std::env::var("RUST_LOG").ok() {
|
for env_var in ["RUST_LOG", "RUST_BACKTRACE", "ZED_GENERATE_MINIDUMPS"] {
|
||||||
start_proxy_command = format!(
|
if let Some(value) = std::env::var(env_var).ok() {
|
||||||
"RUST_LOG={} {}",
|
start_proxy_command = format!(
|
||||||
shlex::try_quote(&rust_log).unwrap(),
|
"{}={} {} ",
|
||||||
start_proxy_command
|
env_var,
|
||||||
)
|
shlex::try_quote(&value).unwrap(),
|
||||||
}
|
start_proxy_command,
|
||||||
if let Some(rust_backtrace) = std::env::var("RUST_BACKTRACE").ok() {
|
);
|
||||||
start_proxy_command = format!(
|
}
|
||||||
"RUST_BACKTRACE={} {}",
|
|
||||||
shlex::try_quote(&rust_backtrace).unwrap(),
|
|
||||||
start_proxy_command
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if reconnect {
|
if reconnect {
|
||||||
start_proxy_command.push_str(" --reconnect");
|
start_proxy_command.push_str(" --reconnect");
|
||||||
}
|
}
|
||||||
|
@ -2241,8 +2238,7 @@ impl SshRemoteConnection {
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
{
|
{
|
||||||
run_cmd(Command::new("gzip").args(["-9", "-f", &bin_path.to_string_lossy()]))
|
run_cmd(Command::new("gzip").args(["-f", &bin_path.to_string_lossy()])).await?;
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
|
@ -2474,7 +2470,7 @@ impl ChannelClient {
|
||||||
},
|
},
|
||||||
async {
|
async {
|
||||||
smol::Timer::after(timeout).await;
|
smol::Timer::after(timeout).await;
|
||||||
anyhow::bail!("Timeout detected")
|
anyhow::bail!("Timed out resyncing remote client")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -2488,7 +2484,7 @@ impl ChannelClient {
|
||||||
},
|
},
|
||||||
async {
|
async {
|
||||||
smol::Timer::after(timeout).await;
|
smol::Timer::after(timeout).await;
|
||||||
anyhow::bail!("Timeout detected")
|
anyhow::bail!("Timed out pinging remote client")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -34,10 +34,10 @@ use smol::io::AsyncReadExt;
|
||||||
|
|
||||||
use smol::Async;
|
use smol::Async;
|
||||||
use smol::{net::unix::UnixListener, stream::StreamExt as _};
|
use smol::{net::unix::UnixListener, stream::StreamExt as _};
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::ops::ControlFlow;
|
use std::ops::ControlFlow;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::sync::LazyLock;
|
||||||
use std::{env, thread};
|
use std::{env, thread};
|
||||||
use std::{
|
use std::{
|
||||||
io::Write,
|
io::Write,
|
||||||
|
@ -48,6 +48,13 @@ use std::{
|
||||||
use telemetry_events::LocationData;
|
use telemetry_events::LocationData;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
|
pub static VERSION: LazyLock<&str> = LazyLock::new(|| match *RELEASE_CHANNEL {
|
||||||
|
ReleaseChannel::Stable | ReleaseChannel::Preview => env!("ZED_PKG_VERSION"),
|
||||||
|
ReleaseChannel::Nightly | ReleaseChannel::Dev => {
|
||||||
|
option_env!("ZED_COMMIT_SHA").unwrap_or("missing-zed-commit-sha")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
fn init_logging_proxy() {
|
fn init_logging_proxy() {
|
||||||
env_logger::builder()
|
env_logger::builder()
|
||||||
.format(|buf, record| {
|
.format(|buf, record| {
|
||||||
|
@ -113,7 +120,6 @@ fn init_logging_server(log_file_path: PathBuf) -> Result<Receiver<Vec<u8>>> {
|
||||||
|
|
||||||
fn init_panic_hook(session_id: String) {
|
fn init_panic_hook(session_id: String) {
|
||||||
std::panic::set_hook(Box::new(move |info| {
|
std::panic::set_hook(Box::new(move |info| {
|
||||||
crashes::handle_panic();
|
|
||||||
let payload = info
|
let payload = info
|
||||||
.payload()
|
.payload()
|
||||||
.downcast_ref::<&str>()
|
.downcast_ref::<&str>()
|
||||||
|
@ -121,6 +127,8 @@ fn init_panic_hook(session_id: String) {
|
||||||
.or_else(|| info.payload().downcast_ref::<String>().cloned())
|
.or_else(|| info.payload().downcast_ref::<String>().cloned())
|
||||||
.unwrap_or_else(|| "Box<Any>".to_string());
|
.unwrap_or_else(|| "Box<Any>".to_string());
|
||||||
|
|
||||||
|
crashes::handle_panic(payload.clone(), info.location());
|
||||||
|
|
||||||
let backtrace = backtrace::Backtrace::new();
|
let backtrace = backtrace::Backtrace::new();
|
||||||
let mut backtrace = backtrace
|
let mut backtrace = backtrace
|
||||||
.frames()
|
.frames()
|
||||||
|
@ -150,14 +158,6 @@ fn init_panic_hook(session_id: String) {
|
||||||
(&backtrace).join("\n")
|
(&backtrace).join("\n")
|
||||||
);
|
);
|
||||||
|
|
||||||
let release_channel = *RELEASE_CHANNEL;
|
|
||||||
let version = match release_channel {
|
|
||||||
ReleaseChannel::Stable | ReleaseChannel::Preview => env!("ZED_PKG_VERSION"),
|
|
||||||
ReleaseChannel::Nightly | ReleaseChannel::Dev => {
|
|
||||||
option_env!("ZED_COMMIT_SHA").unwrap_or("missing-zed-commit-sha")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let panic_data = telemetry_events::Panic {
|
let panic_data = telemetry_events::Panic {
|
||||||
thread: thread_name.into(),
|
thread: thread_name.into(),
|
||||||
payload: payload.clone(),
|
payload: payload.clone(),
|
||||||
|
@ -165,9 +165,9 @@ fn init_panic_hook(session_id: String) {
|
||||||
file: location.file().into(),
|
file: location.file().into(),
|
||||||
line: location.line(),
|
line: location.line(),
|
||||||
}),
|
}),
|
||||||
app_version: format!("remote-server-{version}"),
|
app_version: format!("remote-server-{}", *VERSION),
|
||||||
app_commit_sha: option_env!("ZED_COMMIT_SHA").map(|sha| sha.into()),
|
app_commit_sha: option_env!("ZED_COMMIT_SHA").map(|sha| sha.into()),
|
||||||
release_channel: release_channel.dev_name().into(),
|
release_channel: RELEASE_CHANNEL.dev_name().into(),
|
||||||
target: env!("TARGET").to_owned().into(),
|
target: env!("TARGET").to_owned().into(),
|
||||||
os_name: telemetry::os_name(),
|
os_name: telemetry::os_name(),
|
||||||
os_version: Some(telemetry::os_version()),
|
os_version: Some(telemetry::os_version()),
|
||||||
|
@ -204,8 +204,8 @@ fn handle_crash_files_requests(project: &Entity<HeadlessProject>, client: &Arc<C
|
||||||
client.add_request_handler(
|
client.add_request_handler(
|
||||||
project.downgrade(),
|
project.downgrade(),
|
||||||
|_, _: TypedEnvelope<proto::GetCrashFiles>, _cx| async move {
|
|_, _: TypedEnvelope<proto::GetCrashFiles>, _cx| async move {
|
||||||
|
let mut legacy_panics = Vec::new();
|
||||||
let mut crashes = Vec::new();
|
let mut crashes = Vec::new();
|
||||||
let mut minidumps_by_session_id = HashMap::new();
|
|
||||||
let mut children = smol::fs::read_dir(paths::logs_dir()).await?;
|
let mut children = smol::fs::read_dir(paths::logs_dir()).await?;
|
||||||
while let Some(child) = children.next().await {
|
while let Some(child) = children.next().await {
|
||||||
let child = child?;
|
let child = child?;
|
||||||
|
@ -227,41 +227,31 @@ fn handle_crash_files_requests(project: &Entity<HeadlessProject>, client: &Arc<C
|
||||||
.await
|
.await
|
||||||
.context("error reading panic file")?;
|
.context("error reading panic file")?;
|
||||||
|
|
||||||
crashes.push(proto::CrashReport {
|
legacy_panics.push(file_contents);
|
||||||
panic_contents: Some(file_contents),
|
smol::fs::remove_file(&child_path)
|
||||||
minidump_contents: None,
|
.await
|
||||||
});
|
.context("error removing panic")
|
||||||
|
.log_err();
|
||||||
} else if extension == Some(OsStr::new("dmp")) {
|
} else if extension == Some(OsStr::new("dmp")) {
|
||||||
let session_id = child_path.file_stem().unwrap().to_string_lossy();
|
let mut json_path = child_path.clone();
|
||||||
minidumps_by_session_id
|
json_path.set_extension("json");
|
||||||
.insert(session_id.to_string(), smol::fs::read(&child_path).await?);
|
if let Ok(json_content) = smol::fs::read_to_string(&json_path).await {
|
||||||
}
|
crashes.push(CrashReport {
|
||||||
|
metadata: json_content,
|
||||||
// We've done what we can, delete the file
|
minidump_contents: smol::fs::read(&child_path).await?,
|
||||||
smol::fs::remove_file(&child_path)
|
});
|
||||||
.await
|
smol::fs::remove_file(&child_path).await.log_err();
|
||||||
.context("error removing panic")
|
smol::fs::remove_file(&json_path).await.log_err();
|
||||||
.log_err();
|
} else {
|
||||||
}
|
log::error!("Couldn't find json metadata for crash: {child_path:?}");
|
||||||
|
}
|
||||||
for crash in &mut crashes {
|
|
||||||
let panic: telemetry_events::Panic =
|
|
||||||
serde_json::from_str(crash.panic_contents.as_ref().unwrap())?;
|
|
||||||
if let dump @ Some(_) = minidumps_by_session_id.remove(&panic.session_id) {
|
|
||||||
crash.minidump_contents = dump;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
crashes.extend(
|
anyhow::Ok(proto::GetCrashFilesResponse {
|
||||||
minidumps_by_session_id
|
crashes,
|
||||||
.into_values()
|
legacy_panics,
|
||||||
.map(|dmp| CrashReport {
|
})
|
||||||
panic_contents: None,
|
|
||||||
minidump_contents: Some(dmp),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
anyhow::Ok(proto::GetCrashFilesResponse { crashes })
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -442,7 +432,12 @@ pub fn execute_run(
|
||||||
let app = gpui::Application::headless();
|
let app = gpui::Application::headless();
|
||||||
let id = std::process::id().to_string();
|
let id = std::process::id().to_string();
|
||||||
app.background_executor()
|
app.background_executor()
|
||||||
.spawn(crashes::init(id.clone()))
|
.spawn(crashes::init(crashes::InitCrashHandler {
|
||||||
|
session_id: id.clone(),
|
||||||
|
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();
|
.detach();
|
||||||
init_panic_hook(id);
|
init_panic_hook(id);
|
||||||
let log_rx = init_logging_server(log_file)?;
|
let log_rx = init_logging_server(log_file)?;
|
||||||
|
@ -569,7 +564,13 @@ pub fn execute_proxy(identifier: String, is_reconnecting: bool) -> Result<()> {
|
||||||
let server_paths = ServerPaths::new(&identifier)?;
|
let server_paths = ServerPaths::new(&identifier)?;
|
||||||
|
|
||||||
let id = std::process::id().to_string();
|
let id = std::process::id().to_string();
|
||||||
smol::spawn(crashes::init(id.clone())).detach();
|
smol::spawn(crashes::init(crashes::InitCrashHandler {
|
||||||
|
session_id: id.clone(),
|
||||||
|
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);
|
init_panic_hook(id);
|
||||||
|
|
||||||
log::info!("starting proxy process. PID: {}", std::process::id());
|
log::info!("starting proxy process. PID: {}", std::process::id());
|
||||||
|
|
|
@ -8,6 +8,7 @@ use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
|
||||||
use client::{Client, ProxySettings, UserStore, parse_zed_link};
|
use client::{Client, ProxySettings, UserStore, parse_zed_link};
|
||||||
use collab_ui::channel_view::ChannelView;
|
use collab_ui::channel_view::ChannelView;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
use crashes::InitCrashHandler;
|
||||||
use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
|
use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use extension::ExtensionHostProxy;
|
use extension::ExtensionHostProxy;
|
||||||
|
@ -269,7 +270,15 @@ pub fn main() {
|
||||||
let session = app.background_executor().block(Session::new());
|
let session = app.background_executor().block(Session::new());
|
||||||
|
|
||||||
app.background_executor()
|
app.background_executor()
|
||||||
.spawn(crashes::init(session_id.clone()))
|
.spawn(crashes::init(InitCrashHandler {
|
||||||
|
session_id: session_id.clone(),
|
||||||
|
zed_version: app_version.to_string(),
|
||||||
|
release_channel: release_channel::RELEASE_CHANNEL_NAME.clone(),
|
||||||
|
commit_sha: app_commit_sha
|
||||||
|
.as_ref()
|
||||||
|
.map(|sha| sha.full())
|
||||||
|
.unwrap_or_else(|| "no sha".to_owned()),
|
||||||
|
}))
|
||||||
.detach();
|
.detach();
|
||||||
reliability::init_panic_hook(
|
reliability::init_panic_hook(
|
||||||
app_version,
|
app_version,
|
||||||
|
|
|
@ -12,6 +12,7 @@ use gpui::{App, AppContext as _, SemanticVersion};
|
||||||
use http_client::{self, HttpClient, HttpClientWithUrl, HttpRequestExt, Method};
|
use http_client::{self, HttpClient, HttpClientWithUrl, HttpRequestExt, Method};
|
||||||
use paths::{crashes_dir, crashes_retired_dir};
|
use paths::{crashes_dir, crashes_retired_dir};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
|
use proto::{CrashReport, GetCrashFilesResponse};
|
||||||
use release_channel::{AppCommitSha, RELEASE_CHANNEL, ReleaseChannel};
|
use release_channel::{AppCommitSha, RELEASE_CHANNEL, ReleaseChannel};
|
||||||
use reqwest::multipart::{Form, Part};
|
use reqwest::multipart::{Form, Part};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
@ -51,10 +52,6 @@ pub fn init_panic_hook(
|
||||||
thread::yield_now();
|
thread::yield_now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
crashes::handle_panic();
|
|
||||||
|
|
||||||
let thread = thread::current();
|
|
||||||
let thread_name = thread.name().unwrap_or("<unnamed>");
|
|
||||||
|
|
||||||
let payload = info
|
let payload = info
|
||||||
.payload()
|
.payload()
|
||||||
|
@ -63,6 +60,11 @@ pub fn init_panic_hook(
|
||||||
.or_else(|| info.payload().downcast_ref::<String>().cloned())
|
.or_else(|| info.payload().downcast_ref::<String>().cloned())
|
||||||
.unwrap_or_else(|| "Box<Any>".to_string());
|
.unwrap_or_else(|| "Box<Any>".to_string());
|
||||||
|
|
||||||
|
crashes::handle_panic(payload.clone(), info.location());
|
||||||
|
|
||||||
|
let thread = thread::current();
|
||||||
|
let thread_name = thread.name().unwrap_or("<unnamed>");
|
||||||
|
|
||||||
if *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
|
if *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
|
||||||
let location = info.location().unwrap();
|
let location = info.location().unwrap();
|
||||||
let backtrace = Backtrace::new();
|
let backtrace = Backtrace::new();
|
||||||
|
@ -214,45 +216,53 @@ pub fn init(
|
||||||
let installation_id = installation_id.clone();
|
let installation_id = installation_id.clone();
|
||||||
let system_id = system_id.clone();
|
let system_id = system_id.clone();
|
||||||
|
|
||||||
if let Some(ssh_client) = project.ssh_client() {
|
let Some(ssh_client) = project.ssh_client() else {
|
||||||
ssh_client.update(cx, |client, cx| {
|
return;
|
||||||
if TelemetrySettings::get_global(cx).diagnostics {
|
};
|
||||||
let request = client.proto_client().request(proto::GetCrashFiles {});
|
ssh_client.update(cx, |client, cx| {
|
||||||
cx.background_spawn(async move {
|
if !TelemetrySettings::get_global(cx).diagnostics {
|
||||||
let crash_files = request.await?;
|
return;
|
||||||
for crash in crash_files.crashes {
|
}
|
||||||
let mut panic: Option<Panic> = crash
|
let request = client.proto_client().request(proto::GetCrashFiles {});
|
||||||
.panic_contents
|
cx.background_spawn(async move {
|
||||||
.and_then(|s| serde_json::from_str(&s).log_err());
|
let GetCrashFilesResponse {
|
||||||
|
legacy_panics,
|
||||||
|
crashes,
|
||||||
|
} = request.await?;
|
||||||
|
|
||||||
if let Some(panic) = panic.as_mut() {
|
for panic in legacy_panics {
|
||||||
panic.session_id = session_id.clone();
|
if let Some(mut panic) = serde_json::from_str::<Panic>(&panic).log_err() {
|
||||||
panic.system_id = system_id.clone();
|
panic.session_id = session_id.clone();
|
||||||
panic.installation_id = installation_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?;
|
||||||
if let Some(minidump) = crash.minidump_contents {
|
}
|
||||||
upload_minidump(
|
|
||||||
http_client.clone(),
|
|
||||||
minidump.clone(),
|
|
||||||
panic.as_ref(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(panic) = panic {
|
|
||||||
upload_panic(&http_client, &panic_report_url, panic, &mut None)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
anyhow::Ok(())
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let Some(endpoint) = MINIDUMP_ENDPOINT.as_ref() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
for CrashReport {
|
||||||
|
metadata,
|
||||||
|
minidump_contents,
|
||||||
|
} in crashes
|
||||||
|
{
|
||||||
|
if let Some(metadata) = serde_json::from_str(&metadata).log_err() {
|
||||||
|
upload_minidump(
|
||||||
|
http_client.clone(),
|
||||||
|
endpoint,
|
||||||
|
minidump_contents,
|
||||||
|
&metadata,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
}
|
.detach_and_log_err(cx);
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
@ -466,16 +476,18 @@ fn upload_panics_and_crashes(
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) {
|
) {
|
||||||
let telemetry_settings = *client::TelemetrySettings::get_global(cx);
|
if !client::TelemetrySettings::get_global(cx).diagnostics {
|
||||||
|
return;
|
||||||
|
}
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let most_recent_panic =
|
upload_previous_minidumps(http.clone()).await.warn_on_err();
|
||||||
upload_previous_panics(http.clone(), &panic_report_url, telemetry_settings)
|
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, telemetry_settings)
|
|
||||||
.await
|
.await
|
||||||
.log_err()
|
.log_err()
|
||||||
|
.flatten();
|
||||||
|
upload_previous_crashes(http, most_recent_panic, installation_id)
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
})
|
})
|
||||||
.detach()
|
.detach()
|
||||||
}
|
}
|
||||||
|
@ -484,7 +496,6 @@ fn upload_panics_and_crashes(
|
||||||
async fn upload_previous_panics(
|
async fn upload_previous_panics(
|
||||||
http: Arc<HttpClientWithUrl>,
|
http: Arc<HttpClientWithUrl>,
|
||||||
panic_report_url: &Url,
|
panic_report_url: &Url,
|
||||||
telemetry_settings: client::TelemetrySettings,
|
|
||||||
) -> anyhow::Result<Option<(i64, String)>> {
|
) -> anyhow::Result<Option<(i64, String)>> {
|
||||||
let mut children = smol::fs::read_dir(paths::logs_dir()).await?;
|
let mut children = smol::fs::read_dir(paths::logs_dir()).await?;
|
||||||
|
|
||||||
|
@ -507,58 +518,41 @@ async fn upload_previous_panics(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if telemetry_settings.diagnostics {
|
let panic_file_content = smol::fs::read_to_string(&child_path)
|
||||||
let panic_file_content = smol::fs::read_to_string(&child_path)
|
.await
|
||||||
.await
|
.context("error reading panic file")?;
|
||||||
.context("error reading panic file")?;
|
|
||||||
|
|
||||||
let panic: Option<Panic> = serde_json::from_str(&panic_file_content)
|
let panic: Option<Panic> = serde_json::from_str(&panic_file_content)
|
||||||
.log_err()
|
.log_err()
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
panic_file_content
|
panic_file_content
|
||||||
.lines()
|
.lines()
|
||||||
.next()
|
.next()
|
||||||
.and_then(|line| serde_json::from_str(line).ok())
|
.and_then(|line| serde_json::from_str(line).ok())
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
log::error!("failed to deserialize panic file {:?}", panic_file_content);
|
log::error!("failed to deserialize panic file {:?}", panic_file_content);
|
||||||
None
|
None
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(panic) = panic {
|
if let Some(panic) = panic
|
||||||
let minidump_path = paths::logs_dir()
|
&& upload_panic(&http, &panic_report_url, panic, &mut most_recent_panic).await?
|
||||||
.join(&panic.session_id)
|
{
|
||||||
.with_extension("dmp");
|
// We've done what we can, delete the file
|
||||||
if minidump_path.exists() {
|
fs::remove_file(child_path)
|
||||||
let minidump = smol::fs::read(&minidump_path)
|
.context("error removing panic")
|
||||||
.await
|
.log_err();
|
||||||
.context("Failed to read minidump")?;
|
|
||||||
if upload_minidump(http.clone(), minidump, Some(&panic))
|
|
||||||
.await
|
|
||||||
.log_err()
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
fs::remove_file(minidump_path).ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !upload_panic(&http, &panic_report_url, panic, &mut most_recent_panic).await? {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We've done what we can, delete the file
|
|
||||||
fs::remove_file(child_path)
|
|
||||||
.context("error removing panic")
|
|
||||||
.log_err();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if MINIDUMP_ENDPOINT.is_none() {
|
Ok(most_recent_panic)
|
||||||
return Ok(most_recent_panic);
|
}
|
||||||
}
|
|
||||||
|
pub async fn upload_previous_minidumps(http: Arc<HttpClientWithUrl>) -> anyhow::Result<()> {
|
||||||
|
let Some(minidump_endpoint) = MINIDUMP_ENDPOINT.as_ref() else {
|
||||||
|
return Err(anyhow::anyhow!("Minidump endpoint not set"));
|
||||||
|
};
|
||||||
|
|
||||||
// loop back over the directory again to upload any minidumps that are missing panics
|
|
||||||
let mut children = smol::fs::read_dir(paths::logs_dir()).await?;
|
let mut children = smol::fs::read_dir(paths::logs_dir()).await?;
|
||||||
while let Some(child) = children.next().await {
|
while let Some(child) = children.next().await {
|
||||||
let child = child?;
|
let child = child?;
|
||||||
|
@ -566,33 +560,35 @@ async fn upload_previous_panics(
|
||||||
if child_path.extension() != Some(OsStr::new("dmp")) {
|
if child_path.extension() != Some(OsStr::new("dmp")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if upload_minidump(
|
let mut json_path = child_path.clone();
|
||||||
http.clone(),
|
json_path.set_extension("json");
|
||||||
smol::fs::read(&child_path)
|
if let Ok(metadata) = serde_json::from_slice(&smol::fs::read(&json_path).await?) {
|
||||||
.await
|
if upload_minidump(
|
||||||
.context("Failed to read minidump")?,
|
http.clone(),
|
||||||
None,
|
&minidump_endpoint,
|
||||||
)
|
smol::fs::read(&child_path)
|
||||||
.await
|
.await
|
||||||
.log_err()
|
.context("Failed to read minidump")?,
|
||||||
.is_some()
|
&metadata,
|
||||||
{
|
)
|
||||||
fs::remove_file(child_path).ok();
|
.await
|
||||||
|
.log_err()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
fs::remove_file(child_path).ok();
|
||||||
|
fs::remove_file(json_path).ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
Ok(most_recent_panic)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn upload_minidump(
|
async fn upload_minidump(
|
||||||
http: Arc<HttpClientWithUrl>,
|
http: Arc<HttpClientWithUrl>,
|
||||||
|
endpoint: &str,
|
||||||
minidump: Vec<u8>,
|
minidump: Vec<u8>,
|
||||||
panic: Option<&Panic>,
|
metadata: &crashes::CrashInfo,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let minidump_endpoint = MINIDUMP_ENDPOINT
|
|
||||||
.to_owned()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Minidump endpoint not set"))?;
|
|
||||||
|
|
||||||
let mut form = Form::new()
|
let mut form = Form::new()
|
||||||
.part(
|
.part(
|
||||||
"upload_file_minidump",
|
"upload_file_minidump",
|
||||||
|
@ -600,38 +596,22 @@ async fn upload_minidump(
|
||||||
.file_name("minidump.dmp")
|
.file_name("minidump.dmp")
|
||||||
.mime_str("application/octet-stream")?,
|
.mime_str("application/octet-stream")?,
|
||||||
)
|
)
|
||||||
|
.text(
|
||||||
|
"sentry[tags][channel]",
|
||||||
|
metadata.init.release_channel.clone(),
|
||||||
|
)
|
||||||
|
.text("sentry[tags][version]", metadata.init.zed_version.clone())
|
||||||
|
.text("sentry[release]", metadata.init.commit_sha.clone())
|
||||||
.text("platform", "rust");
|
.text("platform", "rust");
|
||||||
if let Some(panic) = panic {
|
if let Some(panic_info) = metadata.panic.as_ref() {
|
||||||
form = form
|
form = form.text("sentry[logentry][formatted]", panic_info.message.clone());
|
||||||
.text("sentry[tags][channel]", panic.release_channel.clone())
|
form = form.text("span", panic_info.span.clone());
|
||||||
.text("sentry[tags][version]", panic.app_version.clone())
|
|
||||||
.text("sentry[context][os][name]", panic.os_name.clone())
|
|
||||||
.text(
|
|
||||||
"sentry[context][device][architecture]",
|
|
||||||
panic.architecture.clone(),
|
|
||||||
)
|
|
||||||
.text("sentry[logentry][formatted]", panic.payload.clone());
|
|
||||||
|
|
||||||
if let Some(sha) = panic.app_commit_sha.clone() {
|
|
||||||
form = form.text("sentry[release]", sha)
|
|
||||||
} else {
|
|
||||||
form = form.text(
|
|
||||||
"sentry[release]",
|
|
||||||
format!("{}-{}", panic.release_channel, panic.app_version),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if let Some(v) = panic.os_version.clone() {
|
|
||||||
form = form.text("sentry[context][os][release]", v);
|
|
||||||
}
|
|
||||||
if let Some(location) = panic.location_data.as_ref() {
|
|
||||||
form = form.text("span", format!("{}:{}", location.file, location.line))
|
|
||||||
}
|
|
||||||
// TODO: add gpu-context, feature-flag-context, and more of device-context like gpu
|
// TODO: add gpu-context, feature-flag-context, and more of device-context like gpu
|
||||||
// name, screen resolution, available ram, device model, etc
|
// name, screen resolution, available ram, device model, etc
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut response_text = String::new();
|
let mut response_text = String::new();
|
||||||
let mut response = http.send_multipart_form(&minidump_endpoint, form).await?;
|
let mut response = http.send_multipart_form(endpoint, form).await?;
|
||||||
response
|
response
|
||||||
.body_mut()
|
.body_mut()
|
||||||
.read_to_string(&mut response_text)
|
.read_to_string(&mut response_text)
|
||||||
|
@ -681,11 +661,7 @@ async fn upload_previous_crashes(
|
||||||
http: Arc<HttpClientWithUrl>,
|
http: Arc<HttpClientWithUrl>,
|
||||||
most_recent_panic: Option<(i64, String)>,
|
most_recent_panic: Option<(i64, String)>,
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
telemetry_settings: client::TelemetrySettings,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if !telemetry_settings.diagnostics {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let last_uploaded = KEY_VALUE_STORE
|
let last_uploaded = KEY_VALUE_STORE
|
||||||
.read_kvp(LAST_CRASH_UPLOADED)?
|
.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.
|
.unwrap_or("zed-2024-01-17-221900.ips".to_string()); // don't upload old crash reports from before we had this.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue