Detailed changes
@@ -37,9 +37,10 @@ pub struct Telemetry {
struct TelemetryState {
settings: TelemetrySettings,
- metrics_id: Option<Arc<str>>, // Per logged-in user
+ system_id: Option<Arc<str>>, // Per system
installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
session_id: Option<String>, // Per app launch
+ metrics_id: Option<Arc<str>>, // Per logged-in user
release_channel: Option<&'static str>,
architecture: &'static str,
events_queue: Vec<EventWrapper>,
@@ -191,9 +192,10 @@ impl Telemetry {
settings: *TelemetrySettings::get_global(cx),
architecture: env::consts::ARCH,
release_channel,
+ system_id: None,
installation_id: None,
- metrics_id: None,
session_id: None,
+ metrics_id: None,
events_queue: Vec::new(),
flush_events_task: None,
log_file: None,
@@ -283,11 +285,13 @@ impl Telemetry {
pub fn start(
self: &Arc<Self>,
+ system_id: Option<String>,
installation_id: Option<String>,
session_id: String,
cx: &mut AppContext,
) {
let mut state = self.state.lock();
+ state.system_id = system_id.map(|id| id.into());
state.installation_id = installation_id.map(|id| id.into());
state.session_id = Some(session_id);
state.app_version = release_channel::AppVersion::global(cx).to_string();
@@ -637,9 +641,10 @@ impl Telemetry {
let state = this.state.lock();
let request_body = EventRequestBody {
+ system_id: state.system_id.as_deref().map(Into::into),
installation_id: state.installation_id.as_deref().map(Into::into),
- metrics_id: state.metrics_id.as_deref().map(Into::into),
session_id: state.session_id.clone(),
+ metrics_id: state.metrics_id.as_deref().map(Into::into),
is_staff: state.is_staff,
app_version: state.app_version.clone(),
os_name: state.os_name.clone(),
@@ -711,6 +716,7 @@ mod tests {
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
));
let http = FakeHttpClient::with_200_response();
+ let system_id = Some("system_id".to_string());
let installation_id = Some("installation_id".to_string());
let session_id = "session_id".to_string();
@@ -718,7 +724,7 @@ mod tests {
let telemetry = Telemetry::new(clock.clone(), http, cx);
telemetry.state.lock().max_queue_size = 4;
- telemetry.start(installation_id, session_id, cx);
+ telemetry.start(system_id, installation_id, session_id, cx);
assert!(is_empty_state(&telemetry));
@@ -796,13 +802,14 @@ mod tests {
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
));
let http = FakeHttpClient::with_200_response();
+ let system_id = Some("system_id".to_string());
let installation_id = Some("installation_id".to_string());
let session_id = "session_id".to_string();
cx.update(|cx| {
let telemetry = Telemetry::new(clock.clone(), http, cx);
telemetry.state.lock().max_queue_size = 4;
- telemetry.start(installation_id, session_id, cx);
+ telemetry.start(system_id, installation_id, session_id, cx);
assert!(is_empty_state(&telemetry));
@@ -149,7 +149,8 @@ pub async fn post_crash(
installation_id = %installation_id,
description = %description,
backtrace = %summary,
- "crash report");
+ "crash report"
+ );
if let Some(slack_panics_webhook) = app.config.slack_panics_webhook.clone() {
let payload = slack::WebhookBody::new(|w| {
@@ -627,7 +628,9 @@ where
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct EditorEventRow {
+ system_id: String,
installation_id: String,
+ session_id: Option<String>,
metrics_id: String,
operation: String,
app_version: String,
@@ -647,7 +650,6 @@ pub struct EditorEventRow {
historical_event: bool,
architecture: String,
is_staff: Option<bool>,
- session_id: Option<String>,
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
@@ -677,9 +679,10 @@ impl EditorEventRow {
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
architecture: body.architecture.clone(),
+ system_id: body.system_id.clone().unwrap_or_default(),
installation_id: body.installation_id.clone().unwrap_or_default(),
- metrics_id: body.metrics_id.clone().unwrap_or_default(),
session_id: body.session_id.clone(),
+ metrics_id: body.metrics_id.clone().unwrap_or_default(),
is_staff: body.is_staff,
time: time.timestamp_millis(),
operation: event.operation,
@@ -699,6 +702,7 @@ impl EditorEventRow {
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct InlineCompletionEventRow {
installation_id: String,
+ session_id: Option<String>,
provider: String,
suggestion_accepted: bool,
app_version: String,
@@ -713,7 +717,6 @@ pub struct InlineCompletionEventRow {
city: String,
time: i64,
is_staff: Option<bool>,
- session_id: Option<String>,
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
@@ -879,7 +882,9 @@ impl AssistantEventRow {
#[derive(Debug, clickhouse::Row, Serialize)]
pub struct CpuEventRow {
+ system_id: Option<String>,
installation_id: Option<String>,
+ session_id: Option<String>,
is_staff: Option<bool>,
usage_as_percentage: f32,
core_count: u32,
@@ -888,7 +893,6 @@ pub struct CpuEventRow {
os_name: String,
os_version: String,
time: i64,
- session_id: Option<String>,
// pub normalized_cpu_usage: f64, MATERIALIZED
major: Option<i32>,
minor: Option<i32>,
@@ -917,6 +921,7 @@ impl CpuEventRow {
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
+ system_id: body.system_id.clone(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
@@ -940,6 +945,7 @@ pub struct MemoryEventRow {
os_version: String,
// ClientEventBase
+ system_id: Option<String>,
installation_id: Option<String>,
session_id: Option<String>,
is_staff: Option<bool>,
@@ -971,6 +977,7 @@ impl MemoryEventRow {
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
+ system_id: body.system_id.clone(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
@@ -994,6 +1001,7 @@ pub struct AppEventRow {
os_version: String,
// ClientEventBase
+ system_id: Option<String>,
installation_id: Option<String>,
session_id: Option<String>,
is_staff: Option<bool>,
@@ -1024,6 +1032,7 @@ impl AppEventRow {
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
+ system_id: body.system_id.clone(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
@@ -1046,6 +1055,7 @@ pub struct SettingEventRow {
os_version: String,
// ClientEventBase
+ system_id: Option<String>,
installation_id: Option<String>,
session_id: Option<String>,
is_staff: Option<bool>,
@@ -1076,6 +1086,7 @@ impl SettingEventRow {
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
+ system_id: body.system_id.clone(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
@@ -1099,6 +1110,7 @@ pub struct ExtensionEventRow {
os_version: String,
// ClientEventBase
+ system_id: Option<String>,
installation_id: Option<String>,
session_id: Option<String>,
is_staff: Option<bool>,
@@ -1134,6 +1146,7 @@ impl ExtensionEventRow {
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
+ system_id: body.system_id.clone(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
@@ -1224,6 +1237,7 @@ pub struct EditEventRow {
os_version: String,
// ClientEventBase
+ system_id: Option<String>,
installation_id: Option<String>,
// Note: This column name has a typo in the ClickHouse table.
#[serde(rename = "sesssion_id")]
@@ -1261,6 +1275,7 @@ impl EditEventRow {
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
+ system_id: body.system_id.clone(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
@@ -11,16 +11,14 @@ pub use smol;
pub use sqlez;
pub use sqlez_macros;
-use release_channel::ReleaseChannel;
pub use release_channel::RELEASE_CHANNEL;
use sqlez::domain::Migrator;
use sqlez::thread_safe_connection::ThreadSafeConnection;
use sqlez_macros::sql;
-use std::env;
use std::future::Future;
use std::path::Path;
-use std::sync::atomic::{AtomicBool, Ordering};
-use std::sync::LazyLock;
+use std::sync::{atomic::Ordering, LazyLock};
+use std::{env, sync::atomic::AtomicBool};
use util::{maybe, ResultExt};
const CONNECTION_INITIALIZE_QUERY: &str = sql!(
@@ -47,16 +45,12 @@ pub static ALL_FILE_DB_FAILED: LazyLock<AtomicBool> = LazyLock::new(|| AtomicBoo
/// This will retry a couple times if there are failures. If opening fails once, the db directory
/// is moved to a backup folder and a new one is created. If that fails, a shared in memory db is created.
/// In either case, static variables are set so that the user can be notified.
-pub async fn open_db<M: Migrator + 'static>(
- db_dir: &Path,
- release_channel: &ReleaseChannel,
-) -> ThreadSafeConnection<M> {
+pub async fn open_db<M: Migrator + 'static>(db_dir: &Path, scope: &str) -> ThreadSafeConnection<M> {
if *ZED_STATELESS {
return open_fallback_db().await;
}
- let release_channel_name = release_channel.dev_name();
- let main_db_dir = db_dir.join(Path::new(&format!("0-{}", release_channel_name)));
+ let main_db_dir = db_dir.join(format!("0-{}", scope));
let connection = maybe!(async {
smol::fs::create_dir_all(&main_db_dir)
@@ -118,7 +112,7 @@ pub async fn open_test_db<M: Migrator>(db_name: &str) -> ThreadSafeConnection<M>
/// Implements a basic DB wrapper for a given domain
#[macro_export]
macro_rules! define_connection {
- (pub static ref $id:ident: $t:ident<()> = $migrations:expr;) => {
+ (pub static ref $id:ident: $t:ident<()> = $migrations:expr; $($global:ident)?) => {
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<$t>);
impl ::std::ops::Deref for $t {
@@ -139,18 +133,23 @@ macro_rules! define_connection {
}
}
- use std::sync::LazyLock;
#[cfg(any(test, feature = "test-support"))]
- pub static $id: LazyLock<$t> = LazyLock::new(|| {
+ pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
$t($crate::smol::block_on($crate::open_test_db(stringify!($id))))
});
#[cfg(not(any(test, feature = "test-support")))]
- pub static $id: LazyLock<$t> = LazyLock::new(|| {
- $t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)))
+ pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
+ let db_dir = $crate::database_dir();
+ let scope = if false $(|| stringify!($global) == "global")? {
+ "global"
+ } else {
+ $crate::RELEASE_CHANNEL.dev_name()
+ };
+ $t($crate::smol::block_on($crate::open_db(db_dir, scope)))
});
};
- (pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr;) => {
+ (pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr; $($global:ident)?) => {
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<( $($d),+, $t )>);
impl ::std::ops::Deref for $t {
@@ -178,7 +177,13 @@ macro_rules! define_connection {
#[cfg(not(any(test, feature = "test-support")))]
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
- $t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)))
+ let db_dir = $crate::database_dir();
+ let scope = if false $(|| stringify!($global) == "global")? {
+ "global"
+ } else {
+ $crate::RELEASE_CHANNEL.dev_name()
+ };
+ $t($crate::smol::block_on($crate::open_db(db_dir, scope)))
});
};
}
@@ -225,7 +230,11 @@ mod tests {
.prefix("DbTests")
.tempdir()
.unwrap();
- let _bad_db = open_db::<BadDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
+ let _bad_db = open_db::<BadDB>(
+ tempdir.path(),
+ &release_channel::ReleaseChannel::Dev.dev_name(),
+ )
+ .await;
}
/// Test that DB exists but corrupted (causing recreate)
@@ -262,13 +271,19 @@ mod tests {
.tempdir()
.unwrap();
{
- let corrupt_db =
- open_db::<CorruptedDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
+ let corrupt_db = open_db::<CorruptedDB>(
+ tempdir.path(),
+ &release_channel::ReleaseChannel::Dev.dev_name(),
+ )
+ .await;
assert!(corrupt_db.persistent());
}
- let good_db =
- open_db::<GoodDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
+ let good_db = open_db::<GoodDB>(
+ tempdir.path(),
+ &release_channel::ReleaseChannel::Dev.dev_name(),
+ )
+ .await;
assert!(
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
.unwrap()
@@ -311,8 +326,11 @@ mod tests {
.unwrap();
{
// Setup the bad database
- let corrupt_db =
- open_db::<CorruptedDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
+ let corrupt_db = open_db::<CorruptedDB>(
+ tempdir.path(),
+ &release_channel::ReleaseChannel::Dev.dev_name(),
+ )
+ .await;
assert!(corrupt_db.persistent());
}
@@ -323,7 +341,7 @@ mod tests {
let guard = thread::spawn(move || {
let good_db = smol::block_on(open_db::<GoodDB>(
tmp_path.as_path(),
- &release_channel::ReleaseChannel::Dev,
+ &release_channel::ReleaseChannel::Dev.dev_name(),
));
assert!(
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
@@ -60,3 +60,33 @@ mod tests {
assert_eq!(db.read_kvp("key-1").unwrap(), None);
}
}
+
+define_connection!(pub static ref GLOBAL_KEY_VALUE_STORE: GlobalKeyValueStore<()> =
+ &[sql!(
+ CREATE TABLE IF NOT EXISTS kv_store(
+ key TEXT PRIMARY KEY,
+ value TEXT NOT NULL
+ ) STRICT;
+ )];
+ global
+);
+
+impl GlobalKeyValueStore {
+ query! {
+ pub fn read_kvp(key: &str) -> Result<Option<String>> {
+ SELECT value FROM kv_store WHERE key = (?)
+ }
+ }
+
+ query! {
+ pub async fn write_kvp(key: String, value: String) -> Result<()> {
+ INSERT OR REPLACE INTO kv_store(key, value) VALUES ((?), (?))
+ }
+ }
+
+ query! {
+ pub async fn delete_kvp(key: String) -> Result<()> {
+ DELETE FROM kv_store WHERE key = (?)
+ }
+ }
+}
@@ -44,8 +44,8 @@ const FEEDBACK_SUBMISSION_ERROR_TEXT: &str =
struct FeedbackRequestBody<'a> {
feedback_text: &'a str,
email: Option<String>,
- metrics_id: Option<Arc<str>>,
installation_id: Option<Arc<str>>,
+ metrics_id: Option<Arc<str>>,
system_specs: SystemSpecs,
is_staff: bool,
}
@@ -296,16 +296,16 @@ impl FeedbackModal {
}
let telemetry = zed_client.telemetry();
- let metrics_id = telemetry.metrics_id();
let installation_id = telemetry.installation_id();
+ let metrics_id = telemetry.metrics_id();
let is_staff = telemetry.is_staff();
let http_client = zed_client.http_client();
let feedback_endpoint = http_client.build_url("/api/feedback");
let request = FeedbackRequestBody {
feedback_text,
email,
- metrics_id,
installation_id,
+ metrics_id,
system_specs,
is_staff: is_staff.unwrap_or(false),
};
@@ -5,12 +5,14 @@ use std::{fmt::Display, sync::Arc, time::Duration};
#[derive(Serialize, Deserialize, Debug)]
pub struct EventRequestBody {
+ /// Identifier unique to each system Zed is installed on
+ pub system_id: Option<String>,
/// Identifier unique to each Zed installation (differs for stable, preview, dev)
pub installation_id: Option<String>,
/// Identifier unique to each logged in Zed user (randomly generated on first sign in)
- pub metrics_id: Option<String>,
/// Identifier unique to each Zed session (differs for each time you open Zed)
pub session_id: Option<String>,
+ pub metrics_id: Option<String>,
/// True for Zed staff, otherwise false
pub is_staff: Option<bool>,
/// Zed version number
@@ -34,6 +36,7 @@ pub struct EventWrapper {
pub signed_in: bool,
/// Duration between this event's timestamp and the timestamp of the first event in the current batch
pub milliseconds_since_first_event: i64,
+ /// The event itself
#[serde(flatten)]
pub event: Event,
}
@@ -245,8 +248,11 @@ pub struct Panic {
pub architecture: String,
/// The time the panic occurred (UNIX millisecond timestamp)
pub panicked_on: i64,
+ /// Identifier unique to each system Zed is installed on
#[serde(skip_serializing_if = "Option::is_none")]
+ pub system_id: Option<String>,
/// Identifier unique to each Zed installation (differs for stable, preview, dev)
+ #[serde(skip_serializing_if = "Option::is_none")]
pub installation_id: Option<String>,
/// Identifier unique to each Zed session (differs for each time you open Zed)
pub session_id: String,
@@ -13,7 +13,7 @@ use clap::{command, Parser};
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
use client::{parse_zed_link, Client, DevServerToken, ProxySettings, UserStore};
use collab_ui::channel_view::ChannelView;
-use db::kvp::KEY_VALUE_STORE;
+use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
use editor::Editor;
use env_logger::Builder;
use fs::{Fs, RealFs};
@@ -334,19 +334,17 @@ fn main() {
.with_assets(Assets)
.with_http_client(IsahcHttpClient::new(None, None));
- let (installation_id, existing_installation_id_found) = app
- .background_executor()
- .block(installation_id())
- .ok()
- .unzip();
-
+ let system_id = app.background_executor().block(system_id()).ok();
+ let installation_id = app.background_executor().block(installation_id()).ok();
+ let session_id = Uuid::new_v4().to_string();
let session = app.background_executor().block(Session::new());
-
let app_version = AppVersion::init(env!("CARGO_PKG_VERSION"));
+
reliability::init_panic_hook(
- installation_id.clone(),
app_version,
- session.id().to_owned(),
+ system_id.as_ref().map(|id| id.to_string()),
+ installation_id.as_ref().map(|id| id.to_string()),
+ session_id.clone(),
);
let (open_listener, mut open_rx) = OpenListener::new();
@@ -491,14 +489,26 @@ fn main() {
client::init(&client, cx);
language::init(cx);
let telemetry = client.telemetry();
- telemetry.start(installation_id.clone(), session.id().to_owned(), cx);
- telemetry.report_app_event(
- match existing_installation_id_found {
- Some(false) => "first open",
- _ => "open",
- }
- .to_string(),
+ telemetry.start(
+ system_id.as_ref().map(|id| id.to_string()),
+ installation_id.as_ref().map(|id| id.to_string()),
+ session_id,
+ cx,
);
+ if let (Some(system_id), Some(installation_id)) = (&system_id, &installation_id) {
+ match (&system_id, &installation_id) {
+ (IdType::New(_), IdType::New(_)) => {
+ telemetry.report_app_event("first open".to_string());
+ telemetry.report_app_event("first open for release channel".to_string());
+ }
+ (IdType::Existing(_), IdType::New(_)) => {
+ telemetry.report_app_event("first open for release channel".to_string());
+ }
+ (_, IdType::Existing(_)) => {
+ telemetry.report_app_event("open".to_string());
+ }
+ }
+ }
let app_session = cx.new_model(|cx| AppSession::new(session, cx));
let app_state = Arc::new(AppState {
@@ -514,7 +524,11 @@ fn main() {
AppState::set_global(Arc::downgrade(&app_state), cx);
auto_update::init(client.http_client(), cx);
- reliability::init(client.http_client(), installation_id, cx);
+ reliability::init(
+ client.http_client(),
+ installation_id.clone().map(|id| id.to_string()),
+ cx,
+ );
let prompt_builder = init_common(app_state.clone(), cx);
let args = Args::parse();
@@ -755,7 +769,23 @@ async fn authenticate(client: Arc<Client>, cx: &AsyncAppContext) -> Result<()> {
Ok::<_, anyhow::Error>(())
}
-async fn installation_id() -> Result<(String, bool)> {
+async fn system_id() -> Result<IdType> {
+ let key_name = "system_id".to_string();
+
+ if let Ok(Some(system_id)) = GLOBAL_KEY_VALUE_STORE.read_kvp(&key_name) {
+ return Ok(IdType::Existing(system_id));
+ }
+
+ let system_id = Uuid::new_v4().to_string();
+
+ GLOBAL_KEY_VALUE_STORE
+ .write_kvp(key_name, system_id.clone())
+ .await?;
+
+ Ok(IdType::New(system_id))
+}
+
+async fn installation_id() -> Result<IdType> {
let legacy_key_name = "device_id".to_string();
let key_name = "installation_id".to_string();
@@ -765,11 +795,11 @@ async fn installation_id() -> Result<(String, bool)> {
.write_kvp(key_name, installation_id.clone())
.await?;
KEY_VALUE_STORE.delete_kvp(legacy_key_name).await?;
- return Ok((installation_id, true));
+ return Ok(IdType::Existing(installation_id));
}
if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&key_name) {
- return Ok((installation_id, true));
+ return Ok(IdType::Existing(installation_id));
}
let installation_id = Uuid::new_v4().to_string();
@@ -778,7 +808,7 @@ async fn installation_id() -> Result<(String, bool)> {
.write_kvp(key_name, installation_id.clone())
.await?;
- Ok((installation_id, false))
+ Ok(IdType::New(installation_id))
}
async fn restore_or_create_workspace(
@@ -1087,6 +1117,20 @@ struct Args {
dev_server_token: Option<String>,
}
+#[derive(Clone, Debug)]
+enum IdType {
+ New(String),
+ Existing(String),
+}
+
+impl ToString for IdType {
+ fn to_string(&self) -> String {
+ match self {
+ IdType::New(id) | IdType::Existing(id) => id.clone(),
+ }
+ }
+}
+
fn parse_url_arg(arg: &str, cx: &AppContext) -> Result<String> {
match std::fs::canonicalize(Path::new(&arg)) {
Ok(path) => Ok(format!(
@@ -28,8 +28,9 @@ use crate::stdout_is_a_pty;
static PANIC_COUNT: AtomicU32 = AtomicU32::new(0);
pub fn init_panic_hook(
- installation_id: Option<String>,
app_version: SemanticVersion,
+ system_id: Option<String>,
+ installation_id: Option<String>,
session_id: String,
) {
let is_pty = stdout_is_a_pty();
@@ -102,6 +103,7 @@ pub fn init_panic_hook(
architecture: env::consts::ARCH.into(),
panicked_on: Utc::now().timestamp_millis(),
backtrace,
+ system_id: system_id.clone(),
installation_id: installation_id.clone(),
session_id: session_id.clone(),
};