statsig_rust/observability/
observability_client_adapter.rs1use std::{collections::HashMap, sync::Arc};
2
3use async_trait::async_trait;
4
5use crate::{observability::ops_stats::OpsStatsEventObserver, statsig_metadata::StatsigMetadata};
6
7use super::ops_stats::OpsStatsEvent;
8
9static HIGH_CARDINALITY_TAGS: &[&str] = &["lcut", "prev_lcut"];
10static SDK_EXCEPTION_COUNT_EXTRA_TAGS: &[&str] = &["request_path", "status_code"];
11
12#[derive(Clone)]
13pub enum MetricType {
14 Increment,
15 Gauge,
16 Dist,
17}
18
19#[derive(Clone)]
20pub struct ObservabilityEvent {
21 pub metric_type: MetricType,
22 pub metric_name: String,
23 pub value: f64,
24 pub tags: Option<HashMap<String, String>>,
25}
26
27impl ObservabilityEvent {
28 pub fn new_event(
29 metric_type: MetricType,
30 metric_name: String,
31 value: f64,
32 tags: Option<HashMap<String, String>>,
33 ) -> OpsStatsEvent {
34 OpsStatsEvent::Observability(ObservabilityEvent {
35 metric_type,
36 metric_name,
37 value,
38 tags,
39 })
40 }
41}
42
43fn add_default_metric_tags(mut tags: HashMap<String, String>) -> HashMap<String, String> {
44 let metadata = StatsigMetadata::get_metadata();
45 tags.insert("sdk_type".to_string(), metadata.sdk_type);
46 tags.insert("sdk_version".to_string(), metadata.sdk_version);
47 tags
48}
49
50pub trait ObservabilityClient: Send + Sync + 'static + OpsStatsEventObserver {
51 fn init(&self);
52 fn increment(&self, metric_name: String, value: f64, tags: Option<HashMap<String, String>>);
53 fn gauge(&self, metric_name: String, value: f64, tags: Option<HashMap<String, String>>);
54 fn dist(&self, metric_name: String, value: f64, tags: Option<HashMap<String, String>>);
55 fn error(&self, tag: String, error: String);
56 fn should_enable_high_cardinality_for_this_tag(&self, tag: String) -> Option<bool>;
57 fn to_ops_stats_event_observer(self: Arc<Self>) -> Arc<dyn OpsStatsEventObserver>;
61}
62
63#[async_trait]
64impl<T: ObservabilityClient> OpsStatsEventObserver for T {
65 async fn handle_event(&self, event: OpsStatsEvent) {
66 match event {
67 OpsStatsEvent::Observability(data) => {
68 let tags = data
69 .tags
70 .unwrap_or_default()
71 .into_iter()
72 .filter(|(key, _)| {
73 if HIGH_CARDINALITY_TAGS.contains(&key.as_str()) {
74 self.should_enable_high_cardinality_for_this_tag(key.to_string())
75 .unwrap_or_default()
76 } else {
77 true
78 }
79 })
80 .collect();
81 let tags = Some(add_default_metric_tags(tags));
82 let metric_name = format!("statsig.sdk.{}", data.metric_name.clone());
83 match data.metric_type {
84 MetricType::Increment => self.increment(metric_name, data.value, tags),
85 MetricType::Gauge => self.gauge(metric_name, data.value, tags),
86 MetricType::Dist => self.dist(metric_name, data.value, tags),
87 };
88 }
89 OpsStatsEvent::SDKError(error) => {
90 self.error(error.tag.clone(), error.info.to_string());
91 let mut tags = HashMap::from([
92 ("tag".to_string(), error.tag),
93 ("exception".to_string(), error.exception),
94 ]);
95
96 if let Some(extra) = error.extra {
97 for (key, value) in extra {
98 if SDK_EXCEPTION_COUNT_EXTRA_TAGS.contains(&key.as_str()) {
99 tags.insert(key, value);
100 }
101 }
102 }
103
104 self.increment(
105 "statsig.sdk.sdk_exceptions_count".to_string(),
106 1.0,
107 Some(add_default_metric_tags(tags)),
108 );
109 }
110 _ => {}
111 }
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn add_default_metric_tags_attaches_sdk_metadata() {
121 let metadata = StatsigMetadata::get_metadata();
122
123 let tags = add_default_metric_tags(HashMap::new());
124
125 assert_eq!(tags.get("sdk_type"), Some(&metadata.sdk_type));
126 assert_eq!(tags.get("sdk_version"), Some(&metadata.sdk_version));
127 }
128
129 #[test]
130 fn add_default_metric_tags_overwrites_call_site_sdk_metadata() {
131 let metadata = StatsigMetadata::get_metadata();
132 let tags = add_default_metric_tags(HashMap::from([
133 ("sdk_type".to_string(), "wrong".to_string()),
134 ("sdk_version".to_string(), "wrong".to_string()),
135 ]));
136
137 assert_eq!(tags.get("sdk_type"), Some(&metadata.sdk_type));
138 assert_eq!(tags.get("sdk_version"), Some(&metadata.sdk_version));
139 }
140}