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
|
@ -8,6 +8,7 @@ use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
|
|||
use client::{Client, ProxySettings, UserStore, parse_zed_link};
|
||||
use collab_ui::channel_view::ChannelView;
|
||||
use collections::HashMap;
|
||||
use crashes::InitCrashHandler;
|
||||
use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
|
||||
use editor::Editor;
|
||||
use extension::ExtensionHostProxy;
|
||||
|
@ -269,7 +270,15 @@ pub fn main() {
|
|||
let session = app.background_executor().block(Session::new());
|
||||
|
||||
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();
|
||||
reliability::init_panic_hook(
|
||||
app_version,
|
||||
|
|
|
@ -12,6 +12,7 @@ use gpui::{App, AppContext as _, SemanticVersion};
|
|||
use http_client::{self, HttpClient, HttpClientWithUrl, HttpRequestExt, Method};
|
||||
use paths::{crashes_dir, crashes_retired_dir};
|
||||
use project::Project;
|
||||
use proto::{CrashReport, GetCrashFilesResponse};
|
||||
use release_channel::{AppCommitSha, RELEASE_CHANNEL, ReleaseChannel};
|
||||
use reqwest::multipart::{Form, Part};
|
||||
use settings::Settings;
|
||||
|
@ -51,10 +52,6 @@ pub fn init_panic_hook(
|
|||
thread::yield_now();
|
||||
}
|
||||
}
|
||||
crashes::handle_panic();
|
||||
|
||||
let thread = thread::current();
|
||||
let thread_name = thread.name().unwrap_or("<unnamed>");
|
||||
|
||||
let payload = info
|
||||
.payload()
|
||||
|
@ -63,6 +60,11 @@ pub fn init_panic_hook(
|
|||
.or_else(|| info.payload().downcast_ref::<String>().cloned())
|
||||
.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 {
|
||||
let location = info.location().unwrap();
|
||||
let backtrace = Backtrace::new();
|
||||
|
@ -214,45 +216,53 @@ pub fn init(
|
|||
let installation_id = installation_id.clone();
|
||||
let system_id = system_id.clone();
|
||||
|
||||
if let Some(ssh_client) = project.ssh_client() {
|
||||
ssh_client.update(cx, |client, cx| {
|
||||
if TelemetrySettings::get_global(cx).diagnostics {
|
||||
let request = client.proto_client().request(proto::GetCrashFiles {});
|
||||
cx.background_spawn(async move {
|
||||
let crash_files = request.await?;
|
||||
for crash in crash_files.crashes {
|
||||
let mut panic: Option<Panic> = crash
|
||||
.panic_contents
|
||||
.and_then(|s| serde_json::from_str(&s).log_err());
|
||||
let Some(ssh_client) = project.ssh_client() else {
|
||||
return;
|
||||
};
|
||||
ssh_client.update(cx, |client, cx| {
|
||||
if !TelemetrySettings::get_global(cx).diagnostics {
|
||||
return;
|
||||
}
|
||||
let request = client.proto_client().request(proto::GetCrashFiles {});
|
||||
cx.background_spawn(async move {
|
||||
let GetCrashFilesResponse {
|
||||
legacy_panics,
|
||||
crashes,
|
||||
} = request.await?;
|
||||
|
||||
if let Some(panic) = panic.as_mut() {
|
||||
panic.session_id = session_id.clone();
|
||||
panic.system_id = system_id.clone();
|
||||
panic.installation_id = installation_id.clone();
|
||||
}
|
||||
|
||||
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);
|
||||
for panic in legacy_panics {
|
||||
if let Some(mut panic) = serde_json::from_str::<Panic>(&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 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();
|
||||
}
|
||||
|
@ -466,16 +476,18 @@ fn upload_panics_and_crashes(
|
|||
installation_id: Option<String>,
|
||||
cx: &App,
|
||||
) {
|
||||
let telemetry_settings = *client::TelemetrySettings::get_global(cx);
|
||||
if !client::TelemetrySettings::get_global(cx).diagnostics {
|
||||
return;
|
||||
}
|
||||
cx.background_spawn(async move {
|
||||
let most_recent_panic =
|
||||
upload_previous_panics(http.clone(), &panic_report_url, telemetry_settings)
|
||||
.await
|
||||
.log_err()
|
||||
.flatten();
|
||||
upload_previous_crashes(http, most_recent_panic, installation_id, telemetry_settings)
|
||||
upload_previous_minidumps(http.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()
|
||||
}
|
||||
|
@ -484,7 +496,6 @@ fn upload_panics_and_crashes(
|
|||
async fn upload_previous_panics(
|
||||
http: Arc<HttpClientWithUrl>,
|
||||
panic_report_url: &Url,
|
||||
telemetry_settings: client::TelemetrySettings,
|
||||
) -> anyhow::Result<Option<(i64, String)>> {
|
||||
let mut children = smol::fs::read_dir(paths::logs_dir()).await?;
|
||||
|
||||
|
@ -507,58 +518,41 @@ async fn upload_previous_panics(
|
|||
continue;
|
||||
}
|
||||
|
||||
if telemetry_settings.diagnostics {
|
||||
let panic_file_content = smol::fs::read_to_string(&child_path)
|
||||
.await
|
||||
.context("error reading panic file")?;
|
||||
let panic_file_content = smol::fs::read_to_string(&child_path)
|
||||
.await
|
||||
.context("error reading panic file")?;
|
||||
|
||||
let panic: Option<Panic> = 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
|
||||
});
|
||||
let panic: Option<Panic> = 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 {
|
||||
let minidump_path = paths::logs_dir()
|
||||
.join(&panic.session_id)
|
||||
.with_extension("dmp");
|
||||
if minidump_path.exists() {
|
||||
let minidump = smol::fs::read(&minidump_path)
|
||||
.await
|
||||
.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;
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
// 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() {
|
||||
return Ok(most_recent_panic);
|
||||
}
|
||||
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?;
|
||||
while let Some(child) = children.next().await {
|
||||
let child = child?;
|
||||
|
@ -566,33 +560,35 @@ async fn upload_previous_panics(
|
|||
if child_path.extension() != Some(OsStr::new("dmp")) {
|
||||
continue;
|
||||
}
|
||||
if upload_minidump(
|
||||
http.clone(),
|
||||
smol::fs::read(&child_path)
|
||||
.await
|
||||
.context("Failed to read minidump")?,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.log_err()
|
||||
.is_some()
|
||||
{
|
||||
fs::remove_file(child_path).ok();
|
||||
let mut json_path = child_path.clone();
|
||||
json_path.set_extension("json");
|
||||
if let Ok(metadata) = serde_json::from_slice(&smol::fs::read(&json_path).await?) {
|
||||
if upload_minidump(
|
||||
http.clone(),
|
||||
&minidump_endpoint,
|
||||
smol::fs::read(&child_path)
|
||||
.await
|
||||
.context("Failed to read minidump")?,
|
||||
&metadata,
|
||||
)
|
||||
.await
|
||||
.log_err()
|
||||
.is_some()
|
||||
{
|
||||
fs::remove_file(child_path).ok();
|
||||
fs::remove_file(json_path).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(most_recent_panic)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn upload_minidump(
|
||||
http: Arc<HttpClientWithUrl>,
|
||||
endpoint: &str,
|
||||
minidump: Vec<u8>,
|
||||
panic: Option<&Panic>,
|
||||
metadata: &crashes::CrashInfo,
|
||||
) -> Result<()> {
|
||||
let minidump_endpoint = MINIDUMP_ENDPOINT
|
||||
.to_owned()
|
||||
.ok_or_else(|| anyhow::anyhow!("Minidump endpoint not set"))?;
|
||||
|
||||
let mut form = Form::new()
|
||||
.part(
|
||||
"upload_file_minidump",
|
||||
|
@ -600,38 +596,22 @@ async fn upload_minidump(
|
|||
.file_name("minidump.dmp")
|
||||
.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");
|
||||
if let Some(panic) = panic {
|
||||
form = form
|
||||
.text("sentry[tags][channel]", panic.release_channel.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))
|
||||
}
|
||||
if let Some(panic_info) = metadata.panic.as_ref() {
|
||||
form = form.text("sentry[logentry][formatted]", panic_info.message.clone());
|
||||
form = form.text("span", panic_info.span.clone());
|
||||
// TODO: add gpu-context, feature-flag-context, and more of device-context like gpu
|
||||
// name, screen resolution, available ram, device model, etc
|
||||
}
|
||||
|
||||
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
|
||||
.body_mut()
|
||||
.read_to_string(&mut response_text)
|
||||
|
@ -681,11 +661,7 @@ async fn upload_previous_crashes(
|
|||
http: Arc<HttpClientWithUrl>,
|
||||
most_recent_panic: Option<(i64, String)>,
|
||||
installation_id: Option<String>,
|
||||
telemetry_settings: client::TelemetrySettings,
|
||||
) -> Result<()> {
|
||||
if !telemetry_settings.diagnostics {
|
||||
return Ok(());
|
||||
}
|
||||
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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue