Merge pull request #2389 from zed-industries/save-panics-as-structured-data

Joseph T. Lyons created

Save panics as structured data

Change summary

crates/zed/src/main.rs | 99 +++++++++++++++++++++++++++----------------
1 file changed, 62 insertions(+), 37 deletions(-)

Detailed changes

crates/zed/src/main.rs 🔗

@@ -21,7 +21,7 @@ use log::LevelFilter;
 use node_runtime::NodeRuntime;
 use parking_lot::Mutex;
 use project::Fs;
-use serde_json::json;
+use serde::{Deserialize, Serialize};
 use settings::{
     self, settings_file::SettingsFile, KeymapFileContent, Settings, SettingsFileContent,
     WorkingDirectory,
@@ -317,6 +317,30 @@ fn init_logger() {
     }
 }
 
+#[derive(Serialize, Deserialize)]
+struct LocationData {
+    file: String,
+    line: u32,
+}
+
+#[derive(Serialize, Deserialize)]
+struct Panic {
+    thread: String,
+    payload: String,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    location_data: Option<LocationData>,
+    backtrace: Vec<String>,
+    // TODO
+    // stripped_backtrace: String,
+}
+
+#[derive(Serialize)]
+struct PanicRequest {
+    panic: Panic,
+    version: String,
+    token: String,
+}
+
 fn init_panic_hook(app_version: String) {
     let is_pty = stdout_is_a_pty();
     panic::set_hook(Box::new(move |info| {
@@ -333,39 +357,38 @@ fn init_panic_hook(app_version: String) {
             },
         };
 
-        let message = match info.location() {
-            Some(location) => {
-                format!(
-                    "thread '{}' panicked at '{}'\n{}:{}\n{:?}",
-                    thread,
-                    payload,
-                    location.file(),
-                    location.line(),
-                    backtrace
-                )
-            }
-            None => format!(
-                "thread '{}' panicked at '{}'\n{:?}",
-                thread, payload, backtrace
-            ),
+        let panic_data = Panic {
+            thread: thread.into(),
+            payload: payload.into(),
+            location_data: info.location().map(|location| LocationData {
+                file: location.file().into(),
+                line: location.line(),
+            }),
+            backtrace: format!("{:?}", backtrace)
+                .split("\n")
+                .map(|line| line.to_string())
+                .collect(),
+            // modified_backtrace: None,
         };
 
-        if is_pty {
-            eprintln!("{}", message);
-            return;
-        }
+        if let Some(panic_data_json) = serde_json::to_string_pretty(&panic_data).log_err() {
+            if is_pty {
+                eprintln!("{}", panic_data_json);
+                return;
+            }
 
-        let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
-        let panic_file_path =
-            paths::LOGS_DIR.join(format!("zed-{}-{}.panic", app_version, timestamp));
-        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 {
-            write!(&mut panic_file, "{}", message).log_err();
-            panic_file.flush().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-{}-{}.panic", app_version, timestamp));
+            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 {
+                write!(&mut panic_file, "{}", panic_data_json).log_err();
+                panic_file.flush().log_err();
+            }
         }
     }));
 }
@@ -402,15 +425,17 @@ fn upload_previous_panics(http: Arc<dyn HttpClient>, cx: &mut AppContext) {
                     };
 
                     if diagnostics_telemetry {
-                        let text = smol::fs::read_to_string(&child_path)
+                        let panic_data_text = smol::fs::read_to_string(&child_path)
                             .await
                             .context("error reading panic file")?;
-                        let body = serde_json::to_string(&json!({
-                            "text": text,
-                            "version": version,
-                            "token": ZED_SECRET_CLIENT_TOKEN,
-                        }))
+
+                        let body = serde_json::to_string(&PanicRequest {
+                            panic: serde_json::from_str(&panic_data_text)?,
+                            version: version.to_string(),
+                            token: ZED_SECRET_CLIENT_TOKEN.into(),
+                        })
                         .unwrap();
+
                         let request = Request::post(&panic_report_url)
                             .redirect_policy(isahc::config::RedirectPolicy::Follow)
                             .header("Content-Type", "application/json")