statsig_rust/
statsig.rs

1use crate::client_init_response_formatter::{
2    ClientInitResponseFormatter, ClientInitResponseOptions,
3};
4use crate::evaluation::cmab_evaluator::{get_cmab_ranked_list, CMABRankedGroup};
5use crate::evaluation::country_lookup::CountryLookup;
6use crate::evaluation::dynamic_value::DynamicValue;
7use crate::evaluation::evaluation_details::EvaluationDetails;
8use crate::evaluation::evaluation_types::{AnyEvaluation, BaseEvaluation, ExperimentEvaluation};
9use crate::evaluation::evaluator::{Evaluator, SpecType};
10use crate::evaluation::evaluator_context::EvaluatorContext;
11use crate::evaluation::evaluator_result::{
12    result_to_dynamic_config_eval, result_to_experiment_eval, result_to_gate_eval,
13    result_to_layer_eval, EvaluatorResult,
14};
15use crate::evaluation::ua_parser::UserAgentParser;
16use crate::event_logging::config_exposure::ConfigExposure;
17use crate::event_logging::event_logger::{EventLogger, QueuedEventPayload};
18use crate::event_logging::gate_exposure::GateExposure;
19use crate::event_logging::layer_exposure::LayerExposure;
20use crate::event_logging::statsig_event::StatsigEvent;
21use crate::event_logging::statsig_event_internal::make_custom_event;
22use crate::event_logging_adapter::EventLoggingAdapter;
23use crate::event_logging_adapter::StatsigHttpEventLoggingAdapter;
24use crate::hashing::HashUtil;
25use crate::initialize_response::InitializeResponse;
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_simple_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, StepType};
34use crate::spec_store::{SpecStore, SpecStoreData};
35use crate::specs_adapter::{StatsigCustomizedSpecsAdapter, StatsigHttpSpecsAdapter};
36use crate::statsig_err::StatsigErr;
37use crate::statsig_metadata::StatsigMetadata;
38use crate::statsig_options::StatsigOptions;
39use crate::statsig_runtime::StatsigRuntime;
40use crate::statsig_type_factories::{
41    make_dynamic_config, make_experiment, make_feature_gate, make_layer,
42};
43use crate::statsig_types::{
44    DynamicConfig, Experiment, FeatureGate, Layer, OverrideAdapterType, ParameterStore,
45};
46use crate::user::StatsigUserInternal;
47use crate::{
48    dyn_value, log_d, log_e, log_w, read_lock_or_else, IdListsAdapter, ObservabilityClient,
49    OpsStatsEventObserver, OverrideAdapter, SamplingProcessor, SpecsAdapter, SpecsInfo,
50    SpecsSource, SpecsUpdateListener, StatsigHttpIdListsAdapter, StatsigLocalOverrideAdapter,
51    StatsigUser,
52};
53use crate::{
54    log_error_to_statsig_and_console,
55    statsig_core_api_options::{
56        DynamicConfigEvaluationOptions, ExperimentEvaluationOptions, FeatureGateEvaluationOptions,
57        LayerEvaluationOptions, ParameterStoreEvaluationOptions,
58    },
59};
60use serde::de::DeserializeOwned;
61use serde_json::json;
62use serde_json::Value;
63use std::collections::HashMap;
64use std::sync::atomic::{AtomicBool, Ordering};
65use std::sync::Mutex;
66use std::sync::{Arc, Weak};
67use std::time::{Duration, Instant};
68use tokio::time::sleep;
69use tokio::try_join;
70
71const TAG: &str = stringify!(Statsig);
72const ERROR_SDK_KEY: &str = "__STATSIG_ERROR_SDK_KEY__";
73const INIT_IP_TAG: &str = "INIT_COUNTRY_LOOKUP";
74const INIT_UA_TAG: &str = "INIT_UA";
75
76lazy_static::lazy_static! {
77    static ref SHARED_INSTANCE: Mutex<Option<Arc<Statsig>>> = Mutex::new(None);
78}
79
80pub struct Statsig {
81    pub statsig_runtime: Arc<StatsigRuntime>,
82
83    sdk_key: String,
84    options: Arc<StatsigOptions>,
85    event_logger: Arc<EventLogger>,
86    specs_adapter: Arc<dyn SpecsAdapter>,
87    event_logging_adapter: Arc<dyn EventLoggingAdapter>,
88    id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
89    override_adapter: Option<Arc<dyn OverrideAdapter>>,
90    spec_store: Arc<SpecStore>,
91    hashing: Arc<HashUtil>,
92    gcir_formatter: Arc<ClientInitResponseFormatter>,
93    statsig_environment: Option<HashMap<String, DynamicValue>>,
94    fallback_environment: Mutex<Option<HashMap<String, DynamicValue>>>,
95    ops_stats: Arc<OpsStatsForInstance>,
96    error_observer: Arc<dyn OpsStatsEventObserver>,
97    diagnostics_observer: Arc<dyn OpsStatsEventObserver>,
98    sampling_processor: Arc<SamplingProcessor>,
99    background_tasks_started: Arc<AtomicBool>,
100    persistent_values_manager: Option<Arc<PersistentValuesManager>>,
101}
102
103pub struct StatsigContext {
104    pub sdk_key: String,
105    pub options: Arc<StatsigOptions>,
106    pub local_override_adapter: Option<Arc<dyn OverrideAdapter>>,
107    pub spec_store_data: Option<SpecStoreData>,
108    pub error_observer: Arc<dyn OpsStatsEventObserver>,
109    pub diagnostics_observer: Arc<dyn OpsStatsEventObserver>,
110    pub spec_store: Arc<SpecStore>,
111}
112
113#[derive(Debug)]
114pub struct FailureDetails {
115    pub reason: String,
116    pub error: Option<StatsigErr>,
117}
118
119#[derive(Debug)]
120pub struct StatsigInitializeDetails {
121    pub duration: f64,
122    pub init_success: bool,
123    pub is_config_spec_ready: bool,
124    pub is_id_list_ready: Option<bool>,
125    pub source: SpecsSource,
126    pub failure_details: Option<FailureDetails>,
127}
128
129impl Statsig {
130    pub fn new(sdk_key: &str, options: Option<Arc<StatsigOptions>>) -> Self {
131        let statsig_runtime = StatsigRuntime::get_runtime();
132        let options = options.unwrap_or_default();
133
134        let hashing = Arc::new(HashUtil::new());
135
136        let specs_adapter = initialize_specs_adapter(sdk_key, &options, &hashing);
137        let id_lists_adapter = initialize_id_lists_adapter(sdk_key, &options);
138        let event_logging_adapter = initialize_event_logging_adapter(sdk_key, &options);
139        let override_adapter = match options.override_adapter.as_ref() {
140            Some(adapter) => Some(Arc::clone(adapter)),
141            None => Some(Arc::new(StatsigLocalOverrideAdapter::new()) as Arc<dyn OverrideAdapter>),
142        };
143
144        let event_logger = Arc::new(EventLogger::new(
145            sdk_key,
146            event_logging_adapter.clone(),
147            &options,
148            &statsig_runtime,
149        ));
150
151        let diagnostics = Arc::new(Diagnostics::new(event_logger.clone(), sdk_key));
152        let diagnostics_observer: Arc<dyn OpsStatsEventObserver> =
153            Arc::new(DiagnosticsObserver::new(diagnostics));
154        let error_observer: Arc<dyn OpsStatsEventObserver> =
155            Arc::new(SDKErrorsObserver::new(sdk_key, &options));
156
157        let ops_stats = setup_ops_stats(
158            sdk_key,
159            &options,
160            statsig_runtime.clone(),
161            &error_observer,
162            &diagnostics_observer,
163            &options.observability_client,
164        );
165
166        let spec_store = Arc::new(SpecStore::new(
167            sdk_key,
168            hashing.sha256(&sdk_key.to_string()),
169            statsig_runtime.clone(),
170            options.data_store.clone(),
171        ));
172
173        let environment = options
174            .environment
175            .as_ref()
176            .map(|env| HashMap::from([("tier".into(), dyn_value!(env.as_str()))]));
177
178        let sampling_processor = Arc::new(SamplingProcessor::new(
179            &statsig_runtime,
180            hashing.clone(),
181            sdk_key,
182        ));
183
184        let persistent_values_manager = options.persistent_storage.clone().map(|storage| {
185            Arc::new(PersistentValuesManager {
186                persistent_storage: storage,
187            })
188        });
189
190        StatsigMetadata::update_service_name(options.service_name.clone());
191
192        Statsig {
193            sdk_key: sdk_key.to_string(),
194            options,
195            gcir_formatter: Arc::new(ClientInitResponseFormatter::new(
196                &spec_store,
197                &override_adapter,
198            )),
199            event_logger,
200            hashing,
201            statsig_environment: environment,
202            fallback_environment: Mutex::new(None),
203            override_adapter,
204            spec_store,
205            specs_adapter,
206            event_logging_adapter,
207            id_lists_adapter,
208            statsig_runtime,
209            ops_stats,
210            error_observer,
211            sampling_processor,
212            diagnostics_observer,
213            background_tasks_started: Arc::new(AtomicBool::new(false)),
214            persistent_values_manager,
215        }
216    }
217
218    pub async fn initialize(&self) -> Result<(), StatsigErr> {
219        self.ops_stats.add_marker(
220            Marker::new(KeyType::Overall, ActionType::Start, None),
221            Some(ContextType::Initialize),
222        );
223
224        let init_details = if let Some(timeout_ms) = self.options.init_timeout_ms {
225            self.apply_timeout_to_init(timeout_ms).await
226        } else {
227            self.initialize_impl_with_details().await
228        };
229
230        self.log_init_details(&init_details);
231
232        init_details.and_then(|details| {
233            // Touch all fields to avoid dead code warnings - will use these properly in the future
234            let _ = (
235                details.duration,
236                details.init_success,
237                details.is_config_spec_ready,
238                details.is_id_list_ready,
239                details.source,
240            );
241
242            if let Some(failure_details) = details.failure_details {
243                Err(failure_details
244                    .error
245                    .unwrap_or(StatsigErr::InitializationError(failure_details.reason)))
246            } else {
247                Ok(())
248            }
249        })
250    }
251
252    pub async fn shutdown(&self) -> Result<(), StatsigErr> {
253        self.shutdown_with_timeout(Duration::from_secs(3)).await
254    }
255
256    pub async fn shutdown_with_timeout(&self, timeout: Duration) -> Result<(), StatsigErr> {
257        log_d!(
258            TAG,
259            "Shutting down Statsig with timeout {}ms",
260            timeout.as_millis()
261        );
262
263        let start = Instant::now();
264        let shutdown_result = tokio::select! {
265            () = tokio::time::sleep(timeout) => {
266                log_w!(TAG, "Statsig shutdown timed out. {}", start.elapsed().as_millis());
267                Err(StatsigErr::ShutdownTimeout)
268            }
269            sub_result = async {
270                let id_list_shutdown: Pin<Box<_>> = if let Some(adapter) = &self.id_lists_adapter {
271                    adapter.shutdown(timeout)
272                } else {
273                    Box::pin(async { Ok(()) })
274                };
275
276                try_join!(
277                    id_list_shutdown,
278                    self.event_logger.shutdown(timeout),
279                    self.specs_adapter.shutdown(timeout, &self.statsig_runtime),
280                )
281            } => {
282                match sub_result {
283                    Ok(_) => {
284                        log_d!(TAG, "All Statsig tasks shutdown successfully");
285                        Ok(())
286                    }
287                    Err(e) => {
288                        log_w!(TAG, "Error during shutdown: {:?}", e);
289                        Err(e)
290                    }
291                }
292            }
293        };
294
295        self.statsig_runtime.shutdown();
296        shutdown_result
297    }
298
299    async fn start_background_tasks(
300        event_logger: Arc<EventLogger>,
301        statsig_runtime: Arc<StatsigRuntime>,
302        id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
303        specs_adapter: Arc<dyn SpecsAdapter>,
304        ops_stats: Arc<OpsStatsForInstance>,
305        bg_tasks_started: Arc<AtomicBool>,
306    ) -> bool {
307        if bg_tasks_started.load(Ordering::SeqCst) {
308            return true;
309        }
310
311        let mut success = true;
312        event_logger.clone().start_background_task(&statsig_runtime);
313
314        if let Some(adapter) = &id_lists_adapter {
315            if let Err(e) = adapter
316                .clone()
317                .schedule_background_sync(&statsig_runtime)
318                .await
319            {
320                success = false;
321                log_w!(TAG, "Failed to schedule idlist background job {}", e);
322            }
323        }
324
325        if let Err(e) = specs_adapter
326            .clone()
327            .schedule_background_sync(&statsig_runtime)
328            .await
329        {
330            success = false;
331            log_error_to_statsig_and_console!(
332                ops_stats,
333                TAG,
334                "Failed to schedule SpecAdapter({}) background job. Error: {}",
335                specs_adapter.get_type_name(),
336                e,
337            );
338        }
339
340        bg_tasks_started.store(true, Ordering::SeqCst);
341
342        success
343    }
344
345    async fn apply_timeout_to_init(
346        &self,
347        timeout_ms: u64,
348    ) -> Result<StatsigInitializeDetails, StatsigErr> {
349        let timeout = Duration::from_millis(timeout_ms);
350
351        let init_future = self.initialize_impl_with_details();
352        let timeout_future = sleep(timeout);
353
354        let event_logger = self.event_logger.clone();
355        let statsig_runtime = self.statsig_runtime.clone();
356        let id_lists_adapter = self.id_lists_adapter.clone();
357        let specs_adapter = self.specs_adapter.clone();
358        let ops_stats = self.ops_stats.clone();
359        let background_tasks_started = self.background_tasks_started.clone();
360        // Create another clone specifically for the closure
361        let statsig_runtime_for_closure = statsig_runtime.clone();
362
363        tokio::select! {
364            result = init_future => {
365                result
366            },
367            _ = timeout_future => {
368                statsig_runtime.spawn(
369                    "start_background_tasks",
370                    |_shutdown_notify| async move {
371                        Self::start_background_tasks(
372                            event_logger,
373                            statsig_runtime_for_closure,
374                            id_lists_adapter,
375                            specs_adapter,
376                            ops_stats,
377                            background_tasks_started,
378                        ).await;
379                    }
380                );
381                Ok(self.timeout_failure(timeout_ms))
382            },
383        }
384    }
385
386    async fn initialize_impl_with_details(&self) -> Result<StatsigInitializeDetails, StatsigErr> {
387        let start_time = Instant::now();
388        self.spec_store.set_source(SpecsSource::Loading);
389        self.specs_adapter.initialize(self.spec_store.clone());
390
391        let mut error_message = None;
392        let mut id_list_ready = None;
393
394        let init_country_lookup = if !self.options.disable_country_lookup.unwrap_or_default() {
395            Some(self.statsig_runtime.spawn(INIT_IP_TAG, |_| async {
396                CountryLookup::load_country_lookup();
397            }))
398        } else {
399            None
400        };
401
402        let init_ua = if !self.options.disable_user_agent_parsing.unwrap_or_default() {
403            Some(self.statsig_runtime.spawn(INIT_UA_TAG, |_| async {
404                UserAgentParser::load_parser();
405            }))
406        } else {
407            None
408        };
409
410        let init_res = match self
411            .specs_adapter
412            .clone()
413            .start(&self.statsig_runtime)
414            .await
415        {
416            Ok(()) => Ok(()),
417            Err(e) => {
418                self.spec_store.set_source(SpecsSource::NoValues);
419                error_message = Some(format!("Failed to start specs adapter: {e}"));
420                Err(e)
421            }
422        };
423
424        if let Some(adapter) = &self.id_lists_adapter {
425            match adapter
426                .clone()
427                .start(&self.statsig_runtime, self.spec_store.clone())
428                .await
429            {
430                Ok(()) => {
431                    id_list_ready = Some(true);
432                }
433                Err(e) => {
434                    id_list_ready = Some(false);
435                    error_message.get_or_insert_with(|| format!("Failed to sync ID lists: {e}"));
436                }
437            }
438            if let Err(e) = adapter
439                .clone()
440                .schedule_background_sync(&self.statsig_runtime)
441                .await
442            {
443                log_w!(TAG, "Failed to schedule id_list background job {}", e);
444            }
445        }
446
447        if let Err(e) = self
448            .event_logging_adapter
449            .clone()
450            .start(&self.statsig_runtime)
451            .await
452        {
453            log_error_to_statsig_and_console!(
454                self.ops_stats.clone(),
455                TAG,
456                "Failed to start event logging adapter {}",
457                e
458            );
459        }
460
461        let spec_info = self.spec_store.get_current_specs_info();
462        let duration = start_time.elapsed().as_millis() as f64;
463
464        self.set_default_environment_from_server();
465
466        if self.options.wait_for_country_lookup_init.unwrap_or(false) {
467            if let Some(task_id) = init_country_lookup {
468                let _ = self
469                    .statsig_runtime
470                    .await_join_handle(INIT_IP_TAG, &task_id)
471                    .await;
472            }
473        }
474        if self.options.wait_for_user_agent_init.unwrap_or(false) {
475            if let Some(task_id) = init_ua {
476                let _ = self
477                    .statsig_runtime
478                    .await_join_handle(INIT_UA_TAG, &task_id)
479                    .await;
480            };
481        }
482
483        let error = init_res.clone().err();
484
485        let success = Self::start_background_tasks(
486            self.event_logger.clone(),
487            self.statsig_runtime.clone(),
488            self.id_lists_adapter.clone(),
489            self.specs_adapter.clone(),
490            self.ops_stats.clone(),
491            self.background_tasks_started.clone(),
492        )
493        .await;
494
495        Ok(StatsigInitializeDetails {
496            init_success: success,
497            is_config_spec_ready: spec_info.lcut.is_some(),
498            is_id_list_ready: id_list_ready,
499            source: spec_info.source,
500            failure_details: error.as_ref().map(|e| FailureDetails {
501                reason: e.to_string(),
502                error: Some(e.clone()),
503            }),
504            duration,
505        })
506    }
507
508    fn timeout_failure(&self, timeout_ms: u64) -> StatsigInitializeDetails {
509        StatsigInitializeDetails {
510            init_success: false,
511            is_config_spec_ready: false,
512            is_id_list_ready: None,
513            source: SpecsSource::Uninitialized,
514            failure_details: Some(FailureDetails {
515                reason: "Initialization timed out".to_string(),
516                error: None,
517            }),
518            duration: timeout_ms as f64,
519        }
520    }
521
522    fn log_init_details(&self, init_details: &Result<StatsigInitializeDetails, StatsigErr>) {
523        match init_details {
524            Ok(details) => {
525                self.log_init_finish(
526                    details.init_success,
527                    &None,
528                    &details.duration,
529                    &self.spec_store.get_current_specs_info(),
530                );
531                if let Some(failure) = &details.failure_details {
532                    log_error_to_statsig_and_console!(self.ops_stats, TAG, "{}", failure.reason);
533                }
534            }
535            Err(err) => {
536                log_w!(TAG, "Initialization error: {:?}", err);
537            }
538        }
539    }
540
541    pub fn get_context(&self) -> StatsigContext {
542        StatsigContext {
543            sdk_key: self.sdk_key.clone(),
544            options: self.options.clone(),
545            local_override_adapter: self.override_adapter.clone(),
546            spec_store_data: self.spec_store.get_current_values(),
547            error_observer: self.error_observer.clone(),
548            diagnostics_observer: self.diagnostics_observer.clone(),
549            spec_store: self.spec_store.clone(),
550        }
551    }
552
553    // todo: merge into get_context
554    #[deprecated(since = "0.1.0", note = "Use get_context instead")]
555    pub fn get_current_values(&self) -> Option<SpecStoreData> {
556        self.spec_store.get_current_values()
557    }
558
559    pub fn log_event(
560        &self,
561        user: &StatsigUser,
562        event_name: &str,
563        value: Option<String>,
564        metadata: Option<HashMap<String, String>>,
565    ) {
566        let user_internal = self.internalize_user(user);
567
568        self.event_logger
569            .enqueue(QueuedEventPayload::CustomEvent(make_custom_event(
570                user_internal.to_loggable(),
571                StatsigEvent {
572                    event_name: event_name.to_string(),
573                    value: value.map(|v| json!(v)),
574                    metadata,
575                    statsig_metadata: None,
576                },
577            )));
578    }
579
580    pub fn log_event_with_number(
581        &self,
582        user: &StatsigUser,
583        event_name: &str,
584        value: Option<f64>,
585        metadata: Option<HashMap<String, String>>,
586    ) {
587        let user_internal = self.internalize_user(user);
588
589        self.event_logger
590            .enqueue(QueuedEventPayload::CustomEvent(make_custom_event(
591                user_internal.to_loggable(),
592                StatsigEvent {
593                    event_name: event_name.to_string(),
594                    value: value.map(|v| json!(v)),
595                    metadata,
596                    statsig_metadata: None,
597                },
598            )));
599    }
600
601    pub fn log_layer_param_exposure_with_layer_json(
602        &self,
603        layer_json: String,
604        parameter_name: String,
605    ) {
606        let layer = match serde_json::from_str::<Layer>(&layer_json) {
607            Ok(layer) => layer,
608            Err(e) => {
609                log_error_to_statsig_and_console!(
610                    self.ops_stats.clone(),
611                    TAG,
612                    "Shutdown failed: {:?}",
613                    e
614                );
615                return;
616            }
617        };
618
619        self.log_layer_param_exposure_with_layer(layer, parameter_name);
620    }
621
622    pub fn log_layer_param_exposure_with_layer(&self, layer: Layer, parameter_name: String) {
623        if layer.__disable_exposure {
624            self.event_logger
625                .increment_non_exposure_checks_count(layer.name.clone());
626            return;
627        }
628
629        let layer_eval = layer.__evaluation.as_ref();
630
631        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
632            &layer.__user.get_sampling_key(),
633            layer_eval.map(AnyEvaluation::from).as_ref(),
634            Some(&parameter_name),
635        );
636
637        if !sampling_details.should_send_exposure {
638            return;
639        }
640
641        self.event_logger
642            .enqueue(QueuedEventPayload::LayerExposure(LayerExposure {
643                user: layer.__user,
644                parameter_name,
645                evaluation: layer.__evaluation,
646                layer_name: layer.name,
647                evaluation_details: layer.details,
648                version: layer.__version,
649                is_manual_exposure: false,
650                sampling_details,
651                override_config_name: layer.__override_config_name.clone(),
652            }));
653    }
654
655    pub async fn flush_events(&self) {
656        self.event_logger.flush_all_blocking().await;
657    }
658
659    pub fn get_client_init_response(&self, user: &StatsigUser) -> InitializeResponse {
660        self.get_client_init_response_with_options(user, self.gcir_formatter.get_default_options())
661    }
662
663    pub fn get_client_init_response_with_options(
664        &self,
665        user: &StatsigUser,
666        options: &ClientInitResponseOptions,
667    ) -> InitializeResponse {
668        let user_internal = self.internalize_user(user);
669        self.gcir_formatter
670            .get(user_internal, &self.hashing, options)
671    }
672
673    pub fn get_client_init_response_as_string(&self, user: &StatsigUser) -> String {
674        json!(self.get_client_init_response(user)).to_string()
675    }
676
677    pub fn get_client_init_response_with_options_as_string(
678        &self,
679        user: &StatsigUser,
680        options: &ClientInitResponseOptions,
681    ) -> String {
682        json!(self.get_client_init_response_with_options(user, options)).to_string()
683    }
684
685    pub fn get_string_parameter_from_store(
686        &self,
687        user: &StatsigUser,
688        parameter_store_name: &str,
689        parameter_name: &str,
690        fallback: Option<String>,
691    ) -> Option<String> {
692        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
693    }
694
695    pub fn get_boolean_parameter_from_store(
696        &self,
697        user: &StatsigUser,
698        parameter_store_name: &str,
699        parameter_name: &str,
700        fallback: Option<bool>,
701    ) -> Option<bool> {
702        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
703    }
704
705    pub fn get_float_parameter_from_store(
706        &self,
707        user: &StatsigUser,
708        parameter_store_name: &str,
709        parameter_name: &str,
710        fallback: Option<f64>,
711    ) -> Option<f64> {
712        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
713    }
714
715    pub fn get_integer_parameter_from_store(
716        &self,
717        user: &StatsigUser,
718        parameter_store_name: &str,
719        parameter_name: &str,
720        fallback: Option<i64>,
721    ) -> Option<i64> {
722        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
723    }
724
725    pub fn get_array_parameter_from_store(
726        &self,
727        user: &StatsigUser,
728        parameter_store_name: &str,
729        parameter_name: &str,
730        fallback: Option<Vec<Value>>,
731    ) -> Option<Vec<Value>> {
732        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
733    }
734
735    pub fn get_object_parameter_from_store(
736        &self,
737        user: &StatsigUser,
738        parameter_store_name: &str,
739        parameter_name: &str,
740        fallback: Option<HashMap<String, Value>>,
741    ) -> Option<HashMap<String, Value>> {
742        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
743    }
744
745    pub fn get_parameter_from_store<T: DeserializeOwned>(
746        &self,
747        user: &StatsigUser,
748        parameter_store_name: &str,
749        parameter_name: &str,
750        fallback: Option<T>,
751    ) -> Option<T> {
752        let store = self.get_parameter_store(parameter_store_name);
753        match fallback {
754            Some(fallback) => Some(store.get(user, parameter_name, fallback)),
755            None => store.get_opt(user, parameter_name),
756        }
757    }
758
759    pub fn get_parameter_store(&self, parameter_store_name: &str) -> ParameterStore {
760        self.get_parameter_store_with_options(
761            parameter_store_name,
762            ParameterStoreEvaluationOptions::default(),
763        )
764    }
765
766    pub fn get_parameter_store_with_options(
767        &self,
768        parameter_store_name: &str,
769        options: ParameterStoreEvaluationOptions,
770    ) -> ParameterStore {
771        self.event_logger
772            .increment_non_exposure_checks_count(parameter_store_name.to_string());
773        let data = read_lock_or_else!(self.spec_store.data, {
774            log_error_to_statsig_and_console!(
775                self.ops_stats.clone(),
776                TAG,
777                "Failed to acquire read lock for spec store data"
778            );
779            return ParameterStore {
780                name: parameter_store_name.to_string(),
781                parameters: HashMap::new(),
782                details: EvaluationDetails::unrecognized_no_data(),
783                options,
784                _statsig_ref: self,
785            };
786        });
787
788        let stores = &data.values.param_stores;
789        let store = match stores {
790            Some(stores) => stores.get(parameter_store_name),
791            None => {
792                return ParameterStore {
793                    name: parameter_store_name.to_string(),
794                    parameters: HashMap::new(),
795                    details: EvaluationDetails::unrecognized(&data),
796                    options,
797                    _statsig_ref: self,
798                };
799            }
800        };
801        match store {
802            Some(store) => ParameterStore {
803                name: parameter_store_name.to_string(),
804                parameters: store.parameters.clone(),
805                details: EvaluationDetails::recognized(&data, &EvaluatorResult::default()),
806                options,
807                _statsig_ref: self,
808            },
809            None => ParameterStore {
810                name: parameter_store_name.to_string(),
811                parameters: HashMap::new(),
812                details: EvaluationDetails::unrecognized(&data),
813                options,
814                _statsig_ref: self,
815            },
816        }
817    }
818}
819
820// -------------------------
821//   CMAB Functions
822// -------------------------
823
824impl Statsig {
825    pub fn get_cmab_ranked_groups(
826        &self,
827        user: &StatsigUser,
828        cmab_name: &str,
829    ) -> Vec<CMABRankedGroup> {
830        self.event_logger
831            .increment_non_exposure_checks_count(cmab_name.to_string());
832        let data = read_lock_or_else!(self.spec_store.data, {
833            log_error_to_statsig_and_console!(
834                self.ops_stats.clone(),
835                TAG,
836                "Failed to acquire read lock for spec store data"
837            );
838            return vec![];
839        });
840        let user_internal = self.internalize_user(user);
841        get_cmab_ranked_list(
842            &mut EvaluatorContext::new(
843                &user_internal,
844                &data,
845                &self.hashing,
846                &data.values.app_id.as_ref(),
847                &self.override_adapter,
848            ),
849            cmab_name,
850        )
851    }
852
853    pub fn log_cmab_exposure_for_group(
854        &self,
855        user: &StatsigUser,
856        cmab_name: &str,
857        group_id: String,
858    ) {
859        let user_internal = self.internalize_user(user);
860        let experiment = self.get_experiment_impl(&user_internal, cmab_name);
861        let sampling_info = match experiment.__evaluation {
862            Some(ref eval) => eval.base.sampling_info.clone(),
863            None => None,
864        };
865        let base_eval = BaseEvaluation {
866            name: cmab_name.to_string(),
867            rule_id: group_id.clone(),
868            secondary_exposures: match experiment.__evaluation {
869                Some(ref eval) => eval.base.secondary_exposures.clone(),
870                None => vec![],
871            },
872            sampling_info,
873        };
874        let experiment_eval = ExperimentEvaluation {
875            base: base_eval.clone(),
876            id_type: experiment.id_type.clone(),
877            value: HashMap::new(),
878            group: group_id,
879            is_device_based: false,
880            is_in_layer: false,
881            explicit_parameters: None,
882            group_name: None,
883            is_experiment_active: Some(true),
884            is_user_in_experiment: Some(true),
885            undelegated_secondary_exposures: None,
886        };
887
888        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
889            &user_internal.get_sampling_key(),
890            Some(&AnyEvaluation::from(&experiment_eval)),
891            None,
892        );
893
894        if !sampling_details.should_send_exposure {
895            return;
896        }
897
898        self.event_logger
899            .enqueue(QueuedEventPayload::ConfigExposure(ConfigExposure {
900                user: user_internal.to_loggable(),
901                evaluation: Some(base_eval),
902                evaluation_details: experiment.details.clone(),
903                config_name: cmab_name.to_string(),
904                rule_passed: None,
905                version: experiment.__version,
906                is_manual_exposure: true,
907                sampling_details,
908                override_config_name: experiment.__override_config_name.clone(),
909            }));
910    }
911}
912
913// -------------------------
914//   Shared Instance Functions
915// -------------------------
916
917impl Statsig {
918    pub fn shared() -> Arc<Statsig> {
919        let lock = match SHARED_INSTANCE.lock() {
920            Ok(lock) => lock,
921            Err(e) => {
922                log_e!(TAG, "Statsig::shared() mutex error: {}", e);
923                return Arc::new(Statsig::new(ERROR_SDK_KEY, None));
924            }
925        };
926
927        match lock.as_ref() {
928            Some(statsig) => statsig.clone(),
929            None => {
930                log_e!(
931                    TAG,
932                    "Statsig::shared() called, but no instance has been set with Statsig::new_shared(...)"
933                );
934                Arc::new(Statsig::new(ERROR_SDK_KEY, None))
935            }
936        }
937    }
938
939    pub fn new_shared(
940        sdk_key: &str,
941        options: Option<Arc<StatsigOptions>>,
942    ) -> Result<Arc<Statsig>, StatsigErr> {
943        match SHARED_INSTANCE.lock() {
944            Ok(mut lock) => {
945                if lock.is_some() {
946                    let message = "Statsig shared instance already exists. Call Statsig::remove_shared() before creating a new instance.";
947                    log_e!(TAG, "{}", message);
948                    return Err(StatsigErr::SharedInstanceFailure(message.to_string()));
949                }
950
951                let statsig = Arc::new(Statsig::new(sdk_key, options));
952                *lock = Some(statsig.clone());
953                Ok(statsig)
954            }
955            Err(e) => {
956                let message = format!("Statsig::new_shared() mutex error: {}", e);
957                log_e!(TAG, "{}", message);
958                Err(StatsigErr::SharedInstanceFailure(message))
959            }
960        }
961    }
962
963    pub fn remove_shared() {
964        match SHARED_INSTANCE.lock() {
965            Ok(mut lock) => {
966                *lock = None;
967            }
968            Err(e) => {
969                log_e!(TAG, "Statsig::remove_shared() mutex error: {}", e);
970            }
971        }
972    }
973
974    pub fn has_shared_instance() -> bool {
975        match SHARED_INSTANCE.lock() {
976            Ok(lock) => lock.is_some(),
977            Err(_) => false,
978        }
979    }
980}
981
982// -------------------------
983//   Feature Gate Functions
984// -------------------------
985
986impl Statsig {
987    pub fn check_gate(&self, user: &StatsigUser, gate_name: &str) -> bool {
988        self.check_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
989    }
990
991    pub fn check_gate_with_options(
992        &self,
993        user: &StatsigUser,
994        gate_name: &str,
995        options: FeatureGateEvaluationOptions,
996    ) -> bool {
997        self.get_feature_gate_with_options(user, gate_name, options)
998            .value
999    }
1000
1001    pub fn get_feature_gate(&self, user: &StatsigUser, gate_name: &str) -> FeatureGate {
1002        self.get_feature_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1003    }
1004
1005    pub fn get_feature_gate_with_options(
1006        &self,
1007        user: &StatsigUser,
1008        gate_name: &str,
1009        options: FeatureGateEvaluationOptions,
1010    ) -> FeatureGate {
1011        let user_internal = self.internalize_user(user);
1012        let disable_exposure_logging = options.disable_exposure_logging;
1013        let gate = self.get_feature_gate_impl(&user_internal, gate_name);
1014
1015        if disable_exposure_logging {
1016            log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1017            self.event_logger
1018                .increment_non_exposure_checks_count(gate_name.to_string());
1019        } else {
1020            self.log_gate_exposure(user_internal, gate_name, &gate, false);
1021        }
1022
1023        gate
1024    }
1025
1026    pub fn manually_log_gate_exposure(&self, user: &StatsigUser, gate_name: &str) {
1027        let user_internal = self.internalize_user(user);
1028        let gate = self.get_feature_gate_impl(&user_internal, gate_name);
1029        self.log_gate_exposure(user_internal, gate_name, &gate, true);
1030    }
1031
1032    pub fn get_fields_needed_for_gate(&self, gate_name: &str) -> Vec<String> {
1033        let data = read_lock_or_else!(self.spec_store.data, {
1034            log_error_to_statsig_and_console!(
1035                self.ops_stats.clone(),
1036                TAG,
1037                "Failed to acquire read lock for spec store data"
1038            );
1039            return vec![];
1040        });
1041
1042        let gate = data.values.feature_gates.get(gate_name);
1043        match gate {
1044            Some(gate) => match &gate.fields_used {
1045                Some(fields) => fields.clone(),
1046                None => vec![],
1047            },
1048            None => vec![],
1049        }
1050    }
1051
1052    pub fn override_gate(
1053        &self,
1054        gate_name: &str,
1055        value: bool,
1056        _adapter: Option<&OverrideAdapterType>,
1057    ) {
1058        if let Some(adapter) = &self.override_adapter {
1059            adapter.override_gate(gate_name, value);
1060        }
1061    }
1062
1063    pub fn override_dynamic_config(
1064        &self,
1065        config_name: &str,
1066        value: HashMap<String, serde_json::Value>,
1067        _adapter: Option<&OverrideAdapterType>,
1068    ) {
1069        if let Some(adapter) = &self.override_adapter {
1070            adapter.override_dynamic_config(config_name, value);
1071        }
1072    }
1073
1074    pub fn override_layer(
1075        &self,
1076        layer_name: &str,
1077        value: HashMap<String, serde_json::Value>,
1078        _adapter: Option<&OverrideAdapterType>,
1079    ) {
1080        if let Some(adapter) = &self.override_adapter {
1081            adapter.override_layer(layer_name, value);
1082        }
1083    }
1084
1085    pub fn override_experiment(
1086        &self,
1087        experiment_name: &str,
1088        value: HashMap<String, serde_json::Value>,
1089        _adapter: Option<&OverrideAdapterType>,
1090    ) {
1091        if let Some(adapter) = &self.override_adapter {
1092            adapter.override_experiment(experiment_name, value);
1093        }
1094    }
1095
1096    pub fn override_experiment_by_group_name(
1097        &self,
1098        experiment_name: &str,
1099        group_name: &str,
1100        _adapter: Option<&OverrideAdapterType>,
1101    ) {
1102        if let Some(adapter) = &self.override_adapter {
1103            adapter.override_experiment_by_group_name(experiment_name, group_name);
1104        }
1105    }
1106}
1107
1108// -------------------------
1109//   Dynamic Config Functions
1110// -------------------------
1111
1112impl Statsig {
1113    pub fn get_dynamic_config(
1114        &self,
1115        user: &StatsigUser,
1116        dynamic_config_name: &str,
1117    ) -> DynamicConfig {
1118        self.get_dynamic_config_with_options(
1119            user,
1120            dynamic_config_name,
1121            DynamicConfigEvaluationOptions::default(),
1122        )
1123    }
1124
1125    pub fn get_dynamic_config_with_options(
1126        &self,
1127        user: &StatsigUser,
1128        dynamic_config_name: &str,
1129        options: DynamicConfigEvaluationOptions,
1130    ) -> DynamicConfig {
1131        let user_internal = self.internalize_user(user);
1132        let disable_exposure_logging = options.disable_exposure_logging;
1133        let config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1134
1135        if disable_exposure_logging {
1136            log_d!(
1137                TAG,
1138                "Exposure logging is disabled for Dynamic Config {}",
1139                dynamic_config_name
1140            );
1141            self.event_logger
1142                .increment_non_exposure_checks_count(dynamic_config_name.to_string());
1143        } else {
1144            self.log_dynamic_config_exposure(user_internal, dynamic_config_name, &config, false);
1145        }
1146
1147        config
1148    }
1149
1150    pub fn manually_log_dynamic_config_exposure(
1151        &self,
1152        user: &StatsigUser,
1153        dynamic_config_name: &str,
1154    ) {
1155        let user_internal = self.internalize_user(user);
1156        let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1157        self.log_dynamic_config_exposure(user_internal, dynamic_config_name, &dynamic_config, true);
1158    }
1159
1160    pub fn get_fields_needed_for_dynamic_config(&self, config_name: &str) -> Vec<String> {
1161        let data = read_lock_or_else!(self.spec_store.data, {
1162            log_error_to_statsig_and_console!(
1163                self.ops_stats.clone(),
1164                TAG,
1165                "Failed to acquire read lock for spec store data"
1166            );
1167            return vec![];
1168        });
1169
1170        let config = data.values.dynamic_configs.get(config_name);
1171        match config {
1172            Some(config) => match &config.fields_used {
1173                Some(fields) => fields.clone(),
1174                None => vec![],
1175            },
1176            None => vec![],
1177        }
1178    }
1179}
1180
1181// -------------------------
1182//   Experiment Functions
1183// -------------------------
1184
1185impl Statsig {
1186    pub fn get_experiment(&self, user: &StatsigUser, experiment_name: &str) -> Experiment {
1187        self.get_experiment_with_options(
1188            user,
1189            experiment_name,
1190            ExperimentEvaluationOptions::default(),
1191        )
1192    }
1193
1194    pub fn get_experiment_with_options(
1195        &self,
1196        user: &StatsigUser,
1197        experiment_name: &str,
1198        options: ExperimentEvaluationOptions,
1199    ) -> Experiment {
1200        let user_internal = self.internalize_user(user);
1201        let disable_exposure_logging = options.disable_exposure_logging;
1202        let mut experiment = self.get_experiment_impl(&user_internal, experiment_name);
1203        if let Some(persisted_experiment) = self.persistent_values_manager.as_ref().and_then(|m| {
1204            m.try_apply_sticky_value_to_experiment(&user_internal, &options, &experiment)
1205        }) {
1206            experiment = persisted_experiment
1207        }
1208
1209        if disable_exposure_logging {
1210            log_d!(
1211                TAG,
1212                "Exposure logging is disabled for experiment {}",
1213                experiment_name
1214            );
1215            self.event_logger
1216                .increment_non_exposure_checks_count(experiment_name.to_string());
1217        } else {
1218            self.log_experiment_exposure(user_internal, experiment_name, &experiment, false);
1219        }
1220
1221        experiment
1222    }
1223
1224    pub fn manually_log_experiment_exposure(&self, user: &StatsigUser, experiment_name: &str) {
1225        let user_internal = self.internalize_user(user);
1226        let experiment = self.get_experiment_impl(&user_internal, experiment_name);
1227        self.log_experiment_exposure(user_internal, experiment_name, &experiment, true);
1228    }
1229
1230    pub fn get_fields_needed_for_experiment(&self, experiment_name: &str) -> Vec<String> {
1231        let data = read_lock_or_else!(self.spec_store.data, {
1232            log_error_to_statsig_and_console!(
1233                self.ops_stats.clone(),
1234                TAG,
1235                "Failed to acquire read lock for spec store data"
1236            );
1237            return vec![];
1238        });
1239
1240        let config = data.values.dynamic_configs.get(experiment_name);
1241        match config {
1242            Some(config) => match &config.fields_used {
1243                Some(fields) => fields.clone(),
1244                None => vec![],
1245            },
1246            None => vec![],
1247        }
1248    }
1249}
1250
1251// -------------------------
1252//   Layer Functions
1253// -------------------------
1254
1255impl Statsig {
1256    pub fn get_layer(&self, user: &StatsigUser, layer_name: &str) -> Layer {
1257        self.get_layer_with_options(user, layer_name, LayerEvaluationOptions::default())
1258    }
1259
1260    pub fn get_layer_with_options(
1261        &self,
1262        user: &StatsigUser,
1263        layer_name: &str,
1264        options: LayerEvaluationOptions,
1265    ) -> Layer {
1266        let user_internal = self.internalize_user(user);
1267        self.get_layer_impl(user_internal, layer_name, options)
1268    }
1269
1270    pub fn manually_log_layer_parameter_exposure(
1271        &self,
1272        user: &StatsigUser,
1273        layer_name: &str,
1274        parameter_name: String,
1275    ) {
1276        let user_internal = self.internalize_user(user);
1277        let layer =
1278            self.get_layer_impl(user_internal, layer_name, LayerEvaluationOptions::default());
1279
1280        let layer_eval = layer.__evaluation.as_ref();
1281
1282        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1283            &layer.__user.get_sampling_key(),
1284            layer_eval.map(AnyEvaluation::from).as_ref(),
1285            Some(parameter_name.as_str()),
1286        );
1287
1288        if !sampling_details.should_send_exposure {
1289            return;
1290        }
1291
1292        self.event_logger
1293            .enqueue(QueuedEventPayload::LayerExposure(LayerExposure {
1294                user: layer.__user,
1295                parameter_name,
1296                evaluation: layer.__evaluation,
1297                layer_name: layer.name,
1298                evaluation_details: layer.details,
1299                version: layer.__version,
1300                is_manual_exposure: true,
1301                sampling_details,
1302                override_config_name: layer.__override_config_name.clone(),
1303            }));
1304    }
1305
1306    pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> {
1307        let data = read_lock_or_else!(self.spec_store.data, {
1308            log_error_to_statsig_and_console!(
1309                self.ops_stats.clone(),
1310                TAG,
1311                "Failed to acquire read lock for spec store data"
1312            );
1313            return vec![];
1314        });
1315
1316        let layer = data.values.layer_configs.get(layer_name);
1317        match layer {
1318            Some(layer) => match &layer.fields_used {
1319                Some(fields) => fields.clone(),
1320                None => vec![],
1321            },
1322            None => vec![],
1323        }
1324    }
1325}
1326
1327// -------------------------
1328//   Internal Functions
1329// -------------------------
1330
1331impl Statsig {
1332    pub(crate) fn get_from_statsig_env(&self, key: &str) -> Option<DynamicValue> {
1333        if let Some(env) = &self.statsig_environment {
1334            return env.get(key).cloned();
1335        }
1336
1337        if let Ok(fallback_env) = self.fallback_environment.lock() {
1338            if let Some(env) = &*fallback_env {
1339                return env.get(key).cloned();
1340            }
1341        }
1342
1343        None
1344    }
1345
1346    pub(crate) fn get_value_from_global_custom_fields(&self, key: &str) -> Option<&DynamicValue> {
1347        if let Some(env) = &self.options.global_custom_fields {
1348            return env.get(key);
1349        }
1350
1351        None
1352    }
1353
1354    pub(crate) fn use_global_custom_fields<T>(
1355        &self,
1356        f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> Result<(), T>,
1357    ) -> Result<(), T> {
1358        f(self.options.global_custom_fields.as_ref())
1359    }
1360
1361    pub(crate) fn use_statsig_env<T>(
1362        &self,
1363        f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1364    ) -> T {
1365        if let Some(env) = &self.statsig_environment {
1366            return f(Some(env));
1367        }
1368
1369        if let Ok(fallback_env) = self.fallback_environment.lock() {
1370            if let Some(env) = &*fallback_env {
1371                return f(Some(env));
1372            }
1373        }
1374
1375        f(None)
1376    }
1377}
1378
1379// -------------------------
1380//   Private Functions
1381// -------------------------
1382
1383impl Statsig {
1384    fn evaluate_spec<T>(
1385        &self,
1386        user_internal: &StatsigUserInternal,
1387        spec_name: &str,
1388        make_empty_result: impl FnOnce(EvaluationDetails) -> T,
1389        make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
1390        spec_type: &SpecType,
1391    ) -> T {
1392        let data = read_lock_or_else!(self.spec_store.data, {
1393            log_error_to_statsig_and_console!(
1394                &self.ops_stats,
1395                TAG,
1396                "Failed to acquire read lock for spec store data"
1397            );
1398            return make_empty_result(EvaluationDetails::unrecognized_no_data());
1399        });
1400        let app_id = data.values.app_id.as_ref();
1401        let mut context = EvaluatorContext::new(
1402            user_internal,
1403            &data,
1404            &self.hashing,
1405            &app_id,
1406            &self.override_adapter,
1407        );
1408
1409        match Evaluator::evaluate_with_details(&mut context, spec_name, spec_type) {
1410            Ok(eval_details) => make_result(context.result, eval_details),
1411            Err(e) => {
1412                log_error_to_statsig_and_console!(&self.ops_stats, TAG, "Error evaluating: {}", e);
1413                make_empty_result(EvaluationDetails::error(&e.to_string()))
1414            }
1415        }
1416    }
1417
1418    fn get_feature_gate_impl(
1419        &self,
1420        user_internal: &StatsigUserInternal,
1421        gate_name: &str,
1422    ) -> FeatureGate {
1423        self.evaluate_spec(
1424            user_internal,
1425            gate_name,
1426            |eval_details| make_feature_gate(gate_name, None, eval_details, None, None),
1427            |mut result, eval_details| {
1428                let evaluation = result_to_gate_eval(gate_name, &mut result);
1429                make_feature_gate(
1430                    gate_name,
1431                    Some(evaluation),
1432                    eval_details,
1433                    result.version,
1434                    result.override_config_name,
1435                )
1436            },
1437            &SpecType::Gate,
1438        )
1439    }
1440
1441    fn get_dynamic_config_impl(
1442        &self,
1443        user_internal: &StatsigUserInternal,
1444        config_name: &str,
1445    ) -> DynamicConfig {
1446        self.evaluate_spec(
1447            user_internal,
1448            config_name,
1449            |eval_details| make_dynamic_config(config_name, None, eval_details, None, None),
1450            |mut result, eval_details| {
1451                let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
1452                make_dynamic_config(
1453                    config_name,
1454                    Some(evaluation),
1455                    eval_details,
1456                    result.version,
1457                    result.override_config_name,
1458                )
1459            },
1460            &SpecType::DynamicConfig,
1461        )
1462    }
1463
1464    fn get_experiment_impl(
1465        &self,
1466        user_internal: &StatsigUserInternal,
1467        experiment_name: &str,
1468    ) -> Experiment {
1469        self.evaluate_spec(
1470            user_internal,
1471            experiment_name,
1472            |eval_details| make_experiment(experiment_name, None, eval_details, None, None),
1473            |mut result, eval_details| {
1474                let data = read_lock_or_else!(self.spec_store.data, {
1475                    let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
1476                    return make_experiment(
1477                        experiment_name,
1478                        Some(evaluation),
1479                        eval_details,
1480                        result.version,
1481                        result.override_config_name,
1482                    );
1483                });
1484                let evaluation = result_to_experiment_eval(
1485                    experiment_name,
1486                    data.values.dynamic_configs.get(experiment_name),
1487                    &mut result,
1488                );
1489                make_experiment(
1490                    experiment_name,
1491                    Some(evaluation),
1492                    eval_details,
1493                    result.version,
1494                    result.override_config_name,
1495                )
1496            },
1497            &SpecType::Experiment,
1498        )
1499    }
1500
1501    fn get_layer_impl(
1502        &self,
1503        user_internal: StatsigUserInternal,
1504        layer_name: &str,
1505        evaluation_options: LayerEvaluationOptions,
1506    ) -> Layer {
1507        let disable_exposure_logging = evaluation_options.disable_exposure_logging;
1508        let event_logger_ptr = Arc::downgrade(&self.event_logger);
1509        let sampling_processor_ptr = Arc::downgrade(&self.sampling_processor);
1510
1511        if disable_exposure_logging {
1512            self.event_logger
1513                .increment_non_exposure_checks_count(layer_name.to_string());
1514        }
1515
1516        let mut layer = self.evaluate_spec(
1517            &user_internal,
1518            layer_name,
1519            |eval_details| {
1520                make_layer(
1521                    user_internal.to_loggable(),
1522                    layer_name,
1523                    None,
1524                    eval_details,
1525                    None,
1526                    None,
1527                    disable_exposure_logging,
1528                    None,
1529                    None,
1530                )
1531            },
1532            |mut result, eval_details| {
1533                let evaluation = result_to_layer_eval(layer_name, &mut result);
1534                let event_logger_ptr = Arc::downgrade(&self.event_logger);
1535                let sampling_processor_ptr = Arc::downgrade(&self.sampling_processor);
1536
1537                make_layer(
1538                    user_internal.to_loggable(),
1539                    layer_name,
1540                    Some(evaluation),
1541                    eval_details,
1542                    Some(event_logger_ptr),
1543                    result.version,
1544                    disable_exposure_logging,
1545                    Some(sampling_processor_ptr),
1546                    result.override_config_name,
1547                )
1548            },
1549            &SpecType::Layer,
1550        );
1551        if let Some(persisted_layer) = self.persistent_values_manager.as_ref().and_then(|p| {
1552            p.try_apply_sticky_value_to_layer(
1553                &user_internal,
1554                &evaluation_options,
1555                &layer,
1556                Some(event_logger_ptr),
1557                Some(sampling_processor_ptr),
1558                disable_exposure_logging,
1559            )
1560        }) {
1561            layer = persisted_layer
1562        }
1563        layer
1564    }
1565
1566    fn log_gate_exposure(
1567        &self,
1568        user: StatsigUserInternal,
1569        gate_name: &str,
1570        gate: &FeatureGate,
1571        is_manual: bool,
1572    ) {
1573        let gate_eval = gate.__evaluation.as_ref();
1574        let secondary_exposures = gate_eval.map(|eval| &eval.base.secondary_exposures);
1575        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1576            &user.get_sampling_key(),
1577            gate_eval.map(AnyEvaluation::from).as_ref(),
1578            None,
1579        );
1580
1581        if !sampling_details.should_send_exposure {
1582            return;
1583        }
1584
1585        self.event_logger
1586            .enqueue(QueuedEventPayload::GateExposure(GateExposure {
1587                user: user.to_loggable(),
1588                gate_name: gate_name.to_string(),
1589                value: gate.value,
1590                rule_id: Some(gate.rule_id.clone()),
1591                secondary_exposures: secondary_exposures.cloned(),
1592                evaluation_details: gate.details.clone(),
1593                version: gate.__version,
1594                is_manual_exposure: is_manual,
1595                sampling_details,
1596                override_config_name: gate.__override_config_name.clone(),
1597            }));
1598    }
1599
1600    fn log_dynamic_config_exposure(
1601        &self,
1602        user_internal: StatsigUserInternal,
1603        dynamic_config_name: &str,
1604        dynamic_config: &DynamicConfig,
1605        is_manual: bool,
1606    ) {
1607        let config_eval = dynamic_config.__evaluation.as_ref();
1608        let base_eval = config_eval.map(|eval| eval.base.clone());
1609        let loggable_user = user_internal.to_loggable();
1610
1611        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1612            &loggable_user.get_sampling_key(),
1613            config_eval.map(AnyEvaluation::from).as_ref(),
1614            None,
1615        );
1616
1617        if !sampling_details.should_send_exposure {
1618            return;
1619        }
1620
1621        self.event_logger
1622            .enqueue(QueuedEventPayload::ConfigExposure(ConfigExposure {
1623                user: loggable_user,
1624                evaluation: base_eval,
1625                evaluation_details: dynamic_config.details.clone(),
1626                config_name: dynamic_config_name.to_string(),
1627                rule_passed: dynamic_config.__evaluation.as_ref().map(|eval| eval.passed),
1628                version: dynamic_config.__version,
1629                is_manual_exposure: is_manual,
1630                sampling_details,
1631                override_config_name: dynamic_config.__override_config_name.clone(),
1632            }));
1633    }
1634
1635    fn log_experiment_exposure(
1636        &self,
1637        user_internal: StatsigUserInternal,
1638        experiment_name: &str,
1639        experiment: &Experiment,
1640        is_manual: bool,
1641    ) {
1642        let experiment_eval = experiment.__evaluation.as_ref();
1643        let base_eval = experiment_eval.map(|eval| eval.base.clone());
1644
1645        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1646            &user_internal.get_sampling_key(),
1647            experiment_eval.map(AnyEvaluation::from).as_ref(),
1648            None,
1649        );
1650
1651        if !sampling_details.should_send_exposure {
1652            return;
1653        }
1654
1655        let loggable_user = user_internal.to_loggable();
1656
1657        self.event_logger
1658            .enqueue(QueuedEventPayload::ConfigExposure(ConfigExposure {
1659                user: loggable_user,
1660                evaluation: base_eval,
1661                evaluation_details: experiment.details.clone(),
1662                config_name: experiment_name.to_string(),
1663                rule_passed: None,
1664                version: experiment.__version,
1665                is_manual_exposure: is_manual,
1666                sampling_details,
1667                override_config_name: experiment.__override_config_name.clone(),
1668            }));
1669    }
1670
1671    fn internalize_user<'s, 'u>(&'s self, user: &'u StatsigUser) -> StatsigUserInternal<'s, 'u> {
1672        StatsigUserInternal::new(user, Some(self))
1673    }
1674
1675    fn set_default_environment_from_server(&self) {
1676        let data = read_lock_or_else!(self.spec_store.data, {
1677            return;
1678        });
1679
1680        if let Some(default_env) = data.values.default_environment.as_ref() {
1681            let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
1682
1683            if let Ok(mut fallback_env) = self.fallback_environment.lock() {
1684                *fallback_env = Some(env_map);
1685            }
1686        }
1687    }
1688
1689    fn log_init_finish(
1690        &self,
1691        success: bool,
1692        error_message: &Option<String>,
1693        duration: &f64,
1694        specs_info: &SpecsInfo,
1695    ) {
1696        let is_store_populated = specs_info.source != SpecsSource::NoValues;
1697        let source_str = specs_info.source.to_string();
1698        self.ops_stats.log(ObservabilityEvent::new_event(
1699            MetricType::Dist,
1700            "initialization".to_string(),
1701            *duration,
1702            Some(HashMap::from([
1703                ("success".to_owned(), success.to_string()),
1704                ("source".to_owned(), source_str.clone()),
1705                ("store_populated".to_owned(), is_store_populated.to_string()),
1706            ])),
1707        ));
1708        self.ops_stats.add_marker(
1709            {
1710                let marker =
1711                    Marker::new(KeyType::Overall, ActionType::End, Some(StepType::Process))
1712                        .with_is_success(success)
1713                        .with_config_spec_ready(specs_info.source != SpecsSource::NoValues)
1714                        .with_source(source_str);
1715
1716                if let Some(msg) = &error_message {
1717                    marker.with_message(msg.to_string())
1718                } else {
1719                    marker
1720                }
1721            },
1722            Some(ContextType::Initialize),
1723        );
1724        self.ops_stats
1725            .enqueue_diagnostics_event(None, Some(ContextType::Initialize));
1726    }
1727}
1728
1729fn initialize_event_logging_adapter(
1730    sdk_key: &str,
1731    options: &StatsigOptions,
1732) -> Arc<dyn EventLoggingAdapter> {
1733    let adapter = options.event_logging_adapter.clone().unwrap_or_else(|| {
1734        Arc::new(StatsigHttpEventLoggingAdapter::new(
1735            sdk_key,
1736            options.log_event_url.as_ref(),
1737            options.disable_network,
1738        ))
1739    });
1740    adapter
1741}
1742
1743fn initialize_specs_adapter(
1744    sdk_key: &str,
1745    options: &StatsigOptions,
1746    hashing: &HashUtil,
1747) -> Arc<dyn SpecsAdapter> {
1748    if let Some(adapter) = options.specs_adapter.clone() {
1749        log_d!(TAG, "Using provided SpecsAdapter: {}", sdk_key);
1750        return adapter;
1751    }
1752
1753    if let Some(adapter_config) = options.spec_adapters_config.clone() {
1754        return Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
1755            sdk_key,
1756            adapter_config,
1757            options,
1758            hashing,
1759        ));
1760    }
1761
1762    if let Some(data_adapter) = options.data_store.clone() {
1763        return Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
1764            sdk_key,
1765            data_adapter,
1766            options,
1767            hashing,
1768        ));
1769    }
1770
1771    Arc::new(StatsigHttpSpecsAdapter::new(
1772        sdk_key,
1773        options.specs_url.as_ref(),
1774        options.fallback_to_statsig_api.unwrap_or(false),
1775        options.specs_sync_interval_ms,
1776        options.disable_network,
1777    ))
1778}
1779
1780fn initialize_id_lists_adapter(
1781    sdk_key: &str,
1782    options: &StatsigOptions,
1783) -> Option<Arc<dyn IdListsAdapter>> {
1784    if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
1785        return Some(id_lists_adapter);
1786    }
1787
1788    if options.enable_id_lists.unwrap_or(false) {
1789        return Some(Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options)));
1790    }
1791
1792    None
1793}
1794
1795fn setup_ops_stats(
1796    sdk_key: &str,
1797    options: &StatsigOptions,
1798    statsig_runtime: Arc<StatsigRuntime>,
1799    error_observer: &Arc<dyn OpsStatsEventObserver>,
1800    diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
1801    external_observer: &Option<Weak<dyn ObservabilityClient>>,
1802) -> Arc<OpsStatsForInstance> {
1803    // TODO migrate output logger to use ops_stats
1804    initialize_simple_output_logger(&options.output_log_level);
1805
1806    let ops_stat = OPS_STATS.get_for_instance(sdk_key);
1807    ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
1808    ops_stat.subscribe(
1809        statsig_runtime.clone(),
1810        Arc::downgrade(diagnostics_observer),
1811    );
1812
1813    if let Some(ob_client) = external_observer {
1814        if let Some(client) = ob_client.upgrade() {
1815            client.init();
1816            let as_observer = client.to_ops_stats_event_observer();
1817            ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
1818        }
1819    }
1820
1821    ops_stat
1822}