Skip to main content

statsig_rust/
statsig.rs

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