diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 7416f1cc2d..41ed1d2d25 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -681,10 +681,11 @@ fn upload_panics_and_crashes(http: Arc, cx: &mut AppContext) { let telemetry_settings = *client::TelemetrySettings::get_global(cx); cx.background_executor() .spawn(async move { - upload_previous_panics(http.clone(), telemetry_settings) + let most_recent_panic = upload_previous_panics(http.clone(), telemetry_settings) .await - .log_err(); - upload_previous_crashes(http, telemetry_settings) + .log_err() + .flatten(); + upload_previous_crashes(http, most_recent_panic, telemetry_settings) .await .log_err() }) @@ -695,9 +696,12 @@ fn upload_panics_and_crashes(http: Arc, cx: &mut AppContext) { async fn upload_previous_panics( http: Arc, telemetry_settings: client::TelemetrySettings, -) -> Result<()> { +) -> Result> { let panic_report_url = http.zed_url("/api/panic"); 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(); @@ -720,7 +724,7 @@ async fn upload_previous_panics( .await .context("error reading panic file")?; - let panic = serde_json::from_str(&panic_file_content) + let panic: Option = serde_json::from_str(&panic_file_content) .ok() .or_else(|| { panic_file_content @@ -734,6 +738,8 @@ async fn upload_previous_panics( }); if let Some(panic) = panic { + most_recent_panic = Some((panic.panicked_on, panic.payload.clone())); + let body = serde_json::to_string(&PanicRequest { panic }).unwrap(); let request = Request::post(&panic_report_url) @@ -752,7 +758,7 @@ async fn upload_previous_panics( .context("error removing panic") .log_err(); } - Ok::<_, anyhow::Error>(()) + Ok::<_, anyhow::Error>(most_recent_panic) } static LAST_CRASH_UPLOADED: &'static str = "LAST_CRASH_UPLOADED"; @@ -761,6 +767,7 @@ static LAST_CRASH_UPLOADED: &'static str = "LAST_CRASH_UPLOADED"; /// (only if telemetry is enabled) async fn upload_previous_crashes( http: Arc, + most_recent_panic: Option<(i64, String)>, telemetry_settings: client::TelemetrySettings, ) -> Result<()> { if !telemetry_settings.diagnostics { @@ -797,10 +804,17 @@ async fn upload_previous_crashes( .await .context("error reading crash file")?; - let request = Request::post(&crash_report_url) + let mut request = Request::post(&crash_report_url) .redirect_policy(isahc::config::RedirectPolicy::Follow) - .header("Content-Type", "text/plain") - .body(body.into())?; + .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) + } + + let request = request.body(body.into())?; let response = http.send(request).await.context("error sending crash")?; if !response.status().is_success() { diff --git a/docs/src/developing_zed__debugging_crashes.md b/docs/src/developing_zed__debugging_crashes.md new file mode 100644 index 0000000000..085f927d24 --- /dev/null +++ b/docs/src/developing_zed__debugging_crashes.md @@ -0,0 +1,27 @@ +## Crashes + +When an app crashes, macOS creates a `.ips` file in `~/Library/Logs/DiagnosticReports`. You can view these using the built in Console app (`cmd-space Console`) under "Crash Reports". + +If you have enabeld Zed's telemetry these will be uploaded to us when you restart the app. They end up in Datadog, and a [Slack channel (internal only)](https://zed-industries.slack.com/archives/C04S6T1T7TQ). + +These crash reports are generated by the crashing binary, and contain a wealth of information; but they are hard to read for a few reasons: + +- They don't contain source files and line numbers +- The symbols are [mangled](https://doc.rust-lang.org/rustc/symbol-mangling/index.html) +- Inlined functions are elided + +To get a better sense of the backtrace of a crash you can download the `.ips` file locally and run: + +``` +./script/symbolicate ~/path/zed-XXX-XXX.ips +``` + +This will download the correct debug symbols from our public [digital ocean bucket](https://zed-debug-symbols.nyc3.digitaloceanspaces.com), and run [symbolicate](https://crates.io/crates/symbolicate) for you. + +The output contains the source file and line number, and the demangled symbol information for every inlined frame. + +## Panics + +When the app panics at the rust level, Zed creates a file in `~/Library/Logs/Zed` with the text of the panic, and a summary of the backtrace. On boot, if you have telemetry enabled, we upload these panics so we can keep track of them. + +A panic is also considered a crash, and so for most panics we get both the crash report and the panic. diff --git a/script/symbolicate b/script/symbolicate new file mode 100755 index 0000000000..46e89b6f39 --- /dev/null +++ b/script/symbolicate @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +set -eu +if [[ $# -eq 0 ]] || [[ "$1" == "--help" ]]; then + echo "Usage: $(basename $0) " + echo "This script symbolicates the provided .ips file using the appropriate dSYM file from digital ocean" + echo "" + exit 1 +fi + +ips_file=$1; + +version=$(cat $ips_file | head -n 1 | jq -r .app_version) +bundle_id=$(cat $ips_file | head -n 1 | jq -r .bundleID) +cpu_type=$(cat $ips_file | tail -n+2 | jq -r .cpuType) + +which symbolicate >/dev/null || cargo install symbolicate + +arch="x86_64-apple-darwin" +if [[ "$cpu_type" == *ARM-64* ]]; then + arch="aarch64-apple-darwin" +fi + +channel="stable" +if [[ "$bundle_id" == *nightly* ]]; then + channel="nightly" +elif [[ "$bundle_id" == *preview* ]]; then + channel="preview" +fi + +mkdir -p target/dsyms/$channel + +dsym="$channel/Zed-$version-$arch.dwarf" +if [[ ! -f target/dsyms/$dsym ]]; then + echo "Downloading $dsym..." + curl -o target/dsyms/$dsym.gz "https://zed-debug-symbols.nyc3.digitaloceanspaces.com/$channel/Zed-$version-$arch.dwarf.gz" + gunzip target/dsyms/$dsym.gz +fi + +symbolicate $ips_file target/dsyms/$dsym