statsig_rust/
statsig.rs

1use crate::evaluation::cmab_evaluator::{get_cmab_ranked_list, CMABRankedGroup};
2use crate::evaluation::country_lookup::CountryLookup;
3use crate::evaluation::dynamic_value::DynamicValue;
4use crate::evaluation::evaluation_details::EvaluationDetails;
5use crate::evaluation::evaluation_types::GateEvaluation;
6use crate::evaluation::evaluator::{Evaluator, SpecType};
7use crate::evaluation::evaluator_context::EvaluatorContext;
8use crate::evaluation::evaluator_result::{
9    result_to_dynamic_config_eval, result_to_experiment_eval, result_to_gate_eval,
10    result_to_layer_eval, EvaluatorResult,
11};
12use crate::evaluation::user_agent_parsing::UserAgentParser;
13use crate::event_logging::event_logger::{EventLogger, ExposureTrigger};
14use crate::event_logging::event_queue::queued_config_expo::EnqueueConfigExpoOp;
15use crate::event_logging::event_queue::queued_experiment_expo::EnqueueExperimentExpoOp;
16use crate::event_logging::event_queue::queued_gate_expo::EnqueueGateExpoOp;
17use crate::event_logging::event_queue::queued_layer_param_expo::EnqueueLayerParamExpoOp;
18use crate::event_logging::event_queue::queued_passthrough::EnqueuePassthroughOp;
19use crate::event_logging::statsig_event_internal::StatsigEventInternal;
20use crate::event_logging_adapter::EventLoggingAdapter;
21use crate::event_logging_adapter::StatsigHttpEventLoggingAdapter;
22use crate::gcir::gcir_formatter::GCIRFormatter;
23use crate::hashing::HashUtil;
24use crate::initialize_response::InitializeResponse;
25use crate::networking::NetworkError;
26use crate::observability::diagnostics_observer::DiagnosticsObserver;
27use crate::observability::observability_client_adapter::{MetricType, ObservabilityEvent};
28use crate::observability::ops_stats::{OpsStatsForInstance, OPS_STATS};
29use crate::observability::sdk_errors_observer::{ErrorBoundaryEvent, SDKErrorsObserver};
30use crate::output_logger::{initialize_output_logger, shutdown_output_logger};
31use crate::persistent_storage::persistent_values_manager::PersistentValuesManager;
32use crate::sdk_diagnostics::diagnostics::{ContextType, Diagnostics};
33use crate::sdk_diagnostics::marker::{ActionType, KeyType, Marker};
34use crate::sdk_event_emitter::SdkEventEmitter;
35use crate::spec_store::SpecStore;
36use crate::specs_adapter::{StatsigCustomizedSpecsAdapter, StatsigHttpSpecsAdapter};
37use crate::statsig_err::StatsigErr;
38use crate::statsig_metadata::StatsigMetadata;
39use crate::statsig_options::StatsigOptions;
40use crate::statsig_runtime::StatsigRuntime;
41use crate::statsig_type_factories::{
42    make_dynamic_config, make_experiment, make_feature_gate, make_layer,
43};
44use crate::statsig_types::{DynamicConfig, Experiment, FeatureGate, Layer, ParameterStore};
45use crate::user::StatsigUserInternal;
46use crate::{
47    dyn_value, log_d, log_e, log_w, read_lock_or_else, ClientInitResponseOptions,
48    GCIRResponseFormat, IdListsAdapter, ObservabilityClient, OpsStatsEventObserver,
49    OverrideAdapter, SpecsAdapter, SpecsInfo, SpecsSource, SpecsUpdateListener,
50    StatsigHttpIdListsAdapter, StatsigLocalOverrideAdapter, StatsigUser,
51};
52use crate::{
53    log_error_to_statsig_and_console,
54    statsig_core_api_options::{
55        DynamicConfigEvaluationOptions, ExperimentEvaluationOptions, FeatureGateEvaluationOptions,
56        LayerEvaluationOptions, ParameterStoreEvaluationOptions,
57    },
58};
59use chrono::Utc;
60use parking_lot::Mutex;
61use serde::de::DeserializeOwned;
62use serde::Serialize;
63use serde_json::json;
64use serde_json::Value;
65use std::borrow::Cow;
66use std::collections::HashMap;
67use std::sync::atomic::{AtomicBool, Ordering};
68use std::sync::{Arc, Weak};
69use std::time::{Duration, Instant};
70use tokio::time::sleep;
71use tokio::try_join;
72
73const TAG: &str = stringify!(Statsig);
74const ERROR_SDK_KEY: &str = "__STATSIG_ERROR_SDK_KEY__";
75const INIT_IP_TAG: &str = "INIT_COUNTRY_LOOKUP";
76const INIT_UA_TAG: &str = "INIT_UA";
77
78lazy_static::lazy_static! {
79    static ref SHARED_INSTANCE: Mutex<Option<Arc<Statsig>>> = Mutex::new(None);
80}
81
82pub struct Statsig {
83    pub statsig_runtime: Arc<StatsigRuntime>,
84    pub options: Arc<StatsigOptions>,
85    pub event_emitter: SdkEventEmitter,
86
87    sdk_key: String,
88    event_logger: Arc<EventLogger>,
89    specs_adapter: SpecsAdapterHousing,
90    event_logging_adapter: Arc<dyn EventLoggingAdapter>,
91    id_lists_adapter: IdListsAdapterHousing,
92    override_adapter: Option<Arc<dyn OverrideAdapter>>,
93    spec_store: Arc<SpecStore>,
94    hashing: Arc<HashUtil>,
95    gcir_formatter: Arc<GCIRFormatter>,
96    statsig_environment: Option<HashMap<String, DynamicValue>>,
97    fallback_environment: Mutex<Option<HashMap<String, DynamicValue>>>,
98    ops_stats: Arc<OpsStatsForInstance>,
99    error_observer: Arc<dyn OpsStatsEventObserver>,
100    diagnostics_observer: Arc<dyn OpsStatsEventObserver>,
101    background_tasks_started: Arc<AtomicBool>,
102    persistent_values_manager: Option<Arc<PersistentValuesManager>>,
103    initialize_details: Mutex<InitializeDetails>,
104}
105
106pub struct StatsigContext {
107    pub sdk_key: String,
108    pub options: Arc<StatsigOptions>,
109    pub local_override_adapter: Option<Arc<dyn OverrideAdapter>>,
110    pub error_observer: Arc<dyn OpsStatsEventObserver>,
111    pub diagnostics_observer: Arc<dyn OpsStatsEventObserver>,
112    pub spec_store: Arc<SpecStore>,
113}
114
115#[derive(Debug, Clone, Serialize)]
116pub struct FailureDetails {
117    pub reason: String,
118    pub error: Option<StatsigErr>,
119}
120
121#[derive(Debug, Clone, Serialize)]
122pub struct InitializeDetails {
123    pub duration: f64,
124    pub init_success: bool,
125    pub is_config_spec_ready: bool,
126    pub is_id_list_ready: Option<bool>,
127    pub source: SpecsSource,
128    pub failure_details: Option<FailureDetails>,
129    pub spec_source_api: Option<String>,
130}
131
132impl Default for InitializeDetails {
133    fn default() -> Self {
134        InitializeDetails {
135            duration: 0.0,
136            init_success: false,
137            is_config_spec_ready: false,
138            is_id_list_ready: None,
139            source: SpecsSource::Uninitialized,
140            failure_details: None,
141            spec_source_api: None,
142        }
143    }
144}
145
146impl InitializeDetails {
147    pub fn from_error(reason: &str, error: Option<StatsigErr>) -> Self {
148        InitializeDetails {
149            duration: 0.0,
150            init_success: false,
151            is_config_spec_ready: false,
152            is_id_list_ready: None,
153            source: SpecsSource::Uninitialized,
154            failure_details: Some(FailureDetails {
155                reason: reason.to_string(),
156                error,
157            }),
158            spec_source_api: None,
159        }
160    }
161}
162
163impl Drop for Statsig {
164    fn drop(&mut self) {
165        self.event_logger.force_shutdown();
166
167        if let Some(adapter) = &self.id_lists_adapter.as_default_adapter {
168            adapter.force_shutdown();
169        }
170
171        if let Some(adapter) = &self.specs_adapter.as_default_adapter {
172            adapter.force_shutdown();
173        }
174
175        shutdown_output_logger();
176
177        log_d!(TAG, "Statsig instance dropped");
178    }
179}
180
181impl Statsig {
182    pub fn new(sdk_key: &str, options: Option<Arc<StatsigOptions>>) -> Self {
183        let statsig_runtime = StatsigRuntime::get_runtime();
184        let options = options.map(|o| o.validate_and_fix()).unwrap_or_default();
185
186        initialize_output_logger(
187            &options.output_log_level,
188            options.output_logger_provider.clone(),
189        );
190
191        let hashing = Arc::new(HashUtil::new());
192
193        let specs_adapter = initialize_specs_adapter(sdk_key, &options, &hashing);
194        let id_lists_adapter = initialize_id_lists_adapter(sdk_key, &options);
195        let event_logging_adapter = initialize_event_logging_adapter(sdk_key, &options);
196        let override_adapter = match options.override_adapter.as_ref() {
197            Some(adapter) => Some(Arc::clone(adapter)),
198            None => Some(Arc::new(StatsigLocalOverrideAdapter::new()) as Arc<dyn OverrideAdapter>),
199        };
200
201        let event_logger =
202            EventLogger::new(sdk_key, &options, &event_logging_adapter, &statsig_runtime);
203
204        let diagnostics = Arc::new(Diagnostics::new(event_logger.clone(), sdk_key));
205        let diagnostics_observer: Arc<dyn OpsStatsEventObserver> =
206            Arc::new(DiagnosticsObserver::new(diagnostics));
207        let error_observer: Arc<dyn OpsStatsEventObserver> =
208            Arc::new(SDKErrorsObserver::new(sdk_key, &options));
209
210        let ops_stats = setup_ops_stats(
211            sdk_key,
212            statsig_runtime.clone(),
213            &error_observer,
214            &diagnostics_observer,
215            &options.observability_client,
216        );
217
218        let spec_store = Arc::new(SpecStore::new(
219            sdk_key,
220            hashing.sha256(sdk_key),
221            statsig_runtime.clone(),
222            options.data_store.clone(),
223        ));
224
225        let environment = options
226            .environment
227            .as_ref()
228            .map(|env| HashMap::from([("tier".into(), dyn_value!(env.as_str()))]));
229
230        let persistent_values_manager = options.persistent_storage.clone().map(|storage| {
231            Arc::new(PersistentValuesManager {
232                persistent_storage: storage,
233            })
234        });
235
236        StatsigMetadata::update_service_name(options.service_name.clone());
237
238        Statsig {
239            sdk_key: sdk_key.to_string(),
240            options,
241            gcir_formatter: Arc::new(GCIRFormatter::new(
242                &spec_store,
243                &override_adapter,
244                &ops_stats,
245            )),
246            hashing,
247            statsig_environment: environment,
248            fallback_environment: Mutex::new(None),
249            override_adapter,
250            spec_store,
251            specs_adapter,
252            event_logging_adapter,
253            event_logger,
254            id_lists_adapter,
255            statsig_runtime,
256            ops_stats,
257            error_observer,
258            diagnostics_observer,
259            background_tasks_started: Arc::new(AtomicBool::new(false)),
260            persistent_values_manager,
261            initialize_details: Mutex::new(InitializeDetails::default()),
262            event_emitter: SdkEventEmitter::default(),
263        }
264    }
265
266    /***
267     *  Initializes the Statsig client and returns an error if initialization fails.
268     *
269     *  This method performs the client initialization and returns `Ok(())` if successful.
270     *  If the initialization completes with failure details, it returns a [`StatsigErr`]
271     *  describing the failure.
272     *
273     *  For detailed information about the initialization process—regardless of success or failure—
274     *  use [`initialize_with_details`] instead.
275     *
276     *  # Errors
277     *
278     *  Returns a [`StatsigErr`] if the client fails to initialize successfully.
279     */
280    pub async fn initialize(&self) -> Result<(), StatsigErr> {
281        let details = self.initialize_with_details().await?;
282
283        if let Some(failure_details) = details.failure_details {
284            Err(failure_details
285                .error
286                .unwrap_or(StatsigErr::InitializationError(failure_details.reason)))
287        } else {
288            Ok(())
289        }
290    }
291
292    /***
293     *  Initializes the Statsig client and returns detailed information about the process.
294     *
295     *  This method returns a [`StatsigInitializeDetails`] struct, which includes metadata such as
296     *  the success status, initialization source, and any failure details. Even if initialization
297     *  fails, this method does not return an error; instead, the `init_success` field will be `false`
298     *  and `failure_details` may be populated.
299     ***/
300    pub async fn initialize_with_details(&self) -> Result<InitializeDetails, StatsigErr> {
301        self.ops_stats.add_marker(
302            Marker::new(KeyType::Overall, ActionType::Start, None),
303            Some(ContextType::Initialize),
304        );
305
306        let init_details = if let Some(timeout_ms) = self.options.init_timeout_ms {
307            self.apply_timeout_to_init(timeout_ms).await
308        } else {
309            self.initialize_impl_with_details().await
310        };
311        self.log_init_details(&init_details);
312        if let Ok(details) = &init_details {
313            match self.initialize_details.try_lock_for(Duration::from_secs(5)) {
314                Some(mut curr_init_details) => {
315                    *curr_init_details = details.clone();
316                }
317                None => {
318                    log_e!(TAG, "Failed to lock initialize_details");
319                }
320            }
321        }
322        init_details
323    }
324
325    pub fn get_initialize_details(&self) -> InitializeDetails {
326        match self.initialize_details.try_lock_for(Duration::from_secs(5)) {
327            Some(details) => details.clone(),
328            None => InitializeDetails::from_error(
329                "Failed to lock initialize_details",
330                Some(StatsigErr::LockFailure(
331                    "Failed to lock initialize_details".to_string(),
332                )),
333            ),
334        }
335    }
336
337    pub fn is_initialized(&self) -> bool {
338        match self.initialize_details.try_lock_for(Duration::from_secs(5)) {
339            Some(details) => details.init_success,
340            None => false,
341        }
342    }
343
344    pub async fn shutdown(&self) -> Result<(), StatsigErr> {
345        self.shutdown_with_timeout(Duration::from_secs(3)).await
346    }
347
348    pub async fn shutdown_with_timeout(&self, timeout: Duration) -> Result<(), StatsigErr> {
349        log_d!(
350            TAG,
351            "Shutting down Statsig with timeout {}ms",
352            timeout.as_millis()
353        );
354
355        let start = Instant::now();
356        let shutdown_result = tokio::select! {
357            () = tokio::time::sleep(timeout) => {
358                log_w!(TAG, "Statsig shutdown timed out. {}", start.elapsed().as_millis());
359                Err(StatsigErr::ShutdownFailure(
360                    "Shutdown timed out".to_string()
361                ))
362            }
363            sub_result = async {
364                let id_list_shutdown: Pin<Box<_>> = if let Some(adapter) = &self.id_lists_adapter.inner {
365                    adapter.shutdown(timeout)
366                } else {
367                    Box::pin(async { Ok(()) })
368                };
369
370                shutdown_output_logger();
371
372                try_join!(
373                    id_list_shutdown,
374                    self.event_logger.shutdown(),
375                    self.specs_adapter.inner.shutdown(timeout, &self.statsig_runtime),
376                )
377            } => {
378                match sub_result {
379                    Ok(_) => {
380                        log_d!(TAG, "All Statsig tasks shutdown successfully");
381                        Ok(())
382                    }
383                    Err(e) => {
384                        log_w!(TAG, "Error during shutdown: {:?}", e);
385                        Err(e)
386                    }
387                }
388            }
389        };
390
391        self.statsig_runtime.shutdown();
392        shutdown_result
393    }
394
395    async fn start_background_tasks(
396        statsig_runtime: Arc<StatsigRuntime>,
397        id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
398        specs_adapter: Arc<dyn SpecsAdapter>,
399        ops_stats: Arc<OpsStatsForInstance>,
400        bg_tasks_started: Arc<AtomicBool>,
401    ) -> bool {
402        if bg_tasks_started.load(Ordering::SeqCst) {
403            return true;
404        }
405
406        let mut success = true;
407
408        if let Some(adapter) = &id_lists_adapter {
409            if let Err(e) = adapter
410                .clone()
411                .schedule_background_sync(&statsig_runtime)
412                .await
413            {
414                success = false;
415                log_w!(TAG, "Failed to schedule idlist background job {}", e);
416            }
417        }
418
419        if let Err(e) = specs_adapter
420            .clone()
421            .schedule_background_sync(&statsig_runtime)
422            .await
423        {
424            success = false;
425            log_error_to_statsig_and_console!(
426                ops_stats,
427                TAG,
428                StatsigErr::SpecsAdapterSkipPoll(format!(
429                    "Failed to schedule specs adapter background job: {e}"
430                ))
431            );
432        }
433
434        bg_tasks_started.store(true, Ordering::SeqCst);
435
436        success
437    }
438
439    async fn apply_timeout_to_init(
440        &self,
441        timeout_ms: u64,
442    ) -> Result<InitializeDetails, StatsigErr> {
443        let timeout = Duration::from_millis(timeout_ms);
444
445        let init_future = self.initialize_impl_with_details();
446        let timeout_future = sleep(timeout);
447
448        let statsig_runtime = self.statsig_runtime.clone();
449        let id_lists_adapter = self.id_lists_adapter.inner.clone();
450        let specs_adapter = self.specs_adapter.inner.clone();
451        let ops_stats = self.ops_stats.clone();
452        let background_tasks_started = self.background_tasks_started.clone();
453        // Create another clone specifically for the closure
454        let statsig_runtime_for_closure = statsig_runtime.clone();
455
456        tokio::select! {
457            result = init_future => {
458                result
459            },
460            _ = timeout_future => {
461                statsig_runtime.spawn(
462                    "start_background_tasks",
463                    |_shutdown_notify| async move {
464                        Self::start_background_tasks(
465                            statsig_runtime_for_closure,
466                            id_lists_adapter,
467                            specs_adapter,
468                            ops_stats,
469                            background_tasks_started,
470                        ).await;
471                    }
472                )?;
473                Ok(self.timeout_failure(timeout_ms))
474            },
475        }
476    }
477
478    async fn initialize_impl_with_details(&self) -> Result<InitializeDetails, StatsigErr> {
479        let start_time = Instant::now();
480        self.spec_store.set_source(SpecsSource::Loading);
481        self.specs_adapter.inner.initialize(self.spec_store.clone());
482
483        let mut error_message = None;
484        let mut id_list_ready = None;
485
486        let init_country_lookup = if !self.options.disable_country_lookup.unwrap_or_default() {
487            Some(self.statsig_runtime.spawn(INIT_IP_TAG, |_| async {
488                CountryLookup::load_country_lookup();
489            }))
490        } else {
491            None
492        };
493
494        let init_ua = if !self.options.disable_user_agent_parsing.unwrap_or_default() {
495            Some(self.statsig_runtime.spawn(INIT_UA_TAG, |_| async {
496                UserAgentParser::load_parser();
497            }))
498        } else {
499            None
500        };
501
502        let init_res = match self
503            .specs_adapter
504            .inner
505            .clone()
506            .start(&self.statsig_runtime)
507            .await
508        {
509            Ok(()) => Ok(()),
510            Err(e) => {
511                self.spec_store.set_source(SpecsSource::NoValues);
512                error_message = Some(format!("Failed to start specs adapter: {e}"));
513                Err(e)
514            }
515        };
516
517        if let Some(adapter) = &self.id_lists_adapter.inner {
518            match adapter
519                .clone()
520                .start(&self.statsig_runtime, self.spec_store.clone())
521                .await
522            {
523                Ok(()) => {
524                    id_list_ready = Some(true);
525                }
526                Err(e) => {
527                    id_list_ready = Some(false);
528                    error_message.get_or_insert_with(|| format!("Failed to sync ID lists: {e}"));
529                }
530            }
531            if let Err(e) = adapter
532                .clone()
533                .schedule_background_sync(&self.statsig_runtime)
534                .await
535            {
536                log_w!(TAG, "Failed to schedule id_list background job {}", e);
537            }
538        }
539
540        if let Err(e) = self
541            .event_logging_adapter
542            .clone()
543            .start(&self.statsig_runtime)
544            .await
545        {
546            log_error_to_statsig_and_console!(
547                self.ops_stats.clone(),
548                TAG,
549                StatsigErr::UnstartedAdapter(format!("Failed to start event logging adapter: {e}"))
550            );
551        }
552
553        let spec_info = self.spec_store.get_current_specs_info();
554        let duration = start_time.elapsed().as_millis() as f64;
555
556        self.set_default_environment_from_server();
557
558        if self.options.wait_for_country_lookup_init.unwrap_or(false) {
559            match init_country_lookup {
560                Some(Ok(task_id)) => {
561                    let _ = self
562                        .statsig_runtime
563                        .await_join_handle(INIT_IP_TAG, &task_id)
564                        .await;
565                }
566                Some(Err(e)) => {
567                    log_error_to_statsig_and_console!(
568                        self.ops_stats.clone(),
569                        TAG,
570                        StatsigErr::UnstartedAdapter(format!(
571                            "Failed to spawn country lookup task: {e}"
572                        ))
573                    );
574                }
575                _ => {}
576            }
577        }
578        if self.options.wait_for_user_agent_init.unwrap_or(false) {
579            match init_ua {
580                Some(Ok(task_id)) => {
581                    let _ = self
582                        .statsig_runtime
583                        .await_join_handle(INIT_UA_TAG, &task_id)
584                        .await;
585                }
586                Some(Err(e)) => {
587                    log_error_to_statsig_and_console!(
588                        self.ops_stats.clone(),
589                        TAG,
590                        StatsigErr::UnstartedAdapter(format!(
591                            "Failed to spawn user agent parser task: {e}"
592                        ))
593                    );
594                }
595                _ => {}
596            }
597        }
598
599        let error = init_res.clone().err();
600
601        let success = Self::start_background_tasks(
602            self.statsig_runtime.clone(),
603            self.id_lists_adapter.inner.clone(),
604            self.specs_adapter.inner.clone(),
605            self.ops_stats.clone(),
606            self.background_tasks_started.clone(),
607        )
608        .await;
609
610        Ok(self.construct_initialize_details(success, duration, spec_info, id_list_ready, error))
611    }
612
613    fn construct_initialize_details(
614        &self,
615        init_success: bool,
616        duration: f64,
617        specs_info: SpecsInfo,
618        is_id_list_ready: Option<bool>,
619        error: Option<StatsigErr>,
620    ) -> InitializeDetails {
621        let is_config_spec_ready = matches!(specs_info.lcut, Some(v) if v != 0);
622
623        let failure_details =
624            if let Some(StatsigErr::NetworkError(NetworkError::DisableNetworkOn(_))) = error {
625                None
626            } else {
627                error.as_ref().map(|e| FailureDetails {
628                    reason: e.to_string(),
629                    error: Some(e.clone()),
630                })
631            };
632
633        InitializeDetails {
634            init_success,
635            is_config_spec_ready,
636            is_id_list_ready,
637            source: specs_info.source.clone(),
638            failure_details,
639            duration,
640            spec_source_api: specs_info.source_api.clone(),
641        }
642    }
643
644    fn timeout_failure(&self, timeout_ms: u64) -> InitializeDetails {
645        InitializeDetails {
646            init_success: false,
647            is_config_spec_ready: false,
648            is_id_list_ready: None,
649            source: SpecsSource::Uninitialized,
650            failure_details: Some(FailureDetails {
651                reason: "Initialization timed out".to_string(),
652                error: None,
653            }),
654            duration: timeout_ms as f64,
655            spec_source_api: None,
656        }
657    }
658
659    fn log_init_details(&self, init_details: &Result<InitializeDetails, StatsigErr>) {
660        match init_details {
661            Ok(details) => {
662                self.log_init_finish(
663                    details.init_success,
664                    &None,
665                    &details.duration,
666                    &self.spec_store.get_current_specs_info(),
667                );
668                if let Some(failure) = &details.failure_details {
669                    log_error_to_statsig_and_console!(
670                        self.ops_stats,
671                        TAG,
672                        StatsigErr::InitializationError(failure.reason.clone())
673                    );
674                }
675            }
676            Err(err) => {
677                // we store errors on init details so we should never return error and thus do not need to log
678                log_w!(TAG, "Initialization error: {:?}", err);
679            }
680        }
681    }
682
683    pub fn get_context(&self) -> StatsigContext {
684        StatsigContext {
685            sdk_key: self.sdk_key.clone(),
686            options: self.options.clone(),
687            local_override_adapter: self.override_adapter.clone(),
688            error_observer: self.error_observer.clone(),
689            diagnostics_observer: self.diagnostics_observer.clone(),
690            spec_store: self.spec_store.clone(),
691        }
692    }
693
694    pub fn log_event(
695        &self,
696        user: &StatsigUser,
697        event_name: &str,
698        value: Option<String>,
699        metadata: Option<HashMap<String, String>>,
700    ) {
701        let user_internal = self.internalize_user(user);
702
703        self.event_logger.enqueue(EnqueuePassthroughOp {
704            event: StatsigEventInternal::new_custom_event(
705                user_internal.to_loggable(),
706                event_name.to_string(),
707                value.map(|v| json!(v)),
708                metadata,
709            ),
710        });
711    }
712
713    pub fn log_event_with_number(
714        &self,
715        user: &StatsigUser,
716        event_name: &str,
717        value: Option<f64>,
718        metadata: Option<HashMap<String, String>>,
719    ) {
720        let user_internal = self.internalize_user(user);
721        self.event_logger.enqueue(EnqueuePassthroughOp {
722            event: StatsigEventInternal::new_custom_event(
723                user_internal.to_loggable(),
724                event_name.to_string(),
725                value.map(|v| json!(v)),
726                metadata,
727            ),
728        });
729    }
730
731    pub fn log_layer_param_exposure_with_layer_json(
732        &self,
733        layer_json: String,
734        parameter_name: String,
735    ) {
736        let layer = match serde_json::from_str::<Layer>(&layer_json) {
737            Ok(layer) => layer,
738            Err(e) => {
739                log_error_to_statsig_and_console!(
740                    self.ops_stats.clone(),
741                    TAG,
742                    StatsigErr::ShutdownFailure(e.to_string())
743                );
744                return;
745            }
746        };
747
748        self.log_layer_param_exposure_with_layer(layer, parameter_name);
749    }
750
751    pub fn log_layer_param_exposure_with_layer(&self, layer: Layer, parameter_name: String) {
752        if layer.__disable_exposure {
753            self.event_logger.increment_non_exposure_checks(&layer.name);
754            return;
755        }
756
757        self.event_logger
758            .enqueue(EnqueueLayerParamExpoOp::LayerOwned(
759                Utc::now().timestamp_millis() as u64,
760                Box::new(layer),
761                parameter_name,
762                ExposureTrigger::Auto,
763            ));
764    }
765
766    pub async fn flush_events(&self) {
767        let _ = self.event_logger.flush_all_pending_events().await;
768    }
769
770    pub fn get_client_init_response(&self, user: &StatsigUser) -> InitializeResponse {
771        self.get_client_init_response_with_options(user, self.gcir_formatter.get_default_options())
772    }
773
774    pub fn get_client_init_response_with_options(
775        &self,
776        user: &StatsigUser,
777        options: &ClientInitResponseOptions,
778    ) -> InitializeResponse {
779        let user_internal = self.internalize_user(user);
780        self.gcir_formatter
781            .get_as_v1_format(user_internal, &self.hashing, options)
782    }
783
784    pub fn get_client_init_response_as_string(&self, user: &StatsigUser) -> String {
785        serde_json::to_string(&self.get_client_init_response(user)).unwrap_or_default()
786    }
787
788    pub fn get_client_init_response_with_options_as_string(
789        &self,
790        user: &StatsigUser,
791        options: &ClientInitResponseOptions,
792    ) -> String {
793        let user_internal = self.internalize_user(user);
794        let response = match options.response_format {
795            Some(GCIRResponseFormat::InitializeWithSecondaryExposureMapping) => {
796                json!(self
797                    .gcir_formatter
798                    .get_as_v2_format(user_internal, &self.hashing, options))
799            }
800            _ => json!(self
801                .gcir_formatter
802                .get_as_v1_format(user_internal, &self.hashing, options)),
803        };
804
805        json!(response).to_string()
806    }
807
808    pub fn get_string_parameter_from_store(
809        &self,
810        user: &StatsigUser,
811        parameter_store_name: &str,
812        parameter_name: &str,
813        fallback: Option<String>,
814        options: Option<ParameterStoreEvaluationOptions>,
815    ) -> Option<String> {
816        self.get_parameter_from_store(
817            user,
818            parameter_store_name,
819            parameter_name,
820            fallback,
821            options,
822        )
823    }
824
825    pub fn get_boolean_parameter_from_store(
826        &self,
827        user: &StatsigUser,
828        parameter_store_name: &str,
829        parameter_name: &str,
830        fallback: Option<bool>,
831        options: Option<ParameterStoreEvaluationOptions>,
832    ) -> Option<bool> {
833        self.get_parameter_from_store(
834            user,
835            parameter_store_name,
836            parameter_name,
837            fallback,
838            options,
839        )
840    }
841
842    pub fn get_float_parameter_from_store(
843        &self,
844        user: &StatsigUser,
845        parameter_store_name: &str,
846        parameter_name: &str,
847        fallback: Option<f64>,
848        options: Option<ParameterStoreEvaluationOptions>,
849    ) -> Option<f64> {
850        self.get_parameter_from_store(
851            user,
852            parameter_store_name,
853            parameter_name,
854            fallback,
855            options,
856        )
857    }
858
859    pub fn get_integer_parameter_from_store(
860        &self,
861        user: &StatsigUser,
862        parameter_store_name: &str,
863        parameter_name: &str,
864        fallback: Option<i64>,
865        options: Option<ParameterStoreEvaluationOptions>,
866    ) -> Option<i64> {
867        self.get_parameter_from_store(
868            user,
869            parameter_store_name,
870            parameter_name,
871            fallback,
872            options,
873        )
874    }
875
876    pub fn get_array_parameter_from_store(
877        &self,
878        user: &StatsigUser,
879        parameter_store_name: &str,
880        parameter_name: &str,
881        fallback: Option<Vec<Value>>,
882        options: Option<ParameterStoreEvaluationOptions>,
883    ) -> Option<Vec<Value>> {
884        self.get_parameter_from_store(
885            user,
886            parameter_store_name,
887            parameter_name,
888            fallback,
889            options,
890        )
891    }
892
893    pub fn get_object_parameter_from_store(
894        &self,
895        user: &StatsigUser,
896        parameter_store_name: &str,
897        parameter_name: &str,
898        fallback: Option<HashMap<String, Value>>,
899        options: Option<ParameterStoreEvaluationOptions>,
900    ) -> Option<HashMap<String, Value>> {
901        self.get_parameter_from_store(
902            user,
903            parameter_store_name,
904            parameter_name,
905            fallback,
906            options,
907        )
908    }
909
910    pub fn get_parameter_from_store<T: DeserializeOwned>(
911        &self,
912        user: &StatsigUser,
913        parameter_store_name: &str,
914        parameter_name: &str,
915        fallback: Option<T>,
916        options: Option<ParameterStoreEvaluationOptions>,
917    ) -> Option<T> {
918        let store = self
919            .get_parameter_store_with_options(parameter_store_name, options.unwrap_or_default());
920        match fallback {
921            Some(fallback) => Some(store.get(user, parameter_name, fallback)),
922            None => store.get_opt(user, parameter_name),
923        }
924    }
925
926    pub fn get_parameter_store(&self, parameter_store_name: &str) -> ParameterStore {
927        self.get_parameter_store_with_options(
928            parameter_store_name,
929            ParameterStoreEvaluationOptions::default(),
930        )
931    }
932
933    pub fn get_parameter_store_with_options(
934        &self,
935        parameter_store_name: &str,
936        options: ParameterStoreEvaluationOptions,
937    ) -> ParameterStore {
938        self.event_logger
939            .increment_non_exposure_checks(parameter_store_name);
940
941        let data = read_lock_or_else!(self.spec_store.data, {
942            log_error_to_statsig_and_console!(
943                self.ops_stats.clone(),
944                TAG,
945                StatsigErr::LockFailure(
946                    "Failed to acquire read lock for spec store data".to_string()
947                )
948            );
949            return ParameterStore {
950                name: parameter_store_name.to_string(),
951                parameters: HashMap::new(),
952                details: EvaluationDetails::unrecognized_no_data(),
953                options,
954                _statsig_ref: self,
955            };
956        });
957
958        let stores = &data.values.param_stores;
959        let store = match stores {
960            Some(stores) => stores.get(parameter_store_name),
961            None => {
962                return ParameterStore {
963                    name: parameter_store_name.to_string(),
964                    parameters: HashMap::new(),
965                    details: EvaluationDetails::unrecognized(&data),
966                    options,
967                    _statsig_ref: self,
968                };
969            }
970        };
971        match store {
972            Some(store) => ParameterStore {
973                name: parameter_store_name.to_string(),
974                parameters: store.parameters.clone(),
975                details: EvaluationDetails::recognized(&data, &EvaluatorResult::default()),
976                options,
977                _statsig_ref: self,
978            },
979            None => ParameterStore {
980                name: parameter_store_name.to_string(),
981                parameters: HashMap::new(),
982                details: EvaluationDetails::unrecognized(&data),
983                options,
984                _statsig_ref: self,
985            },
986        }
987    }
988}
989
990// -------------------------
991//   User Store Functions
992// -------------------------
993
994impl Statsig {
995    pub fn identify(&self, user: &StatsigUser) {
996        let user_internal = self.internalize_user(user);
997
998        self.event_logger.enqueue(EnqueuePassthroughOp {
999            event: StatsigEventInternal::new_custom_event(
1000                user_internal.to_loggable(),
1001                "statsig::identify".to_string(),
1002                None,
1003                None,
1004            ),
1005        });
1006    }
1007}
1008
1009// -------------------------
1010//   CMAB Functions
1011// -------------------------
1012
1013impl Statsig {
1014    pub fn get_cmab_ranked_groups(
1015        &self,
1016        user: &StatsigUser,
1017        cmab_name: &str,
1018    ) -> Vec<CMABRankedGroup> {
1019        self.event_logger.increment_non_exposure_checks(cmab_name);
1020
1021        let data = read_lock_or_else!(self.spec_store.data, {
1022            log_error_to_statsig_and_console!(
1023                self.ops_stats.clone(),
1024                TAG,
1025                StatsigErr::LockFailure(
1026                    "Failed to acquire read lock for spec store data".to_string()
1027                )
1028            );
1029            return vec![];
1030        });
1031        let user_internal = self.internalize_user(user);
1032        get_cmab_ranked_list(
1033            &mut EvaluatorContext::new(
1034                &user_internal,
1035                &data,
1036                &self.hashing,
1037                data.values.app_id.as_ref(),
1038                self.override_adapter.as_ref(),
1039            ),
1040            cmab_name,
1041        )
1042    }
1043
1044    pub fn log_cmab_exposure_for_group(
1045        &self,
1046        user: &StatsigUser,
1047        cmab_name: &str,
1048        group_id: String,
1049    ) {
1050        let user_internal = self.internalize_user(user);
1051
1052        let mut experiment = self.get_experiment_impl(&user_internal, cmab_name);
1053        experiment.rule_id = group_id;
1054
1055        self.event_logger.enqueue(EnqueueExperimentExpoOp {
1056            exposure_time: Utc::now().timestamp_millis() as u64,
1057            user: &user_internal,
1058            experiment: &experiment,
1059            trigger: ExposureTrigger::Manual,
1060        });
1061    }
1062}
1063
1064// -------------------------
1065//   Shared Instance Functions
1066// -------------------------
1067
1068impl Statsig {
1069    pub fn shared() -> Arc<Statsig> {
1070        let lock = match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
1071            Some(lock) => lock,
1072            None => {
1073                log_e!(
1074                    TAG,
1075                    "Statsig::shared() mutex error: Failed to lock SHARED_INSTANCE"
1076                );
1077                return Arc::new(Statsig::new(ERROR_SDK_KEY, None));
1078            }
1079        };
1080
1081        match lock.as_ref() {
1082            Some(statsig) => statsig.clone(),
1083            None => {
1084                log_e!(
1085                    TAG,
1086                    "Statsig::shared() called, but no instance has been set with Statsig::new_shared(...)"
1087                );
1088                Arc::new(Statsig::new(ERROR_SDK_KEY, None))
1089            }
1090        }
1091    }
1092
1093    pub fn new_shared(
1094        sdk_key: &str,
1095        options: Option<Arc<StatsigOptions>>,
1096    ) -> Result<Arc<Statsig>, StatsigErr> {
1097        match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
1098            Some(mut lock) => {
1099                if lock.is_some() {
1100                    let message = "Statsig shared instance already exists. Call Statsig::remove_shared() before creating a new instance.";
1101                    log_e!(TAG, "{}", message);
1102                    return Err(StatsigErr::SharedInstanceFailure(message.to_string()));
1103                }
1104
1105                let statsig = Arc::new(Statsig::new(sdk_key, options));
1106                *lock = Some(statsig.clone());
1107                Ok(statsig)
1108            }
1109            None => {
1110                let message = "Statsig::new_shared() mutex error: Failed to lock SHARED_INSTANCE";
1111                log_e!(TAG, "{}", message);
1112                Err(StatsigErr::SharedInstanceFailure(message.to_string()))
1113            }
1114        }
1115    }
1116
1117    pub fn remove_shared() {
1118        match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
1119            Some(mut lock) => {
1120                *lock = None;
1121            }
1122            None => {
1123                log_e!(
1124                    TAG,
1125                    "Statsig::remove_shared() mutex error: Failed to lock SHARED_INSTANCE"
1126                );
1127            }
1128        }
1129    }
1130
1131    pub fn has_shared_instance() -> bool {
1132        match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
1133            Some(lock) => lock.is_some(),
1134            None => false,
1135        }
1136    }
1137}
1138
1139// -------------------------
1140//   Feature Gate Functions
1141// -------------------------
1142
1143impl Statsig {
1144    pub fn check_gate(&self, user: &StatsigUser, gate_name: &str) -> bool {
1145        self.check_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1146    }
1147
1148    pub fn check_gate_with_options(
1149        &self,
1150        user: &StatsigUser,
1151        gate_name: &str,
1152        options: FeatureGateEvaluationOptions,
1153    ) -> bool {
1154        let user_internal = self.internalize_user(user);
1155        let disable_exposure_logging = options.disable_exposure_logging;
1156        let (details, evaluation) = self.get_gate_evaluation(&user_internal, gate_name);
1157
1158        let value = evaluation.as_ref().map(|e| e.value).unwrap_or_default();
1159        let rule_id = evaluation
1160            .as_ref()
1161            .map(|e| e.base.rule_id.clone())
1162            .unwrap_or_default();
1163
1164        if disable_exposure_logging {
1165            log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1166            self.event_logger.increment_non_exposure_checks(gate_name);
1167        } else {
1168            self.event_logger.enqueue(EnqueueGateExpoOp {
1169                exposure_time: Utc::now().timestamp_millis() as u64,
1170                user: &user_internal,
1171                queried_gate_name: gate_name,
1172                evaluation: evaluation.map(Cow::Owned),
1173                details: details.clone(),
1174                trigger: ExposureTrigger::Auto,
1175            });
1176        }
1177
1178        self.emit_gate_evaluated(gate_name, rule_id.as_str(), value, &details.reason);
1179
1180        value
1181    }
1182
1183    pub fn get_feature_gate(&self, user: &StatsigUser, gate_name: &str) -> FeatureGate {
1184        self.get_feature_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1185    }
1186
1187    pub fn get_feature_gate_with_options(
1188        &self,
1189        user: &StatsigUser,
1190        gate_name: &str,
1191        options: FeatureGateEvaluationOptions,
1192    ) -> FeatureGate {
1193        let user_internal = self.internalize_user(user);
1194        let disable_exposure_logging = options.disable_exposure_logging;
1195        let (details, evaluation) = self.get_gate_evaluation(&user_internal, gate_name);
1196
1197        if disable_exposure_logging {
1198            log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1199            self.event_logger.increment_non_exposure_checks(gate_name);
1200        } else {
1201            self.event_logger.enqueue(EnqueueGateExpoOp {
1202                exposure_time: Utc::now().timestamp_millis() as u64,
1203                user: &user_internal,
1204                queried_gate_name: gate_name,
1205                evaluation: evaluation.as_ref().map(Cow::Borrowed),
1206                details: details.clone(),
1207                trigger: ExposureTrigger::Auto,
1208            });
1209        }
1210
1211        let gate = make_feature_gate(gate_name, evaluation, details);
1212        self.emit_gate_evaluated(gate_name, &gate.rule_id, gate.value, &gate.details.reason);
1213        gate
1214    }
1215
1216    pub fn manually_log_gate_exposure(&self, user: &StatsigUser, gate_name: &str) {
1217        let user_internal = self.internalize_user(user);
1218        let (details, evaluation) = self.get_gate_evaluation(&user_internal, gate_name);
1219        self.event_logger.enqueue(EnqueueGateExpoOp {
1220            exposure_time: Utc::now().timestamp_millis() as u64,
1221            user: &user_internal,
1222            queried_gate_name: gate_name,
1223            evaluation: evaluation.map(Cow::Owned),
1224            details: details.clone(),
1225            trigger: ExposureTrigger::Manual,
1226        });
1227    }
1228
1229    pub fn get_fields_needed_for_gate(&self, gate_name: &str) -> Vec<String> {
1230        let data = read_lock_or_else!(self.spec_store.data, {
1231            log_error_to_statsig_and_console!(
1232                self.ops_stats.clone(),
1233                TAG,
1234                StatsigErr::LockFailure(
1235                    "Failed to acquire read lock for spec store data".to_string()
1236                )
1237            );
1238            return vec![];
1239        });
1240
1241        let gate = data.values.feature_gates.get(gate_name);
1242        match gate {
1243            Some(gate) => match &gate.spec.fields_used {
1244                Some(fields) => fields.clone(),
1245                None => vec![],
1246            },
1247            None => vec![],
1248        }
1249    }
1250}
1251
1252// -------------------------
1253//   Override Functions
1254// -------------------------
1255
1256impl Statsig {
1257    pub fn override_gate(&self, gate_name: &str, value: bool, id: Option<&str>) {
1258        if let Some(adapter) = &self.override_adapter {
1259            adapter.override_gate(gate_name, value, id);
1260        }
1261    }
1262
1263    pub fn override_dynamic_config(
1264        &self,
1265        config_name: &str,
1266        value: HashMap<String, serde_json::Value>,
1267        id: Option<&str>,
1268    ) {
1269        if let Some(adapter) = &self.override_adapter {
1270            adapter.override_dynamic_config(config_name, value, id);
1271        }
1272    }
1273
1274    pub fn override_layer(
1275        &self,
1276        layer_name: &str,
1277        value: HashMap<String, serde_json::Value>,
1278        id: Option<&str>,
1279    ) {
1280        if let Some(adapter) = &self.override_adapter {
1281            adapter.override_layer(layer_name, value, id);
1282        }
1283    }
1284
1285    pub fn override_experiment(
1286        &self,
1287        experiment_name: &str,
1288        value: HashMap<String, serde_json::Value>,
1289        id: Option<&str>,
1290    ) {
1291        if let Some(adapter) = &self.override_adapter {
1292            adapter.override_experiment(experiment_name, value, id);
1293        }
1294    }
1295
1296    pub fn override_experiment_by_group_name(
1297        &self,
1298        experiment_name: &str,
1299        group_name: &str,
1300        id: Option<&str>,
1301    ) {
1302        if let Some(adapter) = &self.override_adapter {
1303            adapter.override_experiment_by_group_name(experiment_name, group_name, id);
1304        }
1305    }
1306
1307    pub fn remove_gate_override(&self, gate_name: &str, id: Option<&str>) {
1308        if let Some(adapter) = &self.override_adapter {
1309            adapter.remove_gate_override(gate_name, id);
1310        }
1311    }
1312
1313    pub fn remove_dynamic_config_override(&self, config_name: &str, id: Option<&str>) {
1314        if let Some(adapter) = &self.override_adapter {
1315            adapter.remove_dynamic_config_override(config_name, id);
1316        }
1317    }
1318
1319    pub fn remove_experiment_override(&self, experiment_name: &str, id: Option<&str>) {
1320        if let Some(adapter) = &self.override_adapter {
1321            adapter.remove_experiment_override(experiment_name, id);
1322        }
1323    }
1324
1325    pub fn remove_layer_override(&self, layer_name: &str, id: Option<&str>) {
1326        if let Some(adapter) = &self.override_adapter {
1327            adapter.remove_layer_override(layer_name, id);
1328        }
1329    }
1330
1331    pub fn remove_all_overrides(&self) {
1332        if let Some(adapter) = &self.override_adapter {
1333            adapter.remove_all_overrides();
1334        }
1335    }
1336}
1337
1338// -------------------------
1339//   Debugging Functions
1340// -------------------------
1341
1342impl Statsig {
1343    pub fn get_feature_gate_list(&self) -> Vec<String> {
1344        let data = read_lock_or_else!(self.spec_store.data, {
1345            log_error_to_statsig_and_console!(
1346                &self.ops_stats,
1347                TAG,
1348                StatsigErr::LockFailure(
1349                    "Failed to acquire read lock for spec store data".to_string()
1350                )
1351            );
1352            return vec![];
1353        });
1354
1355        data.values.feature_gates.unperformant_keys()
1356    }
1357
1358    pub fn get_dynamic_config_list(&self) -> Vec<String> {
1359        let data = read_lock_or_else!(self.spec_store.data, {
1360            log_error_to_statsig_and_console!(
1361                &self.ops_stats,
1362                TAG,
1363                StatsigErr::LockFailure(
1364                    "Failed to acquire read lock for spec store data".to_string()
1365                )
1366            );
1367            return vec![];
1368        });
1369
1370        data.values
1371            .dynamic_configs
1372            .unperformant_keys_entity_filter("dynamic_config")
1373    }
1374
1375    pub fn get_experiment_list(&self) -> Vec<String> {
1376        let data = read_lock_or_else!(self.spec_store.data, {
1377            log_error_to_statsig_and_console!(
1378                &self.ops_stats,
1379                TAG,
1380                StatsigErr::LockFailure(
1381                    "Failed to acquire read lock for spec store data".to_string()
1382                )
1383            );
1384            return vec![];
1385        });
1386
1387        data.values
1388            .dynamic_configs
1389            .unperformant_keys_entity_filter("experiment")
1390    }
1391
1392    pub fn get_autotune_list(&self) -> Vec<String> {
1393        let data = read_lock_or_else!(self.spec_store.data, {
1394            log_error_to_statsig_and_console!(
1395                &self.ops_stats,
1396                TAG,
1397                StatsigErr::LockFailure(
1398                    "Failed to acquire read lock for spec store data".to_string()
1399                )
1400            );
1401            return vec![];
1402        });
1403
1404        data.values
1405            .dynamic_configs
1406            .unperformant_keys_entity_filter("autotune")
1407    }
1408
1409    pub fn get_parameter_store_list(&self) -> Vec<String> {
1410        let data = read_lock_or_else!(self.spec_store.data, {
1411            log_error_to_statsig_and_console!(
1412                &self.ops_stats,
1413                TAG,
1414                StatsigErr::LockFailure(
1415                    "Failed to acquire read lock for spec store data".to_string()
1416                )
1417            );
1418            return vec![];
1419        });
1420
1421        match &data.values.param_stores {
1422            Some(param_stores) => param_stores.keys().cloned().collect(),
1423            None => vec![],
1424        }
1425    }
1426
1427    pub fn get_layer_list(&self) -> Vec<String> {
1428        let data = read_lock_or_else!(self.spec_store.data, {
1429            log_error_to_statsig_and_console!(
1430                &self.ops_stats,
1431                TAG,
1432                StatsigErr::LockFailure(
1433                    "Failed to acquire read lock for spec store data".to_string()
1434                )
1435            );
1436            return vec![];
1437        });
1438
1439        data.values.layer_configs.unperformant_keys()
1440    }
1441}
1442
1443// -------------------------
1444//   Dynamic Config Functions
1445// -------------------------
1446
1447impl Statsig {
1448    pub fn get_dynamic_config(
1449        &self,
1450        user: &StatsigUser,
1451        dynamic_config_name: &str,
1452    ) -> DynamicConfig {
1453        self.get_dynamic_config_with_options(
1454            user,
1455            dynamic_config_name,
1456            DynamicConfigEvaluationOptions::default(),
1457        )
1458    }
1459
1460    pub fn get_dynamic_config_with_options(
1461        &self,
1462        user: &StatsigUser,
1463        dynamic_config_name: &str,
1464        options: DynamicConfigEvaluationOptions,
1465    ) -> DynamicConfig {
1466        let user_internal = self.internalize_user(user);
1467        let disable_exposure_logging = options.disable_exposure_logging;
1468        let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1469
1470        if disable_exposure_logging {
1471            log_d!(
1472                TAG,
1473                "Exposure logging is disabled for Dynamic Config {}",
1474                dynamic_config_name
1475            );
1476            self.event_logger
1477                .increment_non_exposure_checks(dynamic_config_name);
1478        } else {
1479            self.event_logger.enqueue(EnqueueConfigExpoOp {
1480                exposure_time: Utc::now().timestamp_millis() as u64,
1481                user: &user_internal,
1482                config: &dynamic_config,
1483                trigger: ExposureTrigger::Auto,
1484            });
1485        }
1486
1487        self.emit_dynamic_config_evaluated(&dynamic_config);
1488
1489        dynamic_config
1490    }
1491
1492    pub fn manually_log_dynamic_config_exposure(
1493        &self,
1494        user: &StatsigUser,
1495        dynamic_config_name: &str,
1496    ) {
1497        let user_internal = self.internalize_user(user);
1498        let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1499        self.event_logger.enqueue(EnqueueConfigExpoOp {
1500            exposure_time: Utc::now().timestamp_millis() as u64,
1501            user: &user_internal,
1502            config: &dynamic_config,
1503            trigger: ExposureTrigger::Manual,
1504        });
1505    }
1506
1507    pub fn get_fields_needed_for_dynamic_config(&self, config_name: &str) -> Vec<String> {
1508        let data = read_lock_or_else!(self.spec_store.data, {
1509            log_error_to_statsig_and_console!(
1510                self.ops_stats.clone(),
1511                TAG,
1512                StatsigErr::LockFailure(
1513                    "Failed to acquire read lock for spec store data".to_string()
1514                )
1515            );
1516            return vec![];
1517        });
1518
1519        let config = data.values.dynamic_configs.get(config_name);
1520        match config {
1521            Some(config) => match &config.spec.fields_used {
1522                Some(fields) => fields.clone(),
1523                None => vec![],
1524            },
1525            None => vec![],
1526        }
1527    }
1528}
1529
1530// -------------------------
1531//   Experiment Functions
1532// -------------------------
1533
1534impl Statsig {
1535    pub fn get_experiment(&self, user: &StatsigUser, experiment_name: &str) -> Experiment {
1536        self.get_experiment_with_options(
1537            user,
1538            experiment_name,
1539            ExperimentEvaluationOptions::default(),
1540        )
1541    }
1542
1543    pub fn get_experiment_with_options(
1544        &self,
1545        user: &StatsigUser,
1546        experiment_name: &str,
1547        options: ExperimentEvaluationOptions,
1548    ) -> Experiment {
1549        let user_internal = self.internalize_user(user);
1550        let disable_exposure_logging = options.disable_exposure_logging;
1551        let mut experiment = self.get_experiment_impl(&user_internal, experiment_name);
1552        if let Some(persisted_experiment) = self.persistent_values_manager.as_ref().and_then(|m| {
1553            m.try_apply_sticky_value_to_experiment(&user_internal, &options, &experiment)
1554        }) {
1555            experiment = persisted_experiment
1556        }
1557
1558        if disable_exposure_logging {
1559            log_d!(
1560                TAG,
1561                "Exposure logging is disabled for experiment {}",
1562                experiment_name
1563            );
1564            self.event_logger
1565                .increment_non_exposure_checks(experiment_name);
1566        } else {
1567            self.event_logger.enqueue(EnqueueExperimentExpoOp {
1568                exposure_time: Utc::now().timestamp_millis() as u64,
1569                user: &user_internal,
1570                experiment: &experiment,
1571                trigger: ExposureTrigger::Auto,
1572            });
1573        }
1574
1575        self.emit_experiment_evaluated(&experiment);
1576
1577        experiment
1578    }
1579
1580    pub fn manually_log_experiment_exposure(&self, user: &StatsigUser, experiment_name: &str) {
1581        let user_internal = self.internalize_user(user);
1582        let experiment = self.get_experiment_impl(&user_internal, experiment_name);
1583        self.event_logger.enqueue(EnqueueExperimentExpoOp {
1584            exposure_time: Utc::now().timestamp_millis() as u64,
1585            user: &user_internal,
1586            experiment: &experiment,
1587            trigger: ExposureTrigger::Manual,
1588        });
1589    }
1590
1591    pub fn get_fields_needed_for_experiment(&self, experiment_name: &str) -> Vec<String> {
1592        let data = read_lock_or_else!(self.spec_store.data, {
1593            log_error_to_statsig_and_console!(
1594                self.ops_stats.clone(),
1595                TAG,
1596                StatsigErr::LockFailure(
1597                    "Failed to acquire read lock for spec store data".to_string()
1598                )
1599            );
1600            return vec![];
1601        });
1602
1603        let config = data.values.dynamic_configs.get(experiment_name);
1604        match config {
1605            Some(config) => match &config.spec.fields_used {
1606                Some(fields) => fields.clone(),
1607                None => vec![],
1608            },
1609            None => vec![],
1610        }
1611    }
1612
1613    pub fn get_experiment_by_group_name(
1614        &self,
1615        experiment_name: &str,
1616        group_name: &str,
1617    ) -> Experiment {
1618        let data = read_lock_or_else!(self.spec_store.data, {
1619            log_error_to_statsig_and_console!(
1620                self.ops_stats.clone(),
1621                TAG,
1622                StatsigErr::LockFailure(
1623                    "Failed to acquire read lock for spec store data".to_string()
1624                )
1625            );
1626            return make_experiment(
1627                experiment_name,
1628                None,
1629                EvaluationDetails::error("Failed to acquire read lock for spec store data"),
1630            );
1631        });
1632
1633        let Some(exp) = data.values.dynamic_configs.get(experiment_name) else {
1634            return make_experiment(
1635                experiment_name,
1636                None,
1637                EvaluationDetails::unrecognized(&data),
1638            );
1639        };
1640
1641        if let Some(rule) = exp
1642            .spec
1643            .rules
1644            .iter()
1645            .find(|rule| rule.group_name.as_deref() == Some(group_name))
1646        {
1647            let value = rule.return_value.get_json().unwrap_or_default();
1648            let rule_id = String::from(rule.id.as_str());
1649            let id_type = rule.id_type.value.clone();
1650            let group_name = rule.group_name.clone();
1651
1652            return Experiment {
1653                name: experiment_name.to_string(),
1654                value,
1655                rule_id,
1656                id_type,
1657                group_name,
1658                details: EvaluationDetails::recognized_without_eval_result(&data),
1659                __evaluation: None,
1660            };
1661        }
1662
1663        make_experiment(
1664            experiment_name,
1665            None,
1666            EvaluationDetails::unrecognized(&data),
1667        )
1668    }
1669}
1670
1671// -------------------------
1672//   Layer Functions
1673// -------------------------
1674
1675impl Statsig {
1676    pub fn get_layer(&self, user: &StatsigUser, layer_name: &str) -> Layer {
1677        self.get_layer_with_options(user, layer_name, LayerEvaluationOptions::default())
1678    }
1679
1680    pub fn get_layer_with_options(
1681        &self,
1682        user: &StatsigUser,
1683        layer_name: &str,
1684        options: LayerEvaluationOptions,
1685    ) -> Layer {
1686        let user_internal = self.internalize_user(user);
1687        self.get_layer_impl(user_internal, layer_name, options)
1688    }
1689
1690    pub fn manually_log_layer_parameter_exposure(
1691        &self,
1692        user: &StatsigUser,
1693        layer_name: &str,
1694        parameter_name: String,
1695    ) {
1696        let user_internal = self.internalize_user(user);
1697        let layer =
1698            self.get_layer_impl(user_internal, layer_name, LayerEvaluationOptions::default());
1699
1700        self.event_logger
1701            .enqueue(EnqueueLayerParamExpoOp::LayerOwned(
1702                Utc::now().timestamp_millis() as u64,
1703                Box::new(layer),
1704                parameter_name,
1705                ExposureTrigger::Manual,
1706            ));
1707    }
1708
1709    pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> {
1710        let data = read_lock_or_else!(self.spec_store.data, {
1711            log_error_to_statsig_and_console!(
1712                self.ops_stats.clone(),
1713                TAG,
1714                StatsigErr::LockFailure(
1715                    "Failed to acquire read lock for spec store data".to_string()
1716                )
1717            );
1718            return vec![];
1719        });
1720
1721        let layer = data.values.layer_configs.get(layer_name);
1722        match layer {
1723            Some(layer) => match &layer.spec.fields_used {
1724                Some(fields) => fields.clone(),
1725                None => vec![],
1726            },
1727            None => vec![],
1728        }
1729    }
1730}
1731
1732// -------------------------
1733//   Internal Functions
1734// -------------------------
1735
1736impl Statsig {
1737    pub(crate) fn get_from_statsig_env(&self, key: &str) -> Option<DynamicValue> {
1738        if let Some(env) = &self.statsig_environment {
1739            return env.get(key).cloned();
1740        }
1741
1742        if let Some(fallback_env) = self
1743            .fallback_environment
1744            .try_lock_for(Duration::from_secs(5))
1745        {
1746            if let Some(env) = &*fallback_env {
1747                return env.get(key).cloned();
1748            }
1749        }
1750
1751        None
1752    }
1753
1754    pub(crate) fn get_value_from_global_custom_fields(&self, key: &str) -> Option<&DynamicValue> {
1755        if let Some(env) = &self.options.global_custom_fields {
1756            return env.get(key);
1757        }
1758
1759        None
1760    }
1761
1762    pub(crate) fn use_global_custom_fields<T>(
1763        &self,
1764        f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1765    ) -> T {
1766        f(self.options.global_custom_fields.as_ref())
1767    }
1768
1769    pub(crate) fn use_statsig_env<T>(
1770        &self,
1771        f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1772    ) -> T {
1773        if let Some(env) = &self.statsig_environment {
1774            return f(Some(env));
1775        }
1776
1777        if let Some(fallback_env) = self
1778            .fallback_environment
1779            .try_lock_for(Duration::from_secs(5))
1780        {
1781            if let Some(env) = &*fallback_env {
1782                return f(Some(env));
1783            }
1784        }
1785
1786        f(None)
1787    }
1788}
1789
1790// -------------------------
1791//   Private Functions
1792// -------------------------
1793
1794impl Statsig {
1795    fn evaluate_spec<T>(
1796        &self,
1797        user_internal: &StatsigUserInternal,
1798        spec_name: &str,
1799        make_empty_result: impl FnOnce(EvaluationDetails) -> T,
1800        make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
1801        spec_type: &SpecType,
1802    ) -> T {
1803        let data = read_lock_or_else!(self.spec_store.data, {
1804            log_error_to_statsig_and_console!(
1805                &self.ops_stats,
1806                TAG,
1807                StatsigErr::LockFailure(
1808                    "Failed to acquire read lock for spec store data".to_string()
1809                )
1810            );
1811            return make_empty_result(EvaluationDetails::unrecognized_no_data());
1812        });
1813
1814        let app_id = data.values.app_id.as_ref();
1815        let mut context = EvaluatorContext::new(
1816            user_internal,
1817            &data,
1818            &self.hashing,
1819            app_id,
1820            self.override_adapter.as_ref(),
1821        );
1822
1823        match Evaluator::evaluate_with_details(&mut context, spec_name, spec_type) {
1824            Ok(eval_details) => make_result(context.result, eval_details),
1825            Err(e) => {
1826                log_error_to_statsig_and_console!(
1827                    &self.ops_stats,
1828                    TAG,
1829                    StatsigErr::EvaluationError(e.to_string())
1830                );
1831                make_empty_result(EvaluationDetails::error(&e.to_string()))
1832            }
1833        }
1834    }
1835
1836    fn get_gate_evaluation(
1837        &self,
1838        user_internal: &StatsigUserInternal,
1839        gate_name: &str,
1840    ) -> (EvaluationDetails, Option<GateEvaluation>) {
1841        self.evaluate_spec(
1842            user_internal,
1843            gate_name,
1844            |eval_details| (eval_details, None),
1845            |mut result, eval_details| {
1846                let evaluation = result_to_gate_eval(gate_name, &mut result);
1847                (eval_details, Some(evaluation))
1848            },
1849            &SpecType::Gate,
1850        )
1851    }
1852
1853    fn get_dynamic_config_impl(
1854        &self,
1855        user_internal: &StatsigUserInternal,
1856        config_name: &str,
1857    ) -> DynamicConfig {
1858        self.evaluate_spec(
1859            user_internal,
1860            config_name,
1861            |eval_details| make_dynamic_config(config_name, None, eval_details),
1862            |mut result, eval_details| {
1863                let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
1864                make_dynamic_config(config_name, Some(evaluation), eval_details)
1865            },
1866            &SpecType::DynamicConfig,
1867        )
1868    }
1869
1870    fn get_experiment_impl(
1871        &self,
1872        user_internal: &StatsigUserInternal,
1873        experiment_name: &str,
1874    ) -> Experiment {
1875        self.evaluate_spec(
1876            user_internal,
1877            experiment_name,
1878            |eval_details| make_experiment(experiment_name, None, eval_details),
1879            |mut result, eval_details| {
1880                let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
1881                make_experiment(experiment_name, Some(evaluation), eval_details)
1882            },
1883            &SpecType::Experiment,
1884        )
1885    }
1886
1887    fn get_layer_impl(
1888        &self,
1889        user_internal: StatsigUserInternal,
1890        layer_name: &str,
1891        evaluation_options: LayerEvaluationOptions,
1892    ) -> Layer {
1893        let disable_exposure_logging = evaluation_options.disable_exposure_logging;
1894
1895        if disable_exposure_logging {
1896            self.event_logger.increment_non_exposure_checks(layer_name);
1897        }
1898
1899        let mut layer = self.evaluate_spec(
1900            &user_internal,
1901            layer_name,
1902            |eval_details| {
1903                make_layer(
1904                    user_internal.to_loggable(),
1905                    layer_name,
1906                    None,
1907                    eval_details,
1908                    None,
1909                    disable_exposure_logging,
1910                )
1911            },
1912            |mut result, eval_details| {
1913                let evaluation = result_to_layer_eval(layer_name, &mut result);
1914                let event_logger_ptr = Arc::downgrade(&self.event_logger);
1915
1916                make_layer(
1917                    user_internal.to_loggable(),
1918                    layer_name,
1919                    Some(evaluation),
1920                    eval_details,
1921                    Some(event_logger_ptr),
1922                    disable_exposure_logging,
1923                )
1924            },
1925            &SpecType::Layer,
1926        );
1927        if let Some(persisted_layer) = self.persistent_values_manager.as_ref().and_then(|p| {
1928            let event_logger_ptr = Arc::downgrade(&self.event_logger);
1929            p.try_apply_sticky_value_to_layer(
1930                &user_internal,
1931                &evaluation_options,
1932                &layer,
1933                Some(event_logger_ptr),
1934                disable_exposure_logging,
1935            )
1936        }) {
1937            layer = persisted_layer
1938        }
1939
1940        self.emit_layer_evaluated(&layer);
1941
1942        layer
1943    }
1944
1945    fn internalize_user<'s, 'u>(&'s self, user: &'u StatsigUser) -> StatsigUserInternal<'s, 'u> {
1946        StatsigUserInternal::new(user, Some(self))
1947    }
1948
1949    fn set_default_environment_from_server(&self) {
1950        let data = read_lock_or_else!(self.spec_store.data, {
1951            return;
1952        });
1953
1954        if let Some(default_env) = data.values.default_environment.as_ref() {
1955            let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
1956
1957            match self
1958                .fallback_environment
1959                .try_lock_for(Duration::from_secs(5))
1960            {
1961                Some(mut fallback_env) => {
1962                    *fallback_env = Some(env_map);
1963                }
1964                None => {
1965                    log_e!(TAG, "Failed to lock fallback_environment");
1966                }
1967            }
1968        }
1969    }
1970
1971    fn log_init_finish(
1972        &self,
1973        success: bool,
1974        error_message: &Option<String>,
1975        duration: &f64,
1976        specs_info: &SpecsInfo,
1977    ) {
1978        let is_store_populated = specs_info.source != SpecsSource::NoValues;
1979        let source_str = specs_info.source.to_string();
1980
1981        let event = ObservabilityEvent::new_event(
1982            MetricType::Dist,
1983            "initialization".to_string(),
1984            *duration,
1985            Some(HashMap::from([
1986                ("success".to_owned(), success.to_string()),
1987                ("source".to_owned(), source_str.clone()),
1988                ("store_populated".to_owned(), is_store_populated.to_string()),
1989                (
1990                    "spec_source_api".to_owned(),
1991                    specs_info.source_api.clone().unwrap_or_default(),
1992                ),
1993            ])),
1994        );
1995
1996        self.ops_stats.log(event);
1997        self.ops_stats.add_marker(
1998            {
1999                let marker = Marker::new(KeyType::Overall, ActionType::End, None)
2000                    .with_is_success(success)
2001                    .with_config_spec_ready(specs_info.source != SpecsSource::NoValues)
2002                    .with_source(source_str);
2003
2004                if let Some(msg) = &error_message {
2005                    marker.with_message(msg.to_string())
2006                } else {
2007                    marker
2008                }
2009            },
2010            Some(ContextType::Initialize),
2011        );
2012        self.ops_stats
2013            .enqueue_diagnostics_event(None, Some(ContextType::Initialize));
2014    }
2015}
2016
2017fn initialize_event_logging_adapter(
2018    sdk_key: &str,
2019    options: &StatsigOptions,
2020) -> Arc<dyn EventLoggingAdapter> {
2021    options
2022        .event_logging_adapter
2023        .clone()
2024        .unwrap_or_else(|| Arc::new(StatsigHttpEventLoggingAdapter::new(sdk_key, Some(options))))
2025}
2026
2027fn initialize_specs_adapter(
2028    sdk_key: &str,
2029    options: &StatsigOptions,
2030    hashing: &HashUtil,
2031) -> SpecsAdapterHousing {
2032    if let Some(adapter) = options.specs_adapter.clone() {
2033        log_d!(TAG, "Using provided SpecsAdapter: {}", sdk_key);
2034        return SpecsAdapterHousing {
2035            inner: adapter,
2036            as_default_adapter: None,
2037        };
2038    }
2039
2040    if let Some(adapter_config) = options.spec_adapters_config.clone() {
2041        let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
2042            sdk_key,
2043            adapter_config,
2044            options,
2045            hashing,
2046        ));
2047
2048        return SpecsAdapterHousing {
2049            inner: adapter,
2050            as_default_adapter: None,
2051        };
2052    }
2053
2054    if let Some(data_adapter) = options.data_store.clone() {
2055        let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
2056            sdk_key,
2057            data_adapter,
2058            options,
2059            hashing,
2060        ));
2061
2062        return SpecsAdapterHousing {
2063            inner: adapter,
2064            as_default_adapter: None,
2065        };
2066    }
2067
2068    let adapter = Arc::new(StatsigHttpSpecsAdapter::new(sdk_key, Some(options), None));
2069
2070    SpecsAdapterHousing {
2071        inner: adapter.clone(),
2072        as_default_adapter: Some(adapter),
2073    }
2074}
2075
2076fn initialize_id_lists_adapter(sdk_key: &str, options: &StatsigOptions) -> IdListsAdapterHousing {
2077    if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
2078        return IdListsAdapterHousing {
2079            inner: Some(id_lists_adapter),
2080            as_default_adapter: None,
2081        };
2082    }
2083
2084    if options.enable_id_lists.unwrap_or(false) {
2085        let adapter = Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options));
2086
2087        return IdListsAdapterHousing {
2088            inner: Some(adapter.clone()),
2089            as_default_adapter: Some(adapter),
2090        };
2091    }
2092
2093    IdListsAdapterHousing {
2094        inner: None,
2095        as_default_adapter: None,
2096    }
2097}
2098
2099struct IdListsAdapterHousing {
2100    inner: Option<Arc<dyn IdListsAdapter>>,
2101    as_default_adapter: Option<Arc<StatsigHttpIdListsAdapter>>,
2102}
2103
2104struct SpecsAdapterHousing {
2105    inner: Arc<dyn SpecsAdapter>,
2106    as_default_adapter: Option<Arc<StatsigHttpSpecsAdapter>>,
2107}
2108
2109fn setup_ops_stats(
2110    sdk_key: &str,
2111    statsig_runtime: Arc<StatsigRuntime>,
2112    error_observer: &Arc<dyn OpsStatsEventObserver>,
2113    diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
2114    external_observer: &Option<Weak<dyn ObservabilityClient>>,
2115) -> Arc<OpsStatsForInstance> {
2116    let ops_stat = OPS_STATS.get_for_instance(sdk_key);
2117    ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
2118    ops_stat.subscribe(
2119        statsig_runtime.clone(),
2120        Arc::downgrade(diagnostics_observer),
2121    );
2122
2123    if let Some(ob_client) = external_observer {
2124        if let Some(client) = ob_client.upgrade() {
2125            client.init();
2126            let as_observer = client.to_ops_stats_event_observer();
2127            ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
2128        }
2129    }
2130
2131    ops_stat
2132}