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