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