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::{ParsedUserAgentValue, 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, StatsigLogLineLevel};
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_third_party_ua_parser = options.use_third_party_ua_parser.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_third_party_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_third_party_ua_parser = self.should_user_third_party_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 use_third_party_ua_parser {
499            Some(self.statsig_runtime.spawn(INIT_UA_TAG, |_| async {
500                UserAgentParser::load_parser();
501            }))
502        } else {
503            None
504        };
505
506        let init_res = match self
507            .specs_adapter
508            .inner
509            .clone()
510            .start(&self.statsig_runtime)
511            .await
512        {
513            Ok(()) => Ok(()),
514            Err(e) => {
515                self.spec_store.set_source(SpecsSource::NoValues);
516                error_message = Some(format!("Failed to start specs adapter: {e}"));
517                Err(e)
518            }
519        };
520
521        if let Some(adapter) = &self.id_lists_adapter.inner {
522            match adapter
523                .clone()
524                .start(&self.statsig_runtime, self.spec_store.clone())
525                .await
526            {
527                Ok(()) => {
528                    id_list_ready = Some(true);
529                }
530                Err(e) => {
531                    id_list_ready = Some(false);
532                    error_message.get_or_insert_with(|| format!("Failed to sync ID lists: {e}"));
533                }
534            }
535            if let Err(e) = adapter
536                .clone()
537                .schedule_background_sync(&self.statsig_runtime)
538                .await
539            {
540                log_w!(TAG, "Failed to schedule id_list background job {}", e);
541            }
542        }
543
544        if let Err(e) = self
545            .event_logging_adapter
546            .clone()
547            .start(&self.statsig_runtime)
548            .await
549        {
550            log_error_to_statsig_and_console!(
551                self.ops_stats.clone(),
552                TAG,
553                StatsigErr::UnstartedAdapter(format!("Failed to start event logging adapter: {e}"))
554            );
555        }
556
557        let spec_info = self.spec_store.get_current_specs_info();
558        let duration = start_time.elapsed().as_millis() as f64;
559
560        self.set_default_environment_from_server();
561
562        if self.options.wait_for_country_lookup_init.unwrap_or(false) {
563            match init_country_lookup {
564                Some(Ok(task_id)) => {
565                    let _ = self
566                        .statsig_runtime
567                        .await_join_handle(INIT_IP_TAG, &task_id)
568                        .await;
569                }
570                Some(Err(e)) => {
571                    log_error_to_statsig_and_console!(
572                        self.ops_stats.clone(),
573                        TAG,
574                        StatsigErr::UnstartedAdapter(format!(
575                            "Failed to spawn country lookup task: {e}"
576                        ))
577                    );
578                }
579                _ => {}
580            }
581        }
582        if self.options.wait_for_user_agent_init.unwrap_or(false) {
583            match init_ua {
584                Some(Ok(task_id)) => {
585                    let _ = self
586                        .statsig_runtime
587                        .await_join_handle(INIT_UA_TAG, &task_id)
588                        .await;
589                }
590                Some(Err(e)) => {
591                    log_error_to_statsig_and_console!(
592                        self.ops_stats.clone(),
593                        TAG,
594                        StatsigErr::UnstartedAdapter(format!(
595                            "Failed to spawn user agent parser task: {e}"
596                        ))
597                    );
598                }
599                _ => {}
600            }
601        }
602
603        let error = init_res.clone().err();
604
605        let success = Self::start_background_tasks(
606            self.statsig_runtime.clone(),
607            self.id_lists_adapter.inner.clone(),
608            self.specs_adapter.inner.clone(),
609            self.ops_stats.clone(),
610            self.background_tasks_started.clone(),
611        )
612        .await;
613
614        Ok(self.construct_initialize_details(success, duration, spec_info, id_list_ready, error))
615    }
616
617    fn construct_initialize_details(
618        &self,
619        init_success: bool,
620        duration: f64,
621        specs_info: SpecsInfo,
622        is_id_list_ready: Option<bool>,
623        error: Option<StatsigErr>,
624    ) -> InitializeDetails {
625        let is_config_spec_ready = matches!(specs_info.lcut, Some(v) if v != 0);
626
627        let failure_details =
628            if let Some(StatsigErr::NetworkError(NetworkError::DisableNetworkOn(_))) = error {
629                None
630            } else {
631                error.as_ref().map(|e| FailureDetails {
632                    reason: e.to_string(),
633                    error: Some(e.clone()),
634                })
635            };
636
637        InitializeDetails {
638            init_success,
639            is_config_spec_ready,
640            is_id_list_ready,
641            source: specs_info.source.clone(),
642            failure_details,
643            duration,
644            spec_source_api: specs_info.source_api.clone(),
645        }
646    }
647
648    fn timeout_failure(&self, timeout_ms: u64) -> InitializeDetails {
649        InitializeDetails {
650            init_success: false,
651            is_config_spec_ready: false,
652            is_id_list_ready: None,
653            source: SpecsSource::Uninitialized,
654            failure_details: Some(FailureDetails {
655                reason: "Initialization timed out".to_string(),
656                error: None,
657            }),
658            duration: timeout_ms as f64,
659            spec_source_api: None,
660        }
661    }
662
663    fn log_init_details(&self, init_details: &Result<InitializeDetails, StatsigErr>) {
664        match init_details {
665            Ok(details) => {
666                self.log_init_finish(
667                    details.init_success,
668                    &None,
669                    &details.duration,
670                    &self.spec_store.get_current_specs_info(),
671                );
672                if let Some(failure) = &details.failure_details {
673                    log_error_to_statsig_and_console!(
674                        self.ops_stats,
675                        TAG,
676                        StatsigErr::InitializationError(failure.reason.clone())
677                    );
678                }
679            }
680            Err(err) => {
681                // we store errors on init details so we should never return error and thus do not need to log
682                log_w!(TAG, "Initialization error: {:?}", err);
683            }
684        }
685    }
686
687    pub fn get_context(&self) -> StatsigContext {
688        StatsigContext {
689            sdk_key: self.sdk_key.clone(),
690            options: self.options.clone(),
691            local_override_adapter: self.override_adapter.clone(),
692            error_observer: self.error_observer.clone(),
693            diagnostics_observer: self.diagnostics_observer.clone(),
694            spec_store: self.spec_store.clone(),
695        }
696    }
697
698    pub fn log_event(
699        &self,
700        user: &StatsigUser,
701        event_name: &str,
702        value: Option<String>,
703        metadata: Option<HashMap<String, String>>,
704    ) {
705        let user_internal = self.internalize_user(user);
706
707        self.event_logger.enqueue(EnqueuePassthroughOp {
708            event: StatsigEventInternal::new_custom_event(
709                user_internal.to_loggable(),
710                event_name.to_string(),
711                value.map(|v| json!(v)),
712                metadata,
713            ),
714        });
715    }
716
717    pub fn log_event_with_number(
718        &self,
719        user: &StatsigUser,
720        event_name: &str,
721        value: Option<f64>,
722        metadata: Option<HashMap<String, String>>,
723    ) {
724        let user_internal = self.internalize_user(user);
725        self.event_logger.enqueue(EnqueuePassthroughOp {
726            event: StatsigEventInternal::new_custom_event(
727                user_internal.to_loggable(),
728                event_name.to_string(),
729                value.map(|v| json!(v)),
730                metadata,
731            ),
732        });
733    }
734
735    pub fn forward_log_line_event(
736        &self,
737        user: &StatsigUser,
738        log_level: StatsigLogLineLevel,
739        value: Option<String>,
740        metadata: Option<HashMap<String, String>>,
741    ) {
742        let user_internal = self.internalize_user(user);
743        self.event_logger.enqueue(EnqueuePassthroughOp {
744            event: StatsigEventInternal::new_statsig_log_line_event(
745                user_internal.to_loggable(),
746                log_level,
747                value,
748                metadata,
749            ),
750        });
751    }
752
753    pub fn log_layer_param_exposure_with_layer_json(
754        &self,
755        layer_json: String,
756        parameter_name: String,
757    ) {
758        let layer = match serde_json::from_str::<Layer>(&layer_json) {
759            Ok(layer) => layer,
760            Err(e) => {
761                log_error_to_statsig_and_console!(
762                    self.ops_stats.clone(),
763                    TAG,
764                    StatsigErr::ShutdownFailure(e.to_string())
765                );
766                return;
767            }
768        };
769
770        self.log_layer_param_exposure_with_layer(layer, parameter_name);
771    }
772
773    pub fn log_layer_param_exposure_with_layer(&self, layer: Layer, parameter_name: String) {
774        if layer.__disable_exposure {
775            self.event_logger.increment_non_exposure_checks(&layer.name);
776            return;
777        }
778
779        self.event_logger
780            .enqueue(EnqueueLayerParamExpoOp::LayerOwned(
781                Utc::now().timestamp_millis() as u64,
782                Box::new(layer),
783                parameter_name,
784                ExposureTrigger::Auto,
785            ));
786    }
787
788    pub async fn flush_events(&self) {
789        let _ = self.event_logger.flush_all_pending_events().await;
790    }
791
792    pub fn get_client_init_response(&self, user: &StatsigUser) -> InitializeResponse {
793        self.get_client_init_response_with_options(user, self.gcir_formatter.get_default_options())
794    }
795
796    pub fn get_client_init_response_with_options(
797        &self,
798        user: &StatsigUser,
799        options: &ClientInitResponseOptions,
800    ) -> InitializeResponse {
801        let user_internal = self.internalize_user(user);
802        self.gcir_formatter
803            .get_as_v1_format(user_internal, &self.hashing, options)
804    }
805
806    pub fn get_client_init_response_as_string(&self, user: &StatsigUser) -> String {
807        serde_json::to_string(&self.get_client_init_response(user)).unwrap_or_default()
808    }
809
810    pub fn get_client_init_response_with_options_as_string(
811        &self,
812        user: &StatsigUser,
813        options: &ClientInitResponseOptions,
814    ) -> String {
815        let user_internal = self.internalize_user(user);
816        let response = match options.response_format {
817            Some(GCIRResponseFormat::InitializeWithSecondaryExposureMapping) => {
818                json!(self
819                    .gcir_formatter
820                    .get_as_v2_format(user_internal, &self.hashing, options))
821            }
822            _ => json!(self
823                .gcir_formatter
824                .get_as_v1_format(user_internal, &self.hashing, options)),
825        };
826
827        json!(response).to_string()
828    }
829
830    pub fn get_string_parameter_from_store(
831        &self,
832        user: &StatsigUser,
833        parameter_store_name: &str,
834        parameter_name: &str,
835        fallback: Option<String>,
836        options: Option<ParameterStoreEvaluationOptions>,
837    ) -> Option<String> {
838        self.get_parameter_from_store(
839            user,
840            parameter_store_name,
841            parameter_name,
842            fallback,
843            options,
844        )
845    }
846
847    pub fn get_boolean_parameter_from_store(
848        &self,
849        user: &StatsigUser,
850        parameter_store_name: &str,
851        parameter_name: &str,
852        fallback: Option<bool>,
853        options: Option<ParameterStoreEvaluationOptions>,
854    ) -> Option<bool> {
855        self.get_parameter_from_store(
856            user,
857            parameter_store_name,
858            parameter_name,
859            fallback,
860            options,
861        )
862    }
863
864    pub fn get_float_parameter_from_store(
865        &self,
866        user: &StatsigUser,
867        parameter_store_name: &str,
868        parameter_name: &str,
869        fallback: Option<f64>,
870        options: Option<ParameterStoreEvaluationOptions>,
871    ) -> Option<f64> {
872        self.get_parameter_from_store(
873            user,
874            parameter_store_name,
875            parameter_name,
876            fallback,
877            options,
878        )
879    }
880
881    pub fn get_integer_parameter_from_store(
882        &self,
883        user: &StatsigUser,
884        parameter_store_name: &str,
885        parameter_name: &str,
886        fallback: Option<i64>,
887        options: Option<ParameterStoreEvaluationOptions>,
888    ) -> Option<i64> {
889        self.get_parameter_from_store(
890            user,
891            parameter_store_name,
892            parameter_name,
893            fallback,
894            options,
895        )
896    }
897
898    pub fn get_array_parameter_from_store(
899        &self,
900        user: &StatsigUser,
901        parameter_store_name: &str,
902        parameter_name: &str,
903        fallback: Option<Vec<Value>>,
904        options: Option<ParameterStoreEvaluationOptions>,
905    ) -> Option<Vec<Value>> {
906        self.get_parameter_from_store(
907            user,
908            parameter_store_name,
909            parameter_name,
910            fallback,
911            options,
912        )
913    }
914
915    pub fn get_object_parameter_from_store(
916        &self,
917        user: &StatsigUser,
918        parameter_store_name: &str,
919        parameter_name: &str,
920        fallback: Option<HashMap<String, Value>>,
921        options: Option<ParameterStoreEvaluationOptions>,
922    ) -> Option<HashMap<String, Value>> {
923        self.get_parameter_from_store(
924            user,
925            parameter_store_name,
926            parameter_name,
927            fallback,
928            options,
929        )
930    }
931
932    pub fn get_parameter_from_store<T: DeserializeOwned>(
933        &self,
934        user: &StatsigUser,
935        parameter_store_name: &str,
936        parameter_name: &str,
937        fallback: Option<T>,
938        options: Option<ParameterStoreEvaluationOptions>,
939    ) -> Option<T> {
940        let store = self
941            .get_parameter_store_with_options(parameter_store_name, options.unwrap_or_default());
942        match fallback {
943            Some(fallback) => Some(store.get(user, parameter_name, fallback)),
944            None => store.get_opt(user, parameter_name),
945        }
946    }
947
948    pub fn get_parameter_store(&self, parameter_store_name: &str) -> ParameterStore<'_> {
949        self.get_parameter_store_with_options(
950            parameter_store_name,
951            ParameterStoreEvaluationOptions::default(),
952        )
953    }
954
955    pub fn get_parameter_store_with_options(
956        &self,
957        parameter_store_name: &str,
958        options: ParameterStoreEvaluationOptions,
959    ) -> ParameterStore<'_> {
960        self.event_logger
961            .increment_non_exposure_checks(parameter_store_name);
962
963        let data = read_lock_or_else!(self.spec_store.data, {
964            log_error_to_statsig_and_console!(
965                self.ops_stats.clone(),
966                TAG,
967                StatsigErr::LockFailure(
968                    "Failed to acquire read lock for spec store data".to_string()
969                )
970            );
971            return ParameterStore {
972                name: parameter_store_name.to_string(),
973                parameters: HashMap::new(),
974                details: EvaluationDetails::unrecognized_no_data(),
975                options,
976                _statsig_ref: self,
977            };
978        });
979
980        let stores = &data.values.param_stores;
981        let store = match stores {
982            Some(stores) => stores.get(parameter_store_name),
983            None => {
984                return ParameterStore {
985                    name: parameter_store_name.to_string(),
986                    parameters: HashMap::new(),
987                    details: EvaluationDetails::unrecognized(&data),
988                    options,
989                    _statsig_ref: self,
990                };
991            }
992        };
993        match store {
994            Some(store) => ParameterStore {
995                name: parameter_store_name.to_string(),
996                parameters: store.parameters.clone(),
997                details: EvaluationDetails::recognized(&data, &EvaluatorResult::default()),
998                options,
999                _statsig_ref: self,
1000            },
1001            None => ParameterStore {
1002                name: parameter_store_name.to_string(),
1003                parameters: HashMap::new(),
1004                details: EvaluationDetails::unrecognized(&data),
1005                options,
1006                _statsig_ref: self,
1007            },
1008        }
1009    }
1010}
1011
1012// -------------------------
1013//   User Store Functions
1014// -------------------------
1015
1016impl Statsig {
1017    pub fn identify(&self, user: &StatsigUser) {
1018        let user_internal = self.internalize_user(user);
1019
1020        self.event_logger.enqueue(EnqueuePassthroughOp {
1021            event: StatsigEventInternal::new_custom_event(
1022                user_internal.to_loggable(),
1023                "statsig::identify".to_string(),
1024                None,
1025                None,
1026            ),
1027        });
1028    }
1029}
1030
1031// -------------------------
1032//   CMAB Functions
1033// -------------------------
1034
1035impl Statsig {
1036    pub fn get_cmab_ranked_groups(
1037        &self,
1038        user: &StatsigUser,
1039        cmab_name: &str,
1040    ) -> Vec<CMABRankedGroup> {
1041        self.event_logger.increment_non_exposure_checks(cmab_name);
1042
1043        let data = read_lock_or_else!(self.spec_store.data, {
1044            log_error_to_statsig_and_console!(
1045                self.ops_stats.clone(),
1046                TAG,
1047                StatsigErr::LockFailure(
1048                    "Failed to acquire read lock for spec store data".to_string()
1049                )
1050            );
1051            return vec![];
1052        });
1053        let user_internal = self.internalize_user(user);
1054        get_cmab_ranked_list(
1055            &mut EvaluatorContext::new(
1056                &user_internal,
1057                &data,
1058                &self.hashing,
1059                data.values.app_id.as_ref(),
1060                self.override_adapter.as_ref(),
1061                self.should_user_third_party_parser(),
1062            ),
1063            cmab_name,
1064        )
1065    }
1066
1067    pub fn log_cmab_exposure_for_group(
1068        &self,
1069        user: &StatsigUser,
1070        cmab_name: &str,
1071        group_id: String,
1072    ) {
1073        let user_internal = self.internalize_user(user);
1074
1075        let mut experiment = self.get_experiment_impl(&user_internal, cmab_name);
1076        experiment.rule_id = group_id;
1077
1078        self.event_logger.enqueue(EnqueueExperimentExpoOp {
1079            exposure_time: Utc::now().timestamp_millis() as u64,
1080            user: &user_internal,
1081            experiment: &experiment,
1082            trigger: ExposureTrigger::Manual,
1083        });
1084    }
1085}
1086
1087// -------------------------
1088//   Shared Instance Functions
1089// -------------------------
1090
1091impl Statsig {
1092    pub fn shared() -> Arc<Statsig> {
1093        let lock = match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
1094            Some(lock) => lock,
1095            None => {
1096                log_e!(
1097                    TAG,
1098                    "Statsig::shared() mutex error: Failed to lock SHARED_INSTANCE"
1099                );
1100                return Arc::new(Statsig::new(ERROR_SDK_KEY, None));
1101            }
1102        };
1103
1104        match lock.as_ref() {
1105            Some(statsig) => statsig.clone(),
1106            None => {
1107                log_e!(
1108                    TAG,
1109                    "Statsig::shared() called, but no instance has been set with Statsig::new_shared(...)"
1110                );
1111                Arc::new(Statsig::new(ERROR_SDK_KEY, None))
1112            }
1113        }
1114    }
1115
1116    pub fn new_shared(
1117        sdk_key: &str,
1118        options: Option<Arc<StatsigOptions>>,
1119    ) -> Result<Arc<Statsig>, StatsigErr> {
1120        match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
1121            Some(mut lock) => {
1122                if lock.is_some() {
1123                    let message = "Statsig shared instance already exists. Call Statsig::remove_shared() before creating a new instance.";
1124                    log_e!(TAG, "{}", message);
1125                    return Err(StatsigErr::SharedInstanceFailure(message.to_string()));
1126                }
1127
1128                let statsig = Arc::new(Statsig::new(sdk_key, options));
1129                *lock = Some(statsig.clone());
1130                Ok(statsig)
1131            }
1132            None => {
1133                let message = "Statsig::new_shared() mutex error: Failed to lock SHARED_INSTANCE";
1134                log_e!(TAG, "{}", message);
1135                Err(StatsigErr::SharedInstanceFailure(message.to_string()))
1136            }
1137        }
1138    }
1139
1140    pub fn remove_shared() {
1141        match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
1142            Some(mut lock) => {
1143                *lock = None;
1144            }
1145            None => {
1146                log_e!(
1147                    TAG,
1148                    "Statsig::remove_shared() mutex error: Failed to lock SHARED_INSTANCE"
1149                );
1150            }
1151        }
1152    }
1153
1154    pub fn has_shared_instance() -> bool {
1155        match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
1156            Some(lock) => lock.is_some(),
1157            None => false,
1158        }
1159    }
1160}
1161
1162// -------------------------
1163//   Feature Gate Functions
1164// -------------------------
1165
1166impl Statsig {
1167    pub fn check_gate(&self, user: &StatsigUser, gate_name: &str) -> bool {
1168        self.check_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1169    }
1170
1171    pub fn check_gate_with_options(
1172        &self,
1173        user: &StatsigUser,
1174        gate_name: &str,
1175        options: FeatureGateEvaluationOptions,
1176    ) -> bool {
1177        let user_internal = self.internalize_user(user);
1178        let disable_exposure_logging = options.disable_exposure_logging;
1179        let (details, evaluation) = self.get_gate_evaluation(&user_internal, gate_name);
1180
1181        let value = evaluation.as_ref().map(|e| e.value).unwrap_or_default();
1182        let rule_id = evaluation
1183            .as_ref()
1184            .map(|e| e.base.rule_id.clone())
1185            .unwrap_or_default();
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.map(Cow::Owned),
1196                details: details.clone(),
1197                trigger: ExposureTrigger::Auto,
1198            });
1199        }
1200
1201        self.emit_gate_evaluated(gate_name, rule_id.as_str(), value, &details.reason);
1202
1203        value
1204    }
1205
1206    pub fn get_feature_gate(&self, user: &StatsigUser, gate_name: &str) -> FeatureGate {
1207        self.get_feature_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1208    }
1209
1210    pub fn get_feature_gate_with_options(
1211        &self,
1212        user: &StatsigUser,
1213        gate_name: &str,
1214        options: FeatureGateEvaluationOptions,
1215    ) -> FeatureGate {
1216        let user_internal = self.internalize_user(user);
1217        let disable_exposure_logging = options.disable_exposure_logging;
1218        let (details, evaluation) = self.get_gate_evaluation(&user_internal, gate_name);
1219
1220        if disable_exposure_logging {
1221            log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1222            self.event_logger.increment_non_exposure_checks(gate_name);
1223        } else {
1224            self.event_logger.enqueue(EnqueueGateExpoOp {
1225                exposure_time: Utc::now().timestamp_millis() as u64,
1226                user: &user_internal,
1227                queried_gate_name: gate_name,
1228                evaluation: evaluation.as_ref().map(Cow::Borrowed),
1229                details: details.clone(),
1230                trigger: ExposureTrigger::Auto,
1231            });
1232        }
1233
1234        let gate = make_feature_gate(gate_name, evaluation, details);
1235        self.emit_gate_evaluated(gate_name, &gate.rule_id, gate.value, &gate.details.reason);
1236        gate
1237    }
1238
1239    pub fn manually_log_gate_exposure(&self, user: &StatsigUser, gate_name: &str) {
1240        let user_internal = self.internalize_user(user);
1241        let (details, evaluation) = self.get_gate_evaluation(&user_internal, gate_name);
1242        self.event_logger.enqueue(EnqueueGateExpoOp {
1243            exposure_time: Utc::now().timestamp_millis() as u64,
1244            user: &user_internal,
1245            queried_gate_name: gate_name,
1246            evaluation: evaluation.map(Cow::Owned),
1247            details: details.clone(),
1248            trigger: ExposureTrigger::Manual,
1249        });
1250    }
1251
1252    pub fn get_fields_needed_for_gate(&self, gate_name: &str) -> Vec<String> {
1253        let data = read_lock_or_else!(self.spec_store.data, {
1254            log_error_to_statsig_and_console!(
1255                self.ops_stats.clone(),
1256                TAG,
1257                StatsigErr::LockFailure(
1258                    "Failed to acquire read lock for spec store data".to_string()
1259                )
1260            );
1261            return vec![];
1262        });
1263
1264        let gate = data.values.feature_gates.get(gate_name);
1265        match gate {
1266            Some(gate) => match &gate.spec.fields_used {
1267                Some(fields) => fields.iter().map(|f| f.unperformant_to_string()).collect(),
1268                None => vec![],
1269            },
1270            None => vec![],
1271        }
1272    }
1273}
1274
1275// -------------------------
1276//   Override Functions
1277// -------------------------
1278
1279impl Statsig {
1280    pub fn override_gate(&self, gate_name: &str, value: bool, id: Option<&str>) {
1281        if let Some(adapter) = &self.override_adapter {
1282            adapter.override_gate(gate_name, value, id);
1283        }
1284    }
1285
1286    pub fn override_dynamic_config(
1287        &self,
1288        config_name: &str,
1289        value: HashMap<String, serde_json::Value>,
1290        id: Option<&str>,
1291    ) {
1292        if let Some(adapter) = &self.override_adapter {
1293            adapter.override_dynamic_config(config_name, value, id);
1294        }
1295    }
1296
1297    pub fn override_layer(
1298        &self,
1299        layer_name: &str,
1300        value: HashMap<String, serde_json::Value>,
1301        id: Option<&str>,
1302    ) {
1303        if let Some(adapter) = &self.override_adapter {
1304            adapter.override_layer(layer_name, value, id);
1305        }
1306    }
1307
1308    pub fn override_experiment(
1309        &self,
1310        experiment_name: &str,
1311        value: HashMap<String, serde_json::Value>,
1312        id: Option<&str>,
1313    ) {
1314        if let Some(adapter) = &self.override_adapter {
1315            adapter.override_experiment(experiment_name, value, id);
1316        }
1317    }
1318
1319    pub fn override_experiment_by_group_name(
1320        &self,
1321        experiment_name: &str,
1322        group_name: &str,
1323        id: Option<&str>,
1324    ) {
1325        if let Some(adapter) = &self.override_adapter {
1326            adapter.override_experiment_by_group_name(experiment_name, group_name, id);
1327        }
1328    }
1329
1330    pub fn remove_gate_override(&self, gate_name: &str, id: Option<&str>) {
1331        if let Some(adapter) = &self.override_adapter {
1332            adapter.remove_gate_override(gate_name, id);
1333        }
1334    }
1335
1336    pub fn remove_dynamic_config_override(&self, config_name: &str, id: Option<&str>) {
1337        if let Some(adapter) = &self.override_adapter {
1338            adapter.remove_dynamic_config_override(config_name, id);
1339        }
1340    }
1341
1342    pub fn remove_experiment_override(&self, experiment_name: &str, id: Option<&str>) {
1343        if let Some(adapter) = &self.override_adapter {
1344            adapter.remove_experiment_override(experiment_name, id);
1345        }
1346    }
1347
1348    pub fn remove_layer_override(&self, layer_name: &str, id: Option<&str>) {
1349        if let Some(adapter) = &self.override_adapter {
1350            adapter.remove_layer_override(layer_name, id);
1351        }
1352    }
1353
1354    pub fn remove_all_overrides(&self) {
1355        if let Some(adapter) = &self.override_adapter {
1356            adapter.remove_all_overrides();
1357        }
1358    }
1359}
1360
1361// -------------------------
1362//   Debugging Functions
1363// -------------------------
1364
1365impl Statsig {
1366    pub fn get_feature_gate_list(&self) -> Vec<String> {
1367        let data = read_lock_or_else!(self.spec_store.data, {
1368            log_error_to_statsig_and_console!(
1369                &self.ops_stats,
1370                TAG,
1371                StatsigErr::LockFailure(
1372                    "Failed to acquire read lock for spec store data".to_string()
1373                )
1374            );
1375            return vec![];
1376        });
1377
1378        data.values.feature_gates.unperformant_keys()
1379    }
1380
1381    pub fn get_dynamic_config_list(&self) -> Vec<String> {
1382        let data = read_lock_or_else!(self.spec_store.data, {
1383            log_error_to_statsig_and_console!(
1384                &self.ops_stats,
1385                TAG,
1386                StatsigErr::LockFailure(
1387                    "Failed to acquire read lock for spec store data".to_string()
1388                )
1389            );
1390            return vec![];
1391        });
1392
1393        data.values
1394            .dynamic_configs
1395            .unperformant_keys_entity_filter("dynamic_config")
1396    }
1397
1398    pub fn get_experiment_list(&self) -> Vec<String> {
1399        let data = read_lock_or_else!(self.spec_store.data, {
1400            log_error_to_statsig_and_console!(
1401                &self.ops_stats,
1402                TAG,
1403                StatsigErr::LockFailure(
1404                    "Failed to acquire read lock for spec store data".to_string()
1405                )
1406            );
1407            return vec![];
1408        });
1409
1410        data.values
1411            .dynamic_configs
1412            .unperformant_keys_entity_filter("experiment")
1413    }
1414
1415    pub fn get_autotune_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
1428            .dynamic_configs
1429            .unperformant_keys_entity_filter("autotune")
1430    }
1431
1432    pub fn get_parameter_store_list(&self) -> Vec<String> {
1433        let data = read_lock_or_else!(self.spec_store.data, {
1434            log_error_to_statsig_and_console!(
1435                &self.ops_stats,
1436                TAG,
1437                StatsigErr::LockFailure(
1438                    "Failed to acquire read lock for spec store data".to_string()
1439                )
1440            );
1441            return vec![];
1442        });
1443
1444        match &data.values.param_stores {
1445            Some(param_stores) => param_stores.keys().cloned().collect(),
1446            None => vec![],
1447        }
1448    }
1449
1450    pub fn get_layer_list(&self) -> Vec<String> {
1451        let data = read_lock_or_else!(self.spec_store.data, {
1452            log_error_to_statsig_and_console!(
1453                &self.ops_stats,
1454                TAG,
1455                StatsigErr::LockFailure(
1456                    "Failed to acquire read lock for spec store data".to_string()
1457                )
1458            );
1459            return vec![];
1460        });
1461
1462        data.values.layer_configs.unperformant_keys()
1463    }
1464
1465    pub fn __get_parsed_user_agent_value(
1466        &self,
1467        user: &StatsigUser,
1468    ) -> Option<ParsedUserAgentValue> {
1469        UserAgentParser::get_parsed_user_agent_value_for_user(user, &self.options)
1470    }
1471}
1472
1473// -------------------------
1474//   Dynamic Config Functions
1475// -------------------------
1476
1477impl Statsig {
1478    pub fn get_dynamic_config(
1479        &self,
1480        user: &StatsigUser,
1481        dynamic_config_name: &str,
1482    ) -> DynamicConfig {
1483        self.get_dynamic_config_with_options(
1484            user,
1485            dynamic_config_name,
1486            DynamicConfigEvaluationOptions::default(),
1487        )
1488    }
1489
1490    pub fn get_dynamic_config_with_options(
1491        &self,
1492        user: &StatsigUser,
1493        dynamic_config_name: &str,
1494        options: DynamicConfigEvaluationOptions,
1495    ) -> DynamicConfig {
1496        let user_internal = self.internalize_user(user);
1497        let disable_exposure_logging = options.disable_exposure_logging;
1498        let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1499
1500        if disable_exposure_logging {
1501            log_d!(
1502                TAG,
1503                "Exposure logging is disabled for Dynamic Config {}",
1504                dynamic_config_name
1505            );
1506            self.event_logger
1507                .increment_non_exposure_checks(dynamic_config_name);
1508        } else {
1509            self.event_logger.enqueue(EnqueueConfigExpoOp {
1510                exposure_time: Utc::now().timestamp_millis() as u64,
1511                user: &user_internal,
1512                config: &dynamic_config,
1513                trigger: ExposureTrigger::Auto,
1514            });
1515        }
1516
1517        self.emit_dynamic_config_evaluated(&dynamic_config);
1518
1519        dynamic_config
1520    }
1521
1522    pub fn manually_log_dynamic_config_exposure(
1523        &self,
1524        user: &StatsigUser,
1525        dynamic_config_name: &str,
1526    ) {
1527        let user_internal = self.internalize_user(user);
1528        let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1529        self.event_logger.enqueue(EnqueueConfigExpoOp {
1530            exposure_time: Utc::now().timestamp_millis() as u64,
1531            user: &user_internal,
1532            config: &dynamic_config,
1533            trigger: ExposureTrigger::Manual,
1534        });
1535    }
1536
1537    pub fn get_fields_needed_for_dynamic_config(&self, config_name: &str) -> Vec<String> {
1538        let data = read_lock_or_else!(self.spec_store.data, {
1539            log_error_to_statsig_and_console!(
1540                self.ops_stats.clone(),
1541                TAG,
1542                StatsigErr::LockFailure(
1543                    "Failed to acquire read lock for spec store data".to_string()
1544                )
1545            );
1546            return vec![];
1547        });
1548
1549        let config = data.values.dynamic_configs.get(config_name);
1550        match config {
1551            Some(config) => match &config.spec.fields_used {
1552                Some(fields) => fields.iter().map(|f| f.unperformant_to_string()).collect(),
1553                None => vec![],
1554            },
1555            None => vec![],
1556        }
1557    }
1558}
1559
1560// -------------------------
1561//   Experiment Functions
1562// -------------------------
1563
1564impl Statsig {
1565    pub fn get_experiment(&self, user: &StatsigUser, experiment_name: &str) -> Experiment {
1566        self.get_experiment_with_options(
1567            user,
1568            experiment_name,
1569            ExperimentEvaluationOptions::default(),
1570        )
1571    }
1572
1573    pub fn get_experiment_with_options(
1574        &self,
1575        user: &StatsigUser,
1576        experiment_name: &str,
1577        options: ExperimentEvaluationOptions,
1578    ) -> Experiment {
1579        let user_internal = self.internalize_user(user);
1580        let disable_exposure_logging = options.disable_exposure_logging;
1581        let mut experiment = self.get_experiment_impl(&user_internal, experiment_name);
1582        if let Some(persisted_experiment) = self.persistent_values_manager.as_ref().and_then(|m| {
1583            m.try_apply_sticky_value_to_experiment(&user_internal, &options, &experiment)
1584        }) {
1585            experiment = persisted_experiment
1586        }
1587
1588        if disable_exposure_logging {
1589            log_d!(
1590                TAG,
1591                "Exposure logging is disabled for experiment {}",
1592                experiment_name
1593            );
1594            self.event_logger
1595                .increment_non_exposure_checks(experiment_name);
1596        } else {
1597            self.event_logger.enqueue(EnqueueExperimentExpoOp {
1598                exposure_time: Utc::now().timestamp_millis() as u64,
1599                user: &user_internal,
1600                experiment: &experiment,
1601                trigger: ExposureTrigger::Auto,
1602            });
1603        }
1604
1605        self.emit_experiment_evaluated(&experiment);
1606
1607        experiment
1608    }
1609
1610    pub fn manually_log_experiment_exposure(&self, user: &StatsigUser, experiment_name: &str) {
1611        let user_internal = self.internalize_user(user);
1612        let experiment = self.get_experiment_impl(&user_internal, experiment_name);
1613        self.event_logger.enqueue(EnqueueExperimentExpoOp {
1614            exposure_time: Utc::now().timestamp_millis() as u64,
1615            user: &user_internal,
1616            experiment: &experiment,
1617            trigger: ExposureTrigger::Manual,
1618        });
1619    }
1620
1621    pub fn get_fields_needed_for_experiment(&self, experiment_name: &str) -> Vec<String> {
1622        let data = read_lock_or_else!(self.spec_store.data, {
1623            log_error_to_statsig_and_console!(
1624                self.ops_stats.clone(),
1625                TAG,
1626                StatsigErr::LockFailure(
1627                    "Failed to acquire read lock for spec store data".to_string()
1628                )
1629            );
1630            return vec![];
1631        });
1632
1633        let config = data.values.dynamic_configs.get(experiment_name);
1634        match config {
1635            Some(config) => match &config.spec.fields_used {
1636                Some(fields) => fields.iter().map(|f| f.unperformant_to_string()).collect(),
1637                None => vec![],
1638            },
1639            None => vec![],
1640        }
1641    }
1642
1643    pub fn get_experiment_by_group_name(
1644        &self,
1645        experiment_name: &str,
1646        group_name: &str,
1647    ) -> Experiment {
1648        let data = read_lock_or_else!(self.spec_store.data, {
1649            log_error_to_statsig_and_console!(
1650                self.ops_stats.clone(),
1651                TAG,
1652                StatsigErr::LockFailure(
1653                    "Failed to acquire read lock for spec store data".to_string()
1654                )
1655            );
1656            return make_experiment(
1657                experiment_name,
1658                None,
1659                EvaluationDetails::error("Failed to acquire read lock for spec store data"),
1660            );
1661        });
1662
1663        let Some(exp) = data.values.dynamic_configs.get(experiment_name) else {
1664            return make_experiment(
1665                experiment_name,
1666                None,
1667                EvaluationDetails::unrecognized(&data),
1668            );
1669        };
1670
1671        if let Some(rule) = exp
1672            .spec
1673            .rules
1674            .iter()
1675            .find(|rule| rule.group_name.as_deref() == Some(group_name))
1676        {
1677            let value = rule.return_value.get_json().unwrap_or_default();
1678            let rule_id = String::from(rule.id.as_str());
1679            let id_type = rule.id_type.value.unperformant_to_string();
1680            let group_name = rule.group_name.as_ref().map(|g| g.unperformant_to_string());
1681
1682            return Experiment {
1683                name: experiment_name.to_string(),
1684                value,
1685                rule_id,
1686                id_type,
1687                group_name,
1688                details: EvaluationDetails::recognized_without_eval_result(&data),
1689                is_experiment_active: exp.spec.is_active.unwrap_or(false),
1690                __evaluation: None,
1691            };
1692        }
1693
1694        make_experiment(
1695            experiment_name,
1696            None,
1697            EvaluationDetails::unrecognized(&data),
1698        )
1699    }
1700}
1701
1702// -------------------------
1703//   Layer Functions
1704// -------------------------
1705
1706impl Statsig {
1707    pub fn get_layer(&self, user: &StatsigUser, layer_name: &str) -> Layer {
1708        self.get_layer_with_options(user, layer_name, LayerEvaluationOptions::default())
1709    }
1710
1711    pub fn get_layer_with_options(
1712        &self,
1713        user: &StatsigUser,
1714        layer_name: &str,
1715        options: LayerEvaluationOptions,
1716    ) -> Layer {
1717        let user_internal = self.internalize_user(user);
1718        self.get_layer_impl(user_internal, layer_name, options)
1719    }
1720
1721    pub fn get_prompt(&self, user: &StatsigUser, prompt_name: &str) -> Layer {
1722        self.get_layer(user, prompt_name)
1723    }
1724
1725    pub fn get_prompt_with_options(
1726        &self,
1727        user: &StatsigUser,
1728        prompt_name: &str,
1729        options: LayerEvaluationOptions,
1730    ) -> Layer {
1731        self.get_layer_with_options(user, prompt_name, options)
1732    }
1733
1734    pub fn manually_log_layer_parameter_exposure(
1735        &self,
1736        user: &StatsigUser,
1737        layer_name: &str,
1738        parameter_name: String,
1739    ) {
1740        let user_internal = self.internalize_user(user);
1741        let layer =
1742            self.get_layer_impl(user_internal, layer_name, LayerEvaluationOptions::default());
1743
1744        self.event_logger
1745            .enqueue(EnqueueLayerParamExpoOp::LayerOwned(
1746                Utc::now().timestamp_millis() as u64,
1747                Box::new(layer),
1748                parameter_name,
1749                ExposureTrigger::Manual,
1750            ));
1751    }
1752
1753    pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> {
1754        let data = read_lock_or_else!(self.spec_store.data, {
1755            log_error_to_statsig_and_console!(
1756                self.ops_stats.clone(),
1757                TAG,
1758                StatsigErr::LockFailure(
1759                    "Failed to acquire read lock for spec store data".to_string()
1760                )
1761            );
1762            return vec![];
1763        });
1764
1765        let layer = data.values.layer_configs.get(layer_name);
1766        match layer {
1767            Some(layer) => match &layer.spec.fields_used {
1768                Some(fields) => fields.iter().map(|f| f.unperformant_to_string()).collect(),
1769                None => vec![],
1770            },
1771            None => vec![],
1772        }
1773    }
1774}
1775
1776// -------------------------
1777//   Internal Functions
1778// -------------------------
1779
1780impl Statsig {
1781    pub(crate) fn get_from_statsig_env(&self, key: &str) -> Option<DynamicValue> {
1782        if let Some(env) = &self.statsig_environment {
1783            return env.get(key).cloned();
1784        }
1785
1786        if let Some(fallback_env) = self
1787            .fallback_environment
1788            .try_lock_for(Duration::from_secs(5))
1789        {
1790            if let Some(env) = &*fallback_env {
1791                return env.get(key).cloned();
1792            }
1793        }
1794
1795        None
1796    }
1797
1798    pub(crate) fn get_value_from_global_custom_fields(&self, key: &str) -> Option<&DynamicValue> {
1799        if let Some(env) = &self.options.global_custom_fields {
1800            return env.get(key);
1801        }
1802
1803        None
1804    }
1805
1806    pub(crate) fn use_global_custom_fields<T>(
1807        &self,
1808        f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1809    ) -> T {
1810        f(self.options.global_custom_fields.as_ref())
1811    }
1812
1813    pub(crate) fn use_statsig_env<T>(
1814        &self,
1815        f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1816    ) -> T {
1817        if let Some(env) = &self.statsig_environment {
1818            return f(Some(env));
1819        }
1820
1821        if let Some(fallback_env) = self
1822            .fallback_environment
1823            .try_lock_for(Duration::from_secs(5))
1824        {
1825            if let Some(env) = &*fallback_env {
1826                return f(Some(env));
1827            }
1828        }
1829
1830        f(None)
1831    }
1832}
1833
1834// -------------------------
1835//   Private Functions
1836// -------------------------
1837
1838impl Statsig {
1839    fn evaluate_spec<T>(
1840        &self,
1841        user_internal: &StatsigUserInternal,
1842        spec_name: &str,
1843        make_empty_result: impl FnOnce(EvaluationDetails) -> T,
1844        make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
1845        spec_type: &SpecType,
1846    ) -> T {
1847        let data = read_lock_or_else!(self.spec_store.data, {
1848            log_error_to_statsig_and_console!(
1849                &self.ops_stats,
1850                TAG,
1851                StatsigErr::LockFailure(
1852                    "Failed to acquire read lock for spec store data".to_string()
1853                )
1854            );
1855            return make_empty_result(EvaluationDetails::unrecognized_no_data());
1856        });
1857
1858        let app_id = data.values.app_id.as_ref();
1859        let mut context = EvaluatorContext::new(
1860            user_internal,
1861            &data,
1862            &self.hashing,
1863            app_id,
1864            self.override_adapter.as_ref(),
1865            self.should_user_third_party_parser(),
1866        );
1867
1868        match Evaluator::evaluate_with_details(&mut context, spec_name, spec_type) {
1869            Ok(eval_details) => make_result(context.result, eval_details),
1870            Err(e) => {
1871                log_error_to_statsig_and_console!(
1872                    &self.ops_stats,
1873                    TAG,
1874                    StatsigErr::EvaluationError(e.to_string())
1875                );
1876                make_empty_result(EvaluationDetails::error(&e.to_string()))
1877            }
1878        }
1879    }
1880
1881    fn get_gate_evaluation(
1882        &self,
1883        user_internal: &StatsigUserInternal,
1884        gate_name: &str,
1885    ) -> (EvaluationDetails, Option<GateEvaluation>) {
1886        self.evaluate_spec(
1887            user_internal,
1888            gate_name,
1889            |eval_details| (eval_details, None),
1890            |mut result, eval_details| {
1891                let evaluation = result_to_gate_eval(gate_name, &mut result);
1892                (eval_details, Some(evaluation))
1893            },
1894            &SpecType::Gate,
1895        )
1896    }
1897
1898    fn get_dynamic_config_impl(
1899        &self,
1900        user_internal: &StatsigUserInternal,
1901        config_name: &str,
1902    ) -> DynamicConfig {
1903        self.evaluate_spec(
1904            user_internal,
1905            config_name,
1906            |eval_details| make_dynamic_config(config_name, None, eval_details),
1907            |mut result, eval_details| {
1908                let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
1909                make_dynamic_config(config_name, Some(evaluation), eval_details)
1910            },
1911            &SpecType::DynamicConfig,
1912        )
1913    }
1914
1915    fn get_experiment_impl(
1916        &self,
1917        user_internal: &StatsigUserInternal,
1918        experiment_name: &str,
1919    ) -> Experiment {
1920        self.evaluate_spec(
1921            user_internal,
1922            experiment_name,
1923            |eval_details| make_experiment(experiment_name, None, eval_details),
1924            |mut result, eval_details| {
1925                let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
1926                make_experiment(experiment_name, Some(evaluation), eval_details)
1927            },
1928            &SpecType::Experiment,
1929        )
1930    }
1931
1932    fn get_layer_impl(
1933        &self,
1934        user_internal: StatsigUserInternal,
1935        layer_name: &str,
1936        evaluation_options: LayerEvaluationOptions,
1937    ) -> Layer {
1938        let disable_exposure_logging = evaluation_options.disable_exposure_logging;
1939
1940        if disable_exposure_logging {
1941            self.event_logger.increment_non_exposure_checks(layer_name);
1942        }
1943
1944        let mut layer = self.evaluate_spec(
1945            &user_internal,
1946            layer_name,
1947            |eval_details| {
1948                make_layer(
1949                    user_internal.to_loggable(),
1950                    layer_name,
1951                    None,
1952                    eval_details,
1953                    None,
1954                    disable_exposure_logging,
1955                )
1956            },
1957            |mut result, eval_details| {
1958                let evaluation = result_to_layer_eval(layer_name, &mut result);
1959                let event_logger_ptr = Arc::downgrade(&self.event_logger);
1960
1961                make_layer(
1962                    user_internal.to_loggable(),
1963                    layer_name,
1964                    Some(evaluation),
1965                    eval_details,
1966                    Some(event_logger_ptr),
1967                    disable_exposure_logging,
1968                )
1969            },
1970            &SpecType::Layer,
1971        );
1972
1973        let data = read_lock_or_else!(self.spec_store.data, {
1974            log_error_to_statsig_and_console!(
1975                &self.ops_stats,
1976                TAG,
1977                StatsigErr::LockFailure(
1978                    "Failed to acquire read lock for spec store data".to_string()
1979                )
1980            );
1981            return layer;
1982        });
1983        if let Some(persisted_layer) = self.persistent_values_manager.as_ref().and_then(|p| {
1984            let event_logger_ptr = Arc::downgrade(&self.event_logger);
1985            p.try_apply_sticky_value_to_layer(
1986                &user_internal,
1987                &evaluation_options,
1988                &layer,
1989                Some(event_logger_ptr),
1990                disable_exposure_logging,
1991                &data,
1992            )
1993        }) {
1994            layer = persisted_layer
1995        }
1996
1997        self.emit_layer_evaluated(&layer);
1998
1999        layer
2000    }
2001
2002    fn internalize_user<'s, 'u>(&'s self, user: &'u StatsigUser) -> StatsigUserInternal<'s, 'u> {
2003        StatsigUserInternal::new(user, Some(self))
2004    }
2005
2006    fn set_default_environment_from_server(&self) {
2007        let data = read_lock_or_else!(self.spec_store.data, {
2008            return;
2009        });
2010
2011        if let Some(default_env) = data.values.default_environment.as_ref() {
2012            let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
2013
2014            match self
2015                .fallback_environment
2016                .try_lock_for(Duration::from_secs(5))
2017            {
2018                Some(mut fallback_env) => {
2019                    *fallback_env = Some(env_map);
2020                }
2021                None => {
2022                    log_e!(TAG, "Failed to lock fallback_environment");
2023                }
2024            }
2025        }
2026    }
2027
2028    fn log_init_finish(
2029        &self,
2030        success: bool,
2031        error_message: &Option<String>,
2032        duration: &f64,
2033        specs_info: &SpecsInfo,
2034    ) {
2035        let is_store_populated = specs_info.source != SpecsSource::NoValues;
2036        let source_str = specs_info.source.to_string();
2037
2038        let event = ObservabilityEvent::new_event(
2039            MetricType::Dist,
2040            "initialization".to_string(),
2041            *duration,
2042            Some(HashMap::from([
2043                ("success".to_owned(), success.to_string()),
2044                ("source".to_owned(), source_str.clone()),
2045                ("store_populated".to_owned(), is_store_populated.to_string()),
2046                (
2047                    "spec_source_api".to_owned(),
2048                    specs_info.source_api.clone().unwrap_or_default(),
2049                ),
2050            ])),
2051        );
2052
2053        self.ops_stats.log(event);
2054        self.ops_stats.add_marker(
2055            {
2056                let marker = Marker::new(KeyType::Overall, ActionType::End, None)
2057                    .with_is_success(success)
2058                    .with_config_spec_ready(specs_info.source != SpecsSource::NoValues)
2059                    .with_source(source_str);
2060
2061                if let Some(msg) = &error_message {
2062                    marker.with_message(msg.to_string())
2063                } else {
2064                    marker
2065                }
2066            },
2067            Some(ContextType::Initialize),
2068        );
2069        self.ops_stats
2070            .enqueue_diagnostics_event(None, Some(ContextType::Initialize));
2071    }
2072
2073    fn should_user_third_party_parser(&self) -> bool {
2074        self.options.use_third_party_ua_parser.unwrap_or(false)
2075    }
2076}
2077
2078fn initialize_event_logging_adapter(
2079    sdk_key: &str,
2080    options: &StatsigOptions,
2081) -> Arc<dyn EventLoggingAdapter> {
2082    options
2083        .event_logging_adapter
2084        .clone()
2085        .unwrap_or_else(|| Arc::new(StatsigHttpEventLoggingAdapter::new(sdk_key, Some(options))))
2086}
2087
2088fn initialize_specs_adapter(
2089    sdk_key: &str,
2090    options: &StatsigOptions,
2091    hashing: &HashUtil,
2092) -> SpecsAdapterHousing {
2093    if let Some(adapter) = options.specs_adapter.clone() {
2094        log_d!(TAG, "Using provided SpecsAdapter: {}", sdk_key);
2095        return SpecsAdapterHousing {
2096            inner: adapter,
2097            as_default_adapter: None,
2098        };
2099    }
2100
2101    if let Some(adapter_config) = options.spec_adapters_config.clone() {
2102        let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
2103            sdk_key,
2104            adapter_config,
2105            options,
2106            hashing,
2107        ));
2108
2109        return SpecsAdapterHousing {
2110            inner: adapter,
2111            as_default_adapter: None,
2112        };
2113    }
2114
2115    if let Some(data_adapter) = options.data_store.clone() {
2116        let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
2117            sdk_key,
2118            data_adapter,
2119            options,
2120            hashing,
2121        ));
2122
2123        return SpecsAdapterHousing {
2124            inner: adapter,
2125            as_default_adapter: None,
2126        };
2127    }
2128
2129    let adapter = Arc::new(StatsigHttpSpecsAdapter::new(sdk_key, Some(options), None));
2130
2131    SpecsAdapterHousing {
2132        inner: adapter.clone(),
2133        as_default_adapter: Some(adapter),
2134    }
2135}
2136
2137fn initialize_id_lists_adapter(sdk_key: &str, options: &StatsigOptions) -> IdListsAdapterHousing {
2138    if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
2139        return IdListsAdapterHousing {
2140            inner: Some(id_lists_adapter),
2141            as_default_adapter: None,
2142        };
2143    }
2144
2145    if options.enable_id_lists.unwrap_or(false) {
2146        let adapter = Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options));
2147
2148        return IdListsAdapterHousing {
2149            inner: Some(adapter.clone()),
2150            as_default_adapter: Some(adapter),
2151        };
2152    }
2153
2154    IdListsAdapterHousing {
2155        inner: None,
2156        as_default_adapter: None,
2157    }
2158}
2159
2160struct IdListsAdapterHousing {
2161    inner: Option<Arc<dyn IdListsAdapter>>,
2162    as_default_adapter: Option<Arc<StatsigHttpIdListsAdapter>>,
2163}
2164
2165struct SpecsAdapterHousing {
2166    inner: Arc<dyn SpecsAdapter>,
2167    as_default_adapter: Option<Arc<StatsigHttpSpecsAdapter>>,
2168}
2169
2170fn setup_ops_stats(
2171    sdk_key: &str,
2172    statsig_runtime: Arc<StatsigRuntime>,
2173    error_observer: &Arc<dyn OpsStatsEventObserver>,
2174    diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
2175    external_observer: &Option<Weak<dyn ObservabilityClient>>,
2176) -> Arc<OpsStatsForInstance> {
2177    let ops_stat = OPS_STATS.get_for_instance(sdk_key);
2178    ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
2179    ops_stat.subscribe(
2180        statsig_runtime.clone(),
2181        Arc::downgrade(diagnostics_observer),
2182    );
2183
2184    if let Some(ob_client) = external_observer {
2185        if let Some(client) = ob_client.upgrade() {
2186            client.init();
2187            let as_observer = client.to_ops_stats_event_observer();
2188            ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
2189        }
2190    }
2191
2192    ops_stat
2193}