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