#[cfg(not(target_arch = "wasm32"))]
mod native;
#[cfg(not(target_arch = "wasm32"))]
pub use native::{Config, ConfigError};
#[cfg(not(target_arch = "wasm32"))]
use native::{Pipeline, PipelineError};
#[cfg(target_arch = "wasm32")]
mod web;
#[cfg(target_arch = "wasm32")]
pub use web::{Config, ConfigError};
#[cfg(target_arch = "wasm32")]
use web::{Pipeline, PipelineError};
#[cfg(not(target_arch = "wasm32"))]
pub mod cli;
mod posthog;
use posthog::{PostHogBatch, PostHogEvent};
pub mod event;
use std::borrow::Cow;
use std::collections::HashMap;
use std::io::Error as IoError;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;
use time::OffsetDateTime;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum EventKind {
Append,
Update,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct AnalyticsEvent {
#[serde(with = "::time::serde::rfc3339")]
time_utc: OffsetDateTime,
kind: EventKind,
name: Cow<'static, str>,
props: HashMap<Cow<'static, str>, Property>,
}
impl AnalyticsEvent {
#[inline]
pub fn new(name: impl Into<Cow<'static, str>>, kind: EventKind) -> Self {
Self {
time_utc: OffsetDateTime::now_utc(),
kind,
name: name.into(),
props: Default::default(),
}
}
#[inline]
pub fn insert(&mut self, name: impl Into<Cow<'static, str>>, value: impl Into<Property>) {
self.props.insert(name.into(), value.into());
}
#[inline]
pub fn insert_opt(
&mut self,
name: impl Into<Cow<'static, str>>,
value: Option<impl Into<Property>>,
) {
if let Some(value) = value {
self.props.insert(name.into(), value.into());
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum Property {
Bool(bool),
Integer(i64),
Float(f64),
String(String),
}
impl Property {
pub fn hashed(&self) -> Self {
const SALT: &str = "d6d6bed3-028a-49ac-94dc-8c89cfb19379";
use sha2::Digest as _;
let mut hasher = sha2::Sha256::default();
hasher.update(SALT);
match self {
Self::Bool(data) => hasher.update([*data as u8]),
Self::Integer(data) => hasher.update(data.to_le_bytes()),
Self::Float(data) => hasher.update(data.to_le_bytes()),
Self::String(data) => hasher.update(data),
}
Self::String(format!("{:x}", hasher.finalize()))
}
}
impl From<bool> for Property {
#[inline]
fn from(value: bool) -> Self {
Self::Bool(value)
}
}
impl From<i64> for Property {
#[inline]
fn from(value: i64) -> Self {
Self::Integer(value)
}
}
impl From<f64> for Property {
#[inline]
fn from(value: f64) -> Self {
Self::Float(value)
}
}
impl From<String> for Property {
#[inline]
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<&str> for Property {
#[inline]
fn from(value: &str) -> Self {
Self::String(value.to_owned())
}
}
#[cfg(not(target_arch = "wasm32"))]
const DISCLAIMER: &str = "
Welcome to Rerun!
This open source library collects anonymous usage data to
help the Rerun team improve the library.
Summary:
- We only collect high level events about the features used within the Rerun Viewer.
- The actual data you log to Rerun, such as point clouds, images, or text logs,
will never be collected.
- We don't log IP addresses.
- We don't log your user name, file paths, or any personal identifiable data.
- Usage data we do collect will be sent to and stored on servers within the EU.
For more details and instructions on how to opt out, run the command:
rerun analytics details
As this is this your first session, _no_ usage data has been sent yet,
giving you an opportunity to opt-out first if you wish.
Happy Rerunning!
";
#[derive(thiserror::Error, Debug)]
pub enum AnalyticsError {
#[error(transparent)]
Config(#[from] ConfigError),
#[error(transparent)]
Pipeline(#[from] PipelineError),
#[error(transparent)]
Io(#[from] IoError),
}
pub struct Analytics {
config: Config,
pipeline: Option<Pipeline>,
default_append_props: HashMap<Cow<'static, str>, Property>,
event_id: AtomicU64,
}
fn load_config() -> Result<Config, ConfigError> {
let config = match Config::load() {
Ok(config) => config,
#[allow(unused_variables)]
Err(err) => {
#[cfg(not(target_arch = "wasm32"))]
re_log::warn!("failed to load analytics config file: {err}");
None
}
};
if let Some(config) = config {
re_log::trace!(?config, "loaded analytics config");
Ok(config)
} else {
re_log::trace!(?config, "initialized analytics config");
let config = Config::new()?;
#[cfg(not(target_arch = "wasm32"))]
if config.is_first_run() {
eprintln!("{DISCLAIMER}");
config.save()?;
re_log::trace!(?config, "saved analytics config");
}
#[cfg(target_arch = "wasm32")]
{
config.save()?;
re_log::trace!(?config, "saved analytics config");
}
Ok(config)
}
}
impl Analytics {
pub fn new(tick: Duration) -> Result<Self, AnalyticsError> {
let config = load_config()?;
let pipeline = Pipeline::new(&config, tick)?;
re_log::trace!("initialized analytics pipeline");
Ok(Self {
config,
default_append_props: Default::default(),
pipeline,
event_id: AtomicU64::new(1), })
}
pub fn config(&self) -> &Config {
&self.config
}
pub fn record<E: Event>(&self, event: E) {
if self.pipeline.is_none() {
return;
}
let mut e = AnalyticsEvent::new(E::NAME, E::KIND);
event.serialize(&mut e);
self.record_raw(e);
}
fn record_raw(&self, mut event: AnalyticsEvent) {
if let Some(pipeline) = self.pipeline.as_ref() {
if event.kind == EventKind::Append {
event.props.extend(self.default_append_props.clone());
event.props.insert(
"event_id".into(),
(self.event_id.fetch_add(1, Ordering::Relaxed) as i64).into(),
);
}
pipeline.record(event);
}
}
}
pub trait Event: Properties {
const NAME: &'static str;
const KIND: EventKind = EventKind::Append;
}
pub trait Properties: Sized {
fn serialize(self, event: &mut AnalyticsEvent) {
let _ = event;
}
}
impl Properties for re_build_info::BuildInfo {
fn serialize(self, event: &mut AnalyticsEvent) {
let git_hash = self.git_hash_or_tag();
let Self {
crate_name: _,
features,
version,
rustc_version,
llvm_version,
git_hash: _,
git_branch: _,
is_in_rerun_workspace,
target_triple,
datetime,
} = self;
event.insert("features", features);
event.insert("git_hash", git_hash);
event.insert("rerun_version", version.to_string());
event.insert("rust_version", rustc_version);
event.insert("llvm_version", llvm_version);
event.insert("target", target_triple);
event.insert("build_date", datetime);
event.insert("debug", cfg!(debug_assertions)); event.insert("rerun_workspace", is_in_rerun_workspace);
}
}