statsig_rust/observability/
observability_client_adapter.rs

1use std::{collections::HashMap, sync::Arc};
2
3use async_trait::async_trait;
4
5use crate::observability::ops_stats::OpsStatsEventObserver;
6
7use super::ops_stats::OpsStatsEvent;
8
9static HIGH_CARDINALITY_TAGS: &[&str] = &["lcut", "prev_lcut"];
10
11#[derive(Clone)]
12pub enum MetricType {
13    Increment,
14    Gauge,
15    Dist,
16}
17
18#[derive(Clone)]
19pub struct ObservabilityEvent {
20    pub metric_type: MetricType,
21    pub metric_name: String,
22    pub value: f64,
23    pub tags: Option<HashMap<String, String>>,
24}
25
26impl ObservabilityEvent {
27    pub fn new_event(
28        metric_type: MetricType,
29        metric_name: String,
30        value: f64,
31        tags: Option<HashMap<String, String>>,
32    ) -> OpsStatsEvent {
33        OpsStatsEvent::Observability(ObservabilityEvent {
34            metric_type,
35            metric_name,
36            value,
37            tags,
38        })
39    }
40}
41
42pub trait ObservabilityClient: Send + Sync + 'static + OpsStatsEventObserver {
43    fn init(&self);
44    fn increment(&self, metric_name: String, value: f64, tags: Option<HashMap<String, String>>);
45    fn gauge(&self, metric_name: String, value: f64, tags: Option<HashMap<String, String>>);
46    fn dist(&self, metric_name: String, value: f64, tags: Option<HashMap<String, String>>);
47    fn error(&self, tag: String, error: String);
48    fn should_enable_high_cardinality_for_this_tag(&self, tag: String) -> Option<bool>;
49    // This is needed since upper casting is not officially supported yet
50    // (WIP to support https://github.com/rust-lang/rust/issues/65991)
51    // For implementation, just return Self; should be sufficient
52    fn to_ops_stats_event_observer(self: Arc<Self>) -> Arc<dyn OpsStatsEventObserver>;
53}
54
55#[async_trait]
56impl<T: ObservabilityClient> OpsStatsEventObserver for T {
57    async fn handle_event(&self, event: OpsStatsEvent) {
58        match event {
59            OpsStatsEvent::Observability(data) => {
60                let tags: Option<HashMap<String, String>> = data.tags.map(|tags_unwrapped| {
61                    tags_unwrapped
62                        .into_iter()
63                        .filter(|(key, _)| {
64                            if HIGH_CARDINALITY_TAGS.contains(&key.as_str()) {
65                                self.should_enable_high_cardinality_for_this_tag(key.to_string())
66                                    .unwrap_or_default()
67                            } else {
68                                true
69                            }
70                        })
71                        .collect()
72                });
73                let metric_name = format!("statsig.sdk.{}", data.metric_name.clone());
74                match data.metric_type {
75                    MetricType::Increment => self.increment(metric_name, data.value, tags),
76                    MetricType::Gauge => self.gauge(metric_name, data.value, tags),
77                    MetricType::Dist => self.dist(metric_name, data.value, tags),
78                };
79            }
80            OpsStatsEvent::SDKError(error) => {
81                self.error(error.tag, error.info.to_string());
82            }
83            _ => {}
84        }
85    }
86}