Add command to view the telemetry log
Co-authored-by: Joseph Lyons <joseph@zed.dev>
This commit is contained in:
parent
f2db3abdb2
commit
3bd68128d7
6 changed files with 127 additions and 17 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -959,6 +959,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"smol",
|
"smol",
|
||||||
"sum_tree",
|
"sum_tree",
|
||||||
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time 0.3.11",
|
"time 0.3.11",
|
||||||
"tiny_http",
|
"tiny_http",
|
||||||
|
|
|
@ -35,6 +35,7 @@ tiny_http = "0.8"
|
||||||
uuid = { version = "1.1.2", features = ["v4"] }
|
uuid = { version = "1.1.2", features = ["v4"] }
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
serde = { version = "*", features = ["derive"] }
|
serde = { version = "*", features = ["derive"] }
|
||||||
|
tempfile = "3"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
|
|
|
@ -33,6 +33,7 @@ use std::{
|
||||||
convert::TryFrom,
|
convert::TryFrom,
|
||||||
fmt::Write as _,
|
fmt::Write as _,
|
||||||
future::Future,
|
future::Future,
|
||||||
|
path::PathBuf,
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
@ -332,10 +333,11 @@ impl Client {
|
||||||
log::info!("set status on client {}: {:?}", self.id, status);
|
log::info!("set status on client {}: {:?}", self.id, status);
|
||||||
let mut state = self.state.write();
|
let mut state = self.state.write();
|
||||||
*state.status.0.borrow_mut() = status;
|
*state.status.0.borrow_mut() = status;
|
||||||
|
let user_id = state.credentials.as_ref().map(|c| c.user_id);
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
Status::Connected { .. } => {
|
Status::Connected { .. } => {
|
||||||
self.telemetry.set_user_id(self.user_id());
|
self.telemetry.set_user_id(user_id);
|
||||||
state._reconnect_task = None;
|
state._reconnect_task = None;
|
||||||
}
|
}
|
||||||
Status::ConnectionLost => {
|
Status::ConnectionLost => {
|
||||||
|
@ -364,7 +366,7 @@ impl Client {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
Status::SignedOut | Status::UpgradeRequired => {
|
Status::SignedOut | Status::UpgradeRequired => {
|
||||||
self.telemetry.set_user_id(self.user_id());
|
self.telemetry.set_user_id(user_id);
|
||||||
state._reconnect_task.take();
|
state._reconnect_task.take();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -1060,6 +1062,10 @@ impl Client {
|
||||||
pub fn report_event(&self, kind: &str, properties: Value) {
|
pub fn report_event(&self, kind: &str, properties: Value) {
|
||||||
self.telemetry.report_event(kind, properties)
|
self.telemetry.report_event(kind, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn telemetry_log_file_path(&self) -> Option<PathBuf> {
|
||||||
|
self.telemetry.log_file_path()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnyWeakEntityHandle {
|
impl AnyWeakEntityHandle {
|
||||||
|
|
|
@ -10,15 +10,18 @@ use lazy_static::lazy_static;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::{
|
use std::{
|
||||||
|
io::Write,
|
||||||
mem,
|
mem,
|
||||||
|
path::PathBuf,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
use tempfile::NamedTempFile;
|
||||||
use util::{post_inc, ResultExt, TryFutureExt};
|
use util::{post_inc, ResultExt, TryFutureExt};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub struct Telemetry {
|
pub struct Telemetry {
|
||||||
client: Arc<dyn HttpClient>,
|
http_client: Arc<dyn HttpClient>,
|
||||||
executor: Arc<Background>,
|
executor: Arc<Background>,
|
||||||
session_id: u128,
|
session_id: u128,
|
||||||
state: Mutex<TelemetryState>,
|
state: Mutex<TelemetryState>,
|
||||||
|
@ -34,6 +37,7 @@ struct TelemetryState {
|
||||||
queue: Vec<AmplitudeEvent>,
|
queue: Vec<AmplitudeEvent>,
|
||||||
next_event_id: usize,
|
next_event_id: usize,
|
||||||
flush_task: Option<Task<()>>,
|
flush_task: Option<Task<()>>,
|
||||||
|
log_file: Option<NamedTempFile>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const AMPLITUDE_EVENTS_URL: &'static str = "https://api2.amplitude.com/batch";
|
const AMPLITUDE_EVENTS_URL: &'static str = "https://api2.amplitude.com/batch";
|
||||||
|
@ -52,10 +56,13 @@ struct AmplitudeEventBatch {
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct AmplitudeEvent {
|
struct AmplitudeEvent {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
user_id: Option<Arc<str>>,
|
user_id: Option<Arc<str>>,
|
||||||
device_id: Option<Arc<str>>,
|
device_id: Option<Arc<str>>,
|
||||||
event_type: String,
|
event_type: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
event_properties: Option<Map<String, Value>>,
|
event_properties: Option<Map<String, Value>>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
user_properties: Option<Map<String, Value>>,
|
user_properties: Option<Map<String, Value>>,
|
||||||
os_name: &'static str,
|
os_name: &'static str,
|
||||||
os_version: Option<Arc<str>>,
|
os_version: Option<Arc<str>>,
|
||||||
|
@ -80,8 +87,8 @@ const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(30);
|
||||||
impl Telemetry {
|
impl Telemetry {
|
||||||
pub fn new(client: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {
|
pub fn new(client: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {
|
||||||
let platform = cx.platform();
|
let platform = cx.platform();
|
||||||
Arc::new(Self {
|
let this = Arc::new(Self {
|
||||||
client,
|
http_client: client,
|
||||||
executor: cx.background().clone(),
|
executor: cx.background().clone(),
|
||||||
session_id: SystemTime::now()
|
session_id: SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
|
@ -101,9 +108,29 @@ impl Telemetry {
|
||||||
queue: Default::default(),
|
queue: Default::default(),
|
||||||
flush_task: Default::default(),
|
flush_task: Default::default(),
|
||||||
next_event_id: 0,
|
next_event_id: 0,
|
||||||
|
log_file: None,
|
||||||
user_id: None,
|
user_id: None,
|
||||||
}),
|
}),
|
||||||
})
|
});
|
||||||
|
|
||||||
|
if AMPLITUDE_API_KEY.is_some() {
|
||||||
|
this.executor
|
||||||
|
.spawn({
|
||||||
|
let this = this.clone();
|
||||||
|
async move {
|
||||||
|
if let Some(tempfile) = NamedTempFile::new().log_err() {
|
||||||
|
this.state.lock().log_file = Some(tempfile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_file_path(&self) -> Option<PathBuf> {
|
||||||
|
Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(self: &Arc<Self>, db: Arc<Db>) {
|
pub fn start(self: &Arc<Self>, db: Arc<Db>) {
|
||||||
|
@ -189,23 +216,39 @@ impl Telemetry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&self) {
|
fn flush(self: &Arc<Self>) {
|
||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
let events = mem::take(&mut state.queue);
|
let events = mem::take(&mut state.queue);
|
||||||
state.flush_task.take();
|
state.flush_task.take();
|
||||||
|
drop(state);
|
||||||
|
|
||||||
if let Some(api_key) = AMPLITUDE_API_KEY.as_ref() {
|
if let Some(api_key) = AMPLITUDE_API_KEY.as_ref() {
|
||||||
let client = self.client.clone();
|
let this = self.clone();
|
||||||
self.executor
|
self.executor
|
||||||
.spawn(async move {
|
.spawn(
|
||||||
let batch = AmplitudeEventBatch { api_key, events };
|
async move {
|
||||||
let body = serde_json::to_vec(&batch).log_err()?;
|
let mut json_bytes = Vec::new();
|
||||||
let request = Request::post(AMPLITUDE_EVENTS_URL)
|
|
||||||
.body(body.into())
|
if let Some(file) = &mut this.state.lock().log_file {
|
||||||
.log_err()?;
|
let file = file.as_file_mut();
|
||||||
client.send(request).await.log_err();
|
for event in &events {
|
||||||
Some(())
|
json_bytes.clear();
|
||||||
})
|
serde_json::to_writer(&mut json_bytes, event)?;
|
||||||
|
file.write_all(&json_bytes)?;
|
||||||
|
file.write(b"\n")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let batch = AmplitudeEventBatch { api_key, events };
|
||||||
|
json_bytes.clear();
|
||||||
|
serde_json::to_writer(&mut json_bytes, &batch)?;
|
||||||
|
let request =
|
||||||
|
Request::post(AMPLITUDE_EVENTS_URL).body(json_bytes.into())?;
|
||||||
|
this.http_client.send(request).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
.log_err(),
|
||||||
|
)
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -332,6 +332,11 @@ pub fn menus() -> Vec<Menu<'static>> {
|
||||||
action: Box::new(command_palette::Toggle),
|
action: Box::new(command_palette::Toggle),
|
||||||
},
|
},
|
||||||
MenuItem::Separator,
|
MenuItem::Separator,
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "View Telemetry Log",
|
||||||
|
action: Box::new(crate::OpenTelemetryLog),
|
||||||
|
},
|
||||||
|
MenuItem::Separator,
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Documentation",
|
name: "Documentation",
|
||||||
action: Box::new(crate::OpenBrowser {
|
action: Box::new(crate::OpenBrowser {
|
||||||
|
|
|
@ -56,6 +56,7 @@ actions!(
|
||||||
DebugElements,
|
DebugElements,
|
||||||
OpenSettings,
|
OpenSettings,
|
||||||
OpenLog,
|
OpenLog,
|
||||||
|
OpenTelemetryLog,
|
||||||
OpenKeymap,
|
OpenKeymap,
|
||||||
OpenDefaultSettings,
|
OpenDefaultSettings,
|
||||||
OpenDefaultKeymap,
|
OpenDefaultKeymap,
|
||||||
|
@ -146,6 +147,12 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
|
||||||
open_log_file(workspace, app_state.clone(), cx);
|
open_log_file(workspace, app_state.clone(), cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
cx.add_action({
|
||||||
|
let app_state = app_state.clone();
|
||||||
|
move |workspace: &mut Workspace, _: &OpenTelemetryLog, cx: &mut ViewContext<Workspace>| {
|
||||||
|
open_telemetry_log_file(workspace, app_state.clone(), cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
cx.add_action({
|
cx.add_action({
|
||||||
let app_state = app_state.clone();
|
let app_state = app_state.clone();
|
||||||
move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext<Workspace>| {
|
move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext<Workspace>| {
|
||||||
|
@ -504,6 +511,53 @@ fn open_log_file(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn open_telemetry_log_file(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
app_state: Arc<AppState>,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) {
|
||||||
|
workspace.with_local_workspace(cx, app_state.clone(), |_, cx| {
|
||||||
|
cx.spawn_weak(|workspace, mut cx| async move {
|
||||||
|
let workspace = workspace.upgrade(&cx)?;
|
||||||
|
let path = app_state.client.telemetry_log_file_path()?;
|
||||||
|
let log = app_state.fs.load(&path).await.log_err()?;
|
||||||
|
workspace.update(&mut cx, |workspace, cx| {
|
||||||
|
let project = workspace.project().clone();
|
||||||
|
let buffer = project
|
||||||
|
.update(cx, |project, cx| project.create_buffer("", None, cx))
|
||||||
|
.expect("creating buffers on a local workspace always succeeds");
|
||||||
|
buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.set_language(app_state.languages.get_language("JSON"), cx);
|
||||||
|
buffer.edit(
|
||||||
|
[(
|
||||||
|
0..0,
|
||||||
|
concat!(
|
||||||
|
"// Zed collects anonymous usage data to help us understand how people are using the app.\n",
|
||||||
|
"// After the beta release, we'll provide the ability to opt out of this telemetry.\n",
|
||||||
|
"\n"
|
||||||
|
),
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
buffer.edit([(buffer.len()..buffer.len(), log)], None, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
let buffer = cx.add_model(|cx| {
|
||||||
|
MultiBuffer::singleton(buffer, cx).with_title("Telemetry Log".into())
|
||||||
|
});
|
||||||
|
workspace.add_item(
|
||||||
|
Box::new(cx.add_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(())
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn open_bundled_config_file(
|
fn open_bundled_config_file(
|
||||||
workspace: &mut Workspace,
|
workspace: &mut Workspace,
|
||||||
app_state: Arc<AppState>,
|
app_state: Arc<AppState>,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue