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