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