use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use url::Url;
use crate::error::{Result, RumError};
use crate::propagation::PropagationMode;
pub(crate) const SDK_VERSION: &str = env!("CARGO_PKG_VERSION");
pub(crate) fn default_platform_type() -> &'static str {
if cfg!(target_os = "macos") {
"macOS"
} else if cfg!(target_os = "windows") {
"Windows"
} else if cfg!(target_os = "linux") {
"Linux"
} else if cfg!(target_os = "android") {
"Android"
} else if cfg!(target_os = "ios") {
"iOS"
} else {
"unknown"
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RumEnv {
Prod,
Gray,
Pre,
Daily,
Local,
Dev,
}
impl RumEnv {
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::Prod => "prod",
Self::Gray => "gray",
Self::Pre => "pre",
Self::Daily => "daily",
Self::Local => "local",
Self::Dev => "dev",
}
}
}
#[derive(Clone)]
pub struct RumConfig {
pub(crate) config_address: Url,
pub(crate) app_id: String,
pub(crate) app_name: Option<String>,
pub(crate) app_version: Option<String>,
pub(crate) app_type: String,
pub(crate) env: RumEnv,
pub(crate) utdid: Option<String>,
pub(crate) user_id: Option<String>,
pub(crate) user_name: Option<String>,
pub(crate) user_tags: Option<String>,
pub(crate) properties: HashMap<String, String>,
pub(crate) device_name: Option<String>,
pub(crate) device_model: Option<String>,
pub(crate) device_brand: Option<String>,
pub(crate) device_type: String,
pub(crate) net_device_model: String,
pub(crate) os_type: String,
pub(crate) os_version: Option<String>,
pub(crate) network: NetworkConfig,
pub(crate) exporter: ExporterConfig,
}
#[derive(Clone)]
pub(crate) struct NetworkConfig {
pub enabled: bool,
pub record_enabled: bool,
pub tracing_enabled: bool,
pub propagation_mode: PropagationMode,
pub should_record_request: Arc<dyn Fn(&Url) -> bool + Send + Sync>,
pub should_trace_request: Arc<dyn Fn(&Url) -> bool + Send + Sync>,
pub excluded_hosts: HashSet<String>,
}
impl Default for NetworkConfig {
fn default() -> Self {
Self {
enabled: true,
record_enabled: true,
tracing_enabled: true,
propagation_mode: PropagationMode::TraceParent,
should_record_request: Arc::new(|_| true),
should_trace_request: Arc::new(|_| true),
excluded_hosts: HashSet::new(),
}
}
}
#[derive(Clone)]
pub(crate) struct ExporterConfig {
pub flush_interval: Duration,
pub max_batch_size: usize,
pub max_queue_size: usize,
pub request_timeout: Duration,
pub cache_path: Option<PathBuf>,
}
impl Default for ExporterConfig {
fn default() -> Self {
Self {
flush_interval: Duration::from_secs(5),
max_batch_size: 20,
max_queue_size: 5120,
request_timeout: Duration::from_secs(30),
cache_path: None,
}
}
}
#[derive(Default)]
pub struct RumConfigBuilder {
config_address: Option<String>,
app_id: Option<String>,
app_name: Option<String>,
app_version: Option<String>,
app_type: Option<String>,
env: Option<RumEnv>,
utdid: Option<String>,
user_id: Option<String>,
user_name: Option<String>,
user_tags: Option<String>,
properties: HashMap<String, String>,
device_name: Option<String>,
device_model: Option<String>,
device_brand: Option<String>,
device_type: Option<String>,
net_device_model: Option<String>,
os_type: Option<String>,
os_version: Option<String>,
network: NetworkConfig,
exporter: ExporterConfig,
}
impl RumConfig {
pub fn builder() -> RumConfigBuilder {
RumConfigBuilder {
env: Some(RumEnv::Prod),
network: NetworkConfig::default(),
exporter: ExporterConfig::default(),
..Default::default()
}
}
pub(crate) fn config_address(&self) -> &Url {
&self.config_address
}
pub(crate) fn app_id(&self) -> &str {
&self.app_id
}
pub(crate) fn app_name_or_id(&self) -> &str {
self.app_name
.as_deref()
.filter(|s| !s.is_empty())
.unwrap_or(&self.app_id)
}
}
impl RumConfigBuilder {
pub fn config_address(mut self, value: impl Into<String>) -> Self {
self.config_address = Some(value.into());
self
}
pub fn app_id(mut self, value: impl Into<String>) -> Self {
self.app_id = Some(value.into());
self
}
pub fn app_name(mut self, value: impl Into<String>) -> Self {
self.app_name = Some(value.into());
self
}
pub fn app_version(mut self, value: impl Into<String>) -> Self {
self.app_version = Some(value.into());
self
}
pub fn app_type(mut self, value: impl Into<String>) -> Self {
self.app_type = Some(value.into());
self
}
pub fn env(mut self, value: RumEnv) -> Self {
self.env = Some(value);
self
}
pub fn utdid(mut self, value: impl Into<String>) -> Self {
self.utdid = Some(value.into());
self
}
pub fn user_id(mut self, value: impl Into<String>) -> Self {
self.user_id = Some(value.into());
self
}
pub fn user_name(mut self, value: impl Into<String>) -> Self {
self.user_name = Some(value.into());
self
}
pub fn user_tags(mut self, value: impl Into<String>) -> Self {
self.user_tags = Some(value.into());
self
}
pub fn property(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.properties.insert(key.into(), value.into());
self
}
pub fn device_name(mut self, value: impl Into<String>) -> Self {
self.device_name = Some(value.into());
self
}
pub fn device_model(mut self, value: impl Into<String>) -> Self {
self.device_model = Some(value.into());
self
}
pub fn device_brand(mut self, value: impl Into<String>) -> Self {
self.device_brand = Some(value.into());
self
}
pub fn device_type(mut self, value: impl Into<String>) -> Self {
self.device_type = Some(value.into());
self
}
pub fn net_device_model(mut self, value: impl Into<String>) -> Self {
self.net_device_model = Some(value.into());
self
}
pub fn os_type(mut self, value: impl Into<String>) -> Self {
self.os_type = Some(value.into());
self
}
pub fn os_version(mut self, value: impl Into<String>) -> Self {
self.os_version = Some(value.into());
self
}
pub fn network<F>(mut self, configure: F) -> Self
where
F: FnOnce(NetworkConfigBuilder) -> NetworkConfigBuilder,
{
self.network = configure(NetworkConfigBuilder {
inner: self.network,
})
.inner;
self
}
pub fn exporter<F>(mut self, configure: F) -> Self
where
F: FnOnce(ExporterConfigBuilder) -> ExporterConfigBuilder,
{
self.exporter = configure(ExporterConfigBuilder {
inner: self.exporter,
})
.inner;
self
}
pub fn build(mut self) -> Result<RumConfig> {
let config_address = self
.config_address
.ok_or(RumError::MissingConfig("config_address"))?;
let config_address = Url::parse(&config_address)?;
if !matches!(config_address.scheme(), "http" | "https") {
return Err(RumError::InvalidConfig {
field: "config_address",
message: "scheme must be http or https".to_string(),
});
}
let app_id = self.app_id.ok_or(RumError::MissingConfig("app_id"))?;
if app_id.trim().is_empty() {
return Err(RumError::InvalidConfig {
field: "app_id",
message: "must not be empty".to_string(),
});
}
if self.exporter.flush_interval.is_zero() {
return Err(RumError::InvalidConfig {
field: "exporter.flush_interval",
message: "must be greater than zero".to_string(),
});
}
if self.exporter.max_batch_size == 0 {
return Err(RumError::InvalidConfig {
field: "exporter.max_batch_size",
message: "must be greater than zero".to_string(),
});
}
if self.exporter.max_queue_size == 0 {
return Err(RumError::InvalidConfig {
field: "exporter.max_queue_size",
message: "must be greater than zero".to_string(),
});
}
if self.exporter.request_timeout.is_zero() {
return Err(RumError::InvalidConfig {
field: "exporter.request_timeout",
message: "must be greater than zero".to_string(),
});
}
self.network.excluded_hosts.insert(
crate::propagation::effective_target_address(&config_address).to_ascii_lowercase(),
);
Ok(RumConfig {
config_address,
app_id,
app_name: self.app_name,
app_version: self.app_version,
app_type: self
.app_type
.unwrap_or_else(|| default_platform_type().to_string()),
env: self.env.unwrap_or(RumEnv::Prod),
utdid: self.utdid,
user_id: self.user_id,
user_name: self.user_name,
user_tags: self.user_tags,
properties: self.properties,
device_name: self.device_name,
device_model: self.device_model,
device_brand: self.device_brand,
device_type: self
.device_type
.unwrap_or_else(|| default_platform_type().to_string()),
net_device_model: self
.net_device_model
.unwrap_or_else(|| "unknown".to_string()),
os_type: self
.os_type
.unwrap_or_else(|| default_platform_type().to_string()),
os_version: self.os_version,
network: self.network,
exporter: self.exporter,
})
}
}
pub struct NetworkConfigBuilder {
inner: NetworkConfig,
}
impl NetworkConfigBuilder {
pub fn enabled(mut self, value: bool) -> Self {
self.inner.enabled = value;
self
}
pub fn record_enabled(mut self, value: bool) -> Self {
self.inner.record_enabled = value;
self
}
pub fn tracing_enabled(mut self, value: bool) -> Self {
self.inner.tracing_enabled = value;
self
}
pub fn trace_parent(mut self) -> Self {
self.inner.propagation_mode = PropagationMode::TraceParent;
self
}
pub fn sw8(mut self) -> Self {
self.inner.propagation_mode = PropagationMode::Sw8;
self
}
pub fn should_record_request<F>(mut self, predicate: F) -> Self
where
F: Fn(&Url) -> bool + Send + Sync + 'static,
{
self.inner.should_record_request = Arc::new(predicate);
self
}
pub fn should_trace_request<F>(mut self, predicate: F) -> Self
where
F: Fn(&Url) -> bool + Send + Sync + 'static,
{
self.inner.should_trace_request = Arc::new(predicate);
self
}
pub fn exclude_host(mut self, host: impl Into<String>) -> Self {
self.inner
.excluded_hosts
.insert(host.into().to_ascii_lowercase());
self
}
}
pub struct ExporterConfigBuilder {
inner: ExporterConfig,
}
impl ExporterConfigBuilder {
pub fn flush_interval(mut self, value: Duration) -> Self {
self.inner.flush_interval = value;
self
}
pub fn max_batch_size(mut self, value: usize) -> Self {
self.inner.max_batch_size = value;
self
}
pub fn max_queue_size(mut self, value: usize) -> Self {
self.inner.max_queue_size = value;
self
}
pub fn request_timeout(mut self, value: Duration) -> Self {
self.inner.request_timeout = value;
self
}
pub fn cache_path(mut self, value: impl Into<PathBuf>) -> Self {
self.inner.cache_path = Some(value.into());
self
}
}