mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
Upload panics via collab instead of zed.dev (#11932)
Release Notes: - N/A
This commit is contained in:
parent
decfbc69a5
commit
44105e1f80
@ -26,6 +26,7 @@ pub fn router() -> Router {
|
||||
Router::new()
|
||||
.route("/telemetry/events", post(post_events))
|
||||
.route("/telemetry/crashes", post(post_crash))
|
||||
.route("/telemetry/panics", post(post_panic))
|
||||
.route("/telemetry/hangs", post(post_hang))
|
||||
}
|
||||
|
||||
@ -325,6 +326,95 @@ pub async fn post_hang(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn post_panic(
|
||||
Extension(app): Extension<Arc<AppState>>,
|
||||
TypedHeader(ZedChecksumHeader(checksum)): TypedHeader<ZedChecksumHeader>,
|
||||
body: Bytes,
|
||||
) -> Result<()> {
|
||||
let Some(expected) = calculate_json_checksum(app.clone(), &body) else {
|
||||
return Err(Error::Http(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"events not enabled".into(),
|
||||
))?;
|
||||
};
|
||||
|
||||
if checksum != expected {
|
||||
return Err(Error::Http(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"invalid checksum".into(),
|
||||
))?;
|
||||
}
|
||||
|
||||
let report: telemetry_events::PanicRequest = serde_json::from_slice(&body)
|
||||
.map_err(|_| Error::Http(StatusCode::BAD_REQUEST, "invalid json".into()))?;
|
||||
let panic = report.panic;
|
||||
|
||||
tracing::error!(
|
||||
service = "client",
|
||||
version = %panic.app_version,
|
||||
os_name = %panic.os_name,
|
||||
os_version = %panic.os_version.clone().unwrap_or_default(),
|
||||
installation_id = %panic.installation_id.unwrap_or_default(),
|
||||
description = %panic.payload,
|
||||
backtrace = %panic.backtrace.join("\n"),
|
||||
"panic report");
|
||||
|
||||
let backtrace = if panic.backtrace.len() > 25 {
|
||||
let total = panic.backtrace.len();
|
||||
format!(
|
||||
"{}\n and {} more",
|
||||
panic
|
||||
.backtrace
|
||||
.iter()
|
||||
.take(20)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
total - 20
|
||||
)
|
||||
} else {
|
||||
panic.backtrace.join("\n")
|
||||
};
|
||||
let backtrace_with_summary = panic.payload + "\n" + &backtrace;
|
||||
|
||||
if let Some(slack_panics_webhook) = app.config.slack_panics_webhook.clone() {
|
||||
let payload = slack::WebhookBody::new(|w| {
|
||||
w.add_section(|s| s.text(slack::Text::markdown("Panic request".to_string())))
|
||||
.add_section(|s| {
|
||||
s.add_field(slack::Text::markdown(format!(
|
||||
"*Version:*\n {} ",
|
||||
panic.app_version
|
||||
)))
|
||||
.add_field({
|
||||
slack::Text::markdown(format!(
|
||||
"*OS:*\n{} {}",
|
||||
panic.os_name,
|
||||
panic.os_version.unwrap_or_default()
|
||||
))
|
||||
})
|
||||
})
|
||||
.add_rich_text(|r| r.add_preformatted(|p| p.add_text(backtrace_with_summary)))
|
||||
});
|
||||
let payload_json = serde_json::to_string(&payload).map_err(|err| {
|
||||
log::error!("Failed to serialize payload to JSON: {err}");
|
||||
Error::Internal(anyhow!(err))
|
||||
})?;
|
||||
|
||||
reqwest::Client::new()
|
||||
.post(slack_panics_webhook)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(payload_json)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
log::error!("Failed to send payload to Slack: {err}");
|
||||
Error::Internal(anyhow!(err))
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn post_events(
|
||||
Extension(app): Extension<Arc<AppState>>,
|
||||
TypedHeader(ZedChecksumHeader(checksum)): TypedHeader<ZedChecksumHeader>,
|
||||
|
@ -155,3 +155,32 @@ pub struct HangReport {
|
||||
pub architecture: String,
|
||||
pub installation_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct LocationData {
|
||||
pub file: String,
|
||||
pub line: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Panic {
|
||||
pub thread: String,
|
||||
pub payload: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub location_data: Option<LocationData>,
|
||||
pub backtrace: Vec<String>,
|
||||
pub app_version: String,
|
||||
pub release_channel: String,
|
||||
pub os_name: String,
|
||||
pub os_version: Option<String>,
|
||||
pub architecture: String,
|
||||
pub panicked_on: i64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub installation_id: Option<String>,
|
||||
pub session_id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct PanicRequest {
|
||||
pub panic: Panic,
|
||||
}
|
||||
|
@ -3,13 +3,13 @@ use backtrace::{self, Backtrace};
|
||||
use chrono::Utc;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use gpui::{App, AppContext, SemanticVersion};
|
||||
use http::Method;
|
||||
use isahc::config::Configurable;
|
||||
|
||||
use http::{self, HttpClient, HttpClientWithUrl};
|
||||
use paths::{CRASHES_DIR, CRASHES_RETIRED_DIR};
|
||||
use release_channel::ReleaseChannel;
|
||||
use release_channel::RELEASE_CHANNEL;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use smol::stream::StreamExt;
|
||||
use std::{
|
||||
@ -18,39 +18,12 @@ use std::{
|
||||
sync::{atomic::Ordering, Arc},
|
||||
};
|
||||
use std::{io::Write, panic, sync::atomic::AtomicU32, thread};
|
||||
use telemetry_events::LocationData;
|
||||
use telemetry_events::Panic;
|
||||
use telemetry_events::PanicRequest;
|
||||
use util::{paths, ResultExt};
|
||||
|
||||
use crate::stdout_is_a_pty;
|
||||
|
||||
#[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>,
|
||||
app_version: String,
|
||||
release_channel: String,
|
||||
os_name: String,
|
||||
os_version: Option<String>,
|
||||
architecture: String,
|
||||
panicked_on: i64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
installation_id: Option<String>,
|
||||
session_id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PanicRequest {
|
||||
panic: Panic,
|
||||
}
|
||||
|
||||
static PANIC_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
pub fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: String) {
|
||||
@ -119,7 +92,7 @@ pub fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: S
|
||||
backtrace.drain(0..=ix);
|
||||
}
|
||||
|
||||
let panic_data = Panic {
|
||||
let panic_data = telemetry_events::Panic {
|
||||
thread: thread_name.into(),
|
||||
payload,
|
||||
location_data: info.location().map(|location| LocationData {
|
||||
@ -397,7 +370,7 @@ async fn upload_previous_panics(
|
||||
http: Arc<HttpClientWithUrl>,
|
||||
telemetry_settings: client::TelemetrySettings,
|
||||
) -> Result<Option<(i64, String)>> {
|
||||
let panic_report_url = http.build_url("/api/panic");
|
||||
let panic_report_url = http.build_zed_api_url("/telemetry/panics", &[])?;
|
||||
let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?;
|
||||
|
||||
let mut most_recent_panic = None;
|
||||
@ -440,12 +413,21 @@ 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 json_bytes = serde_json::to_vec(&PanicRequest { panic }).unwrap();
|
||||
|
||||
let Some(checksum) = client::telemetry::calculate_json_checksum(&json_bytes) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Ok(request) = http::Request::builder()
|
||||
.method(Method::POST)
|
||||
.uri(panic_report_url.as_ref())
|
||||
.header("x-zed-checksum", checksum)
|
||||
.body(json_bytes.into())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let request = http::Request::post(&panic_report_url)
|
||||
.redirect_policy(isahc::config::RedirectPolicy::Follow)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(body.into())?;
|
||||
let response = http.send(request).await.context("error sending panic")?;
|
||||
if !response.status().is_success() {
|
||||
log::error!("Error uploading panic to server: {}", response.status());
|
||||
|
Loading…
Reference in New Issue
Block a user