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