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