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