statsig_rust/
statsig.rs

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