statsig_rust/
statsig.rs

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