1pub struct CrashPanic {
16 pub build_info: BuildInfo,
17 pub callstack: String,
18 pub message: Option<String>,
19 pub file_line: Option<String>,
20}
21
22pub struct Identify {
26 pub build_info: re_build_info::BuildInfo,
28
29 pub rust_version: Option<String>,
35 pub llvm_version: Option<String>,
36 pub python_version: Option<String>,
37
38 pub opt_in_metadata: HashMap<String, Property>,
43}
44
45pub struct ViewerStarted {
49 pub url: Option<String>,
54
55 pub app_env: &'static str,
57
58 pub runtime_info: ViewerRuntimeInformation,
60}
61
62pub struct ViewerRuntimeInformation {
64 pub is_wsl: bool,
66
67 pub graphics_adapter_backend: String,
71
72 pub re_renderer_device_tier: String,
80}
81
82impl Properties for ViewerRuntimeInformation {
83 fn serialize(self, event: &mut AnalyticsEvent) {
84 let Self {
85 is_wsl,
86 graphics_adapter_backend,
87 re_renderer_device_tier,
88 } = self;
89
90 event.insert("is_wsl", is_wsl);
91 event.insert("graphics_adapter_backend", graphics_adapter_backend);
92 event.insert("re_renderer_device_tier", re_renderer_device_tier);
93 }
94}
95
96pub struct OpenRecording {
100 pub url: Option<String>,
105
106 pub app_env: &'static str,
108
109 pub store_info: Option<StoreInfo>,
110
111 pub data_source: Option<&'static str>,
113}
114
115pub struct StoreInfo {
117 pub application_id: Id,
121
122 pub recording_id: Id,
126
127 pub store_source: String,
129
130 pub store_version: String,
132
133 pub rust_version: Option<String>,
135 pub llvm_version: Option<String>,
136 pub python_version: Option<String>,
137
138 pub app_id_starts_with_rerun_example: bool,
140}
141
142#[derive(Clone)]
143pub enum Id {
144 Official(String),
146
147 Hashed(Property),
149}
150
151pub struct ServeWasm;
155
156impl Event for ServeWasm {
157 const NAME: &'static str = "serve_wasm";
158}
159
160impl Properties for ServeWasm {
161 }
163
164use std::collections::HashMap;
167
168use re_build_info::BuildInfo;
169use url::Url;
170
171use crate::{AnalyticsEvent, Event, EventKind, Properties, Property};
172
173impl From<Id> for Property {
174 fn from(val: Id) -> Self {
175 match val {
176 Id::Official(id) => Self::String(id),
177 Id::Hashed(id) => id,
178 }
179 }
180}
181
182impl Event for Identify {
183 const NAME: &'static str = "$identify";
184
185 const KIND: EventKind = EventKind::Update;
186}
187
188impl Properties for Identify {
189 fn serialize(self, event: &mut AnalyticsEvent) {
190 let Self {
191 build_info,
192 rust_version,
193 llvm_version,
194 python_version,
195 opt_in_metadata,
196 } = self;
197
198 build_info.serialize(event);
199 event.insert_opt("rust_version", rust_version);
200 event.insert_opt("llvm_version", llvm_version);
201 event.insert_opt("python_version", python_version);
202 for (name, value) in opt_in_metadata {
203 event.insert(name, value);
204 }
205 }
206}
207
208impl Event for ViewerStarted {
209 const NAME: &'static str = "viewer_started";
210}
211
212const RERUN_DOMAINS: [&str; 1] = ["rerun.io"];
213
214fn extract_root_domain(url: &str) -> Option<String> {
216 let parsed = Url::parse(url).ok()?;
217 let domain = parsed.domain()?;
218 let parts = domain.split('.').collect::<Vec<_>>();
219 if parts.len() >= 2 {
220 Some(parts[parts.len() - 2..].join("."))
221 } else {
222 None
223 }
224}
225
226fn add_sanitized_url_properties(event: &mut AnalyticsEvent, url: Option<String>) {
227 let Some(root_domain) = url.as_ref().and_then(|url| extract_root_domain(url)) else {
228 return;
229 };
230
231 if RERUN_DOMAINS.contains(&root_domain.as_str()) {
232 event.insert_opt("rerun_url", url);
233 }
234
235 let hashed = Property::from(root_domain).hashed();
236 event.insert("hashed_root_domain", hashed);
237}
238
239impl Properties for ViewerStarted {
240 fn serialize(self, event: &mut AnalyticsEvent) {
241 let Self {
242 url,
243 app_env,
244 runtime_info,
245 } = self;
246
247 event.insert("app_env", app_env);
248 add_sanitized_url_properties(event, url);
249 runtime_info.serialize(event);
250 }
251}
252
253impl Event for OpenRecording {
254 const NAME: &'static str = "open_recording";
255}
256
257impl Properties for OpenRecording {
258 fn serialize(self, event: &mut AnalyticsEvent) {
259 let Self {
260 url,
261 app_env,
262 store_info,
263 data_source,
264 } = self;
265
266 add_sanitized_url_properties(event, url);
267
268 event.insert("app_env", app_env);
269
270 if let Some(store_info) = store_info {
271 let StoreInfo {
272 application_id,
273 recording_id,
274 store_source,
275 store_version,
276 rust_version,
277 llvm_version,
278 python_version,
279
280 app_id_starts_with_rerun_example,
281 } = store_info;
282
283 event.insert("application_id", application_id);
284 event.insert("recording_id", recording_id);
285 event.insert("store_source", store_source);
286 event.insert("store_version", store_version);
287 event.insert_opt("rust_version", rust_version);
288 event.insert_opt("llvm_version", llvm_version);
289 event.insert_opt("python_version", python_version);
290 event.insert(
291 "app_id_starts_with_rerun_example",
292 app_id_starts_with_rerun_example,
293 );
294 }
295
296 if let Some(data_source) = data_source {
297 event.insert("data_source", data_source);
298 }
299 }
300}
301
302impl Event for CrashPanic {
303 const NAME: &'static str = "crash-panic";
304}
305
306impl Properties for CrashPanic {
307 fn serialize(self, event: &mut AnalyticsEvent) {
308 let Self {
309 build_info,
310 callstack,
311 message,
312 file_line,
313 } = self;
314
315 build_info.serialize(event);
316 event.insert("callstack", callstack);
317 event.insert_opt("message", message);
318 event.insert_opt("file_line", file_line);
319 }
320}
321
322pub struct CrashSignal {
323 pub build_info: BuildInfo,
324 pub signal: String,
325 pub callstack: String,
326}
327
328impl Event for CrashSignal {
329 const NAME: &'static str = "crash-signal";
330}
331
332impl Properties for CrashSignal {
333 fn serialize(self, event: &mut AnalyticsEvent) {
334 let Self {
335 build_info,
336 signal,
337 callstack,
338 } = self;
339
340 build_info.serialize(event);
341 event.insert("signal", signal.clone());
342 event.insert("callstack", callstack.clone());
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349
350 #[test]
351 fn test_root_domain() {
352 assert_eq!(
354 extract_root_domain("https://rerun.io"),
355 Some("rerun.io".to_owned())
356 );
357 assert_eq!(
358 extract_root_domain("https://ReRun.io"),
359 Some("rerun.io".to_owned())
360 );
361 assert_eq!(
362 extract_root_domain("http://app.rerun.io"),
363 Some("rerun.io".to_owned())
364 );
365 assert_eq!(
366 extract_root_domain(
367 "https://www.rerun.io/viewer?url=https://app.rerun.io/version/0.15.1/examples/detect_and_track_objects.rrd"
368 ),
369 Some("rerun.io".to_owned())
370 );
371
372 assert_eq!(
374 extract_root_domain("http://localhost:9090/?url=rerun%2Bhttp://localhost:9877"),
375 None
376 );
377 assert_eq!(
378 extract_root_domain("http://127.0.0.1:9090/?url=rerun%2Bhttp://localhost:9877"),
379 None
380 );
381
382 assert_eq!(extract_root_domain("rerun.io"), None);
384 assert_eq!(extract_root_domain("https:/rerun"), None);
385 assert_eq!(extract_root_domain("https://rerun"), None);
386 }
387}