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        if partial_raw.disable_exposure {
1873            self.event_logger
1874                .increment_non_exposure_checks(&partial_raw.name);
1875            return;
1876        }
1877
1878        let interned_parameter_name = InternedString::from_string(param_name);
1879
1880        self.event_logger
1881            .enqueue(EnqueueExposureOp::layer_param_exposure_from_partial_raw(
1882                interned_parameter_name,
1883                ExposureTrigger::Auto,
1884                partial_raw,
1885            ));
1886    }
1887}
1888
1889// ------------------------------------------------------------------------------- [ Internal ]
1890
1891impl Statsig {
1892    pub(crate) fn get_from_statsig_env(&self, key: &str) -> Option<DynamicValue> {
1893        if let Some(env) = &self.statsig_environment {
1894            return env.get(key).cloned();
1895        }
1896
1897        if let Some(fallback_env) = self
1898            .fallback_environment
1899            .try_lock_for(Duration::from_secs(5))
1900        {
1901            if let Some(env) = &*fallback_env {
1902                return env.get(key).cloned();
1903            }
1904        }
1905
1906        None
1907    }
1908
1909    pub(crate) fn get_value_from_global_custom_fields(&self, key: &str) -> Option<&DynamicValue> {
1910        if let Some(env) = &self.options.global_custom_fields {
1911            return env.get(key);
1912        }
1913
1914        None
1915    }
1916
1917    pub(crate) fn use_global_custom_fields<T>(
1918        &self,
1919        f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1920    ) -> T {
1921        f(self.options.global_custom_fields.as_ref())
1922    }
1923
1924    pub(crate) fn use_statsig_env<T>(
1925        &self,
1926        f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1927    ) -> T {
1928        if let Some(env) = &self.statsig_environment {
1929            return f(Some(env));
1930        }
1931
1932        if let Some(fallback_env) = self
1933            .fallback_environment
1934            .try_lock_for(Duration::from_secs(5))
1935        {
1936            if let Some(env) = &*fallback_env {
1937                return f(Some(env));
1938            }
1939        }
1940
1941        f(None)
1942    }
1943}
1944
1945// ------------------------------------------------------------------------------- [ Private ]
1946
1947impl Statsig {
1948    async fn start_background_tasks(
1949        statsig_runtime: Arc<StatsigRuntime>,
1950        id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
1951        specs_adapter: Arc<dyn SpecsAdapter>,
1952        ops_stats: Arc<OpsStatsForInstance>,
1953        bg_tasks_started: Arc<AtomicBool>,
1954    ) -> bool {
1955        if bg_tasks_started.load(Ordering::SeqCst) {
1956            return true;
1957        }
1958
1959        let mut success = true;
1960
1961        if let Some(adapter) = &id_lists_adapter {
1962            if let Err(e) = adapter
1963                .clone()
1964                .schedule_background_sync(&statsig_runtime)
1965                .await
1966            {
1967                success = false;
1968                log_w!(TAG, "Failed to schedule idlist background job {}", e);
1969            }
1970        }
1971
1972        if let Err(e) = specs_adapter
1973            .clone()
1974            .schedule_background_sync(&statsig_runtime)
1975            .await
1976        {
1977            success = false;
1978            log_error_to_statsig_and_console!(
1979                ops_stats,
1980                TAG,
1981                StatsigErr::SpecsAdapterSkipPoll(format!(
1982                    "Failed to schedule specs adapter background job: {e}"
1983                ))
1984            );
1985        }
1986
1987        bg_tasks_started.store(true, Ordering::SeqCst);
1988
1989        success
1990    }
1991
1992    async fn apply_timeout_to_init(
1993        &self,
1994        timeout_ms: u64,
1995    ) -> Result<InitializeDetails, StatsigErr> {
1996        let timeout = Duration::from_millis(timeout_ms);
1997
1998        let init_future = self.initialize_impl_with_details();
1999        let timeout_future = sleep(timeout);
2000
2001        let statsig_runtime = self.statsig_runtime.clone();
2002        let id_lists_adapter = self.id_lists_adapter.inner.clone();
2003        let specs_adapter = self.specs_adapter.inner.clone();
2004        let ops_stats = self.ops_stats.clone();
2005        let background_tasks_started = self.background_tasks_started.clone();
2006        // Create another clone specifically for the closure
2007        let statsig_runtime_for_closure = statsig_runtime.clone();
2008
2009        tokio::select! {
2010            result = init_future => {
2011                result
2012            },
2013            _ = timeout_future => {
2014                statsig_runtime.spawn(
2015                    "start_background_tasks",
2016                    |_shutdown_notify| async move {
2017                        Self::start_background_tasks(
2018                            statsig_runtime_for_closure,
2019                            id_lists_adapter,
2020                            specs_adapter,
2021                            ops_stats,
2022                            background_tasks_started,
2023                        ).await;
2024                    }
2025                )?;
2026                Ok(InitializeDetails::from_timeout_failure(timeout_ms))
2027            },
2028        }
2029    }
2030
2031    async fn initialize_impl_with_details(&self) -> Result<InitializeDetails, StatsigErr> {
2032        let start_time = Instant::now();
2033        self.spec_store.set_source(SpecsSource::Loading);
2034        self.specs_adapter.inner.initialize(self.spec_store.clone());
2035        let use_third_party_ua_parser = self.should_user_third_party_parser();
2036
2037        let mut error_message = None;
2038        let mut id_list_ready = None;
2039
2040        let init_country_lookup = if !self.options.disable_country_lookup.unwrap_or_default() {
2041            Some(self.statsig_runtime.spawn(INIT_IP_TAG, |_| async {
2042                CountryLookup::load_country_lookup();
2043            }))
2044        } else {
2045            None
2046        };
2047
2048        let init_ua = if use_third_party_ua_parser {
2049            Some(self.statsig_runtime.spawn(INIT_UA_TAG, |_| async {
2050                UserAgentParser::load_parser();
2051            }))
2052        } else {
2053            None
2054        };
2055
2056        let init_res = match self
2057            .specs_adapter
2058            .inner
2059            .clone()
2060            .start(&self.statsig_runtime)
2061            .await
2062        {
2063            Ok(()) => Ok(()),
2064            Err(e) => {
2065                self.spec_store.set_source(SpecsSource::NoValues);
2066                error_message = Some(format!("Failed to start specs adapter: {e}"));
2067                Err(e)
2068            }
2069        };
2070
2071        if let Some(adapter) = &self.id_lists_adapter.inner {
2072            match adapter
2073                .clone()
2074                .start(&self.statsig_runtime, self.spec_store.clone())
2075                .await
2076            {
2077                Ok(()) => {
2078                    id_list_ready = Some(true);
2079                }
2080                Err(e) => {
2081                    id_list_ready = Some(false);
2082                    error_message.get_or_insert_with(|| format!("Failed to sync ID lists: {e}"));
2083                }
2084            }
2085        }
2086
2087        if let Err(e) = self
2088            .event_logging_adapter
2089            .clone()
2090            .start(&self.statsig_runtime)
2091            .await
2092        {
2093            log_error_to_statsig_and_console!(
2094                self.ops_stats.clone(),
2095                TAG,
2096                StatsigErr::UnstartedAdapter(format!("Failed to start event logging adapter: {e}"))
2097            );
2098        }
2099
2100        let spec_info = self.spec_store.get_current_specs_info();
2101        let duration = start_time.elapsed().as_millis() as u64;
2102
2103        self.set_default_environment_from_server();
2104
2105        if self.options.wait_for_country_lookup_init.unwrap_or(false) {
2106            match init_country_lookup {
2107                Some(Ok(task_id)) => {
2108                    let _ = self
2109                        .statsig_runtime
2110                        .await_join_handle(INIT_IP_TAG, &task_id)
2111                        .await;
2112                }
2113                Some(Err(e)) => {
2114                    log_error_to_statsig_and_console!(
2115                        self.ops_stats.clone(),
2116                        TAG,
2117                        StatsigErr::UnstartedAdapter(format!(
2118                            "Failed to spawn country lookup task: {e}"
2119                        ))
2120                    );
2121                }
2122                _ => {}
2123            }
2124        }
2125        if self.options.wait_for_user_agent_init.unwrap_or(false) {
2126            match init_ua {
2127                Some(Ok(task_id)) => {
2128                    let _ = self
2129                        .statsig_runtime
2130                        .await_join_handle(INIT_UA_TAG, &task_id)
2131                        .await;
2132                }
2133                Some(Err(e)) => {
2134                    log_error_to_statsig_and_console!(
2135                        self.ops_stats.clone(),
2136                        TAG,
2137                        StatsigErr::UnstartedAdapter(format!(
2138                            "Failed to spawn user agent parser task: {e}"
2139                        ))
2140                    );
2141                }
2142                _ => {}
2143            }
2144        }
2145
2146        let error = init_res.clone().err();
2147
2148        let success = Self::start_background_tasks(
2149            self.statsig_runtime.clone(),
2150            self.id_lists_adapter.inner.clone(),
2151            self.specs_adapter.inner.clone(),
2152            self.ops_stats.clone(),
2153            self.background_tasks_started.clone(),
2154        )
2155        .await;
2156
2157        Ok(InitializeDetails::new(
2158            success,
2159            duration,
2160            spec_info,
2161            id_list_ready,
2162            error,
2163        ))
2164    }
2165
2166    fn log_init_details(&self, init_details: &Result<InitializeDetails, StatsigErr>) {
2167        match init_details {
2168            Ok(details) => {
2169                self.log_init_finish(
2170                    details.init_success,
2171                    &None,
2172                    &details.duration_ms,
2173                    &self.spec_store.get_current_specs_info(),
2174                );
2175                if let Some(failure) = &details.failure_details {
2176                    log_error_to_statsig_and_console!(
2177                        self.ops_stats,
2178                        TAG,
2179                        StatsigErr::InitializationError(failure.reason.clone())
2180                    );
2181                }
2182            }
2183            Err(err) => {
2184                // we store errors on init details so we should never return error and thus do not need to log
2185                log_w!(TAG, "Initialization error: {:?}", err);
2186            }
2187        }
2188    }
2189
2190    fn create_standard_eval_context<'a>(
2191        &'a self,
2192        user_internal: &'a StatsigUserInternal,
2193        data: &'a SpecStoreData,
2194        app_id: Option<&'a DynamicValue>,
2195        override_adapter: Option<&'a Arc<dyn OverrideAdapter>>,
2196        disable_exposure_logging: bool,
2197    ) -> EvaluatorContext<'a> {
2198        EvaluatorContext::new(
2199            user_internal,
2200            &data.values,
2201            IdListResolution::MapLookup(&data.id_lists),
2202            &self.hashing,
2203            app_id,
2204            override_adapter,
2205            self.should_user_third_party_parser(),
2206            Some(self),
2207            disable_exposure_logging,
2208        )
2209    }
2210
2211    fn create_gcir_eval_context<'a>(
2212        &'a self,
2213        user_internal: &'a StatsigUserInternal,
2214        data: &'a SpecStoreData,
2215        options: &'a ClientInitResponseOptions,
2216    ) -> EvaluatorContext<'a> {
2217        let app_id = select_app_id_for_gcir(options, &data.values, &self.hashing);
2218        let override_adapter = match options.include_local_overrides {
2219            Some(true) => self.override_adapter.as_ref(),
2220            _ => None,
2221        };
2222
2223        EvaluatorContext::new(
2224            user_internal,
2225            &data.values,
2226            IdListResolution::MapLookup(&data.id_lists),
2227            &self.hashing,
2228            app_id,
2229            override_adapter,
2230            self.should_user_third_party_parser(),
2231            None,
2232            true,
2233        )
2234    }
2235
2236    fn evaluate_spec_raw(
2237        &self,
2238        user_internal: &StatsigUserInternal,
2239        spec_name: &str,
2240        spec_type: &SpecType,
2241        disable_exposure_logging: Option<bool>,
2242    ) -> (EvaluationDetails, Option<EvaluatorResult>) {
2243        let data = read_lock_or_else!(self.spec_store.data, {
2244            log_error_to_statsig_and_console!(
2245                &self.ops_stats,
2246                TAG,
2247                StatsigErr::LockFailure(
2248                    "Failed to acquire read lock for spec store data".to_string()
2249                )
2250            );
2251            return (EvaluationDetails::unrecognized_no_data(), None);
2252        });
2253
2254        let mut context = self.create_standard_eval_context(
2255            user_internal,
2256            &data,
2257            data.values.app_id.as_ref(),
2258            self.override_adapter.as_ref(),
2259            disable_exposure_logging.unwrap_or(false),
2260        );
2261
2262        match Self::evaluate_with_details(&mut context, &data, spec_name, spec_type) {
2263            Ok(eval_details) => (eval_details, Some(context.result)),
2264            Err(e) => {
2265                log_error_to_statsig_and_console!(
2266                    &self.ops_stats,
2267                    TAG,
2268                    StatsigErr::EvaluationError(e.to_string())
2269                );
2270                (EvaluationDetails::error(&e.to_string()), None)
2271            }
2272        }
2273    }
2274
2275    #[allow(clippy::too_many_arguments)]
2276    fn evaluate_spec<T>(
2277        &self,
2278        user_internal: &StatsigUserInternal,
2279        spec_name: &str,
2280        make_empty_result: impl FnOnce(EvaluationDetails) -> T,
2281        make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
2282        spec_type: &SpecType,
2283        disable_exposure_logging: Option<bool>,
2284    ) -> T {
2285        let data = read_lock_or_else!(self.spec_store.data, {
2286            log_error_to_statsig_and_console!(
2287                &self.ops_stats,
2288                TAG,
2289                StatsigErr::LockFailure(
2290                    "Failed to acquire read lock for spec store data".to_string()
2291                )
2292            );
2293            return make_empty_result(EvaluationDetails::unrecognized_no_data());
2294        });
2295
2296        let mut context = self.create_standard_eval_context(
2297            user_internal,
2298            &data,
2299            data.values.app_id.as_ref(),
2300            self.override_adapter.as_ref(),
2301            disable_exposure_logging.unwrap_or(false),
2302        );
2303
2304        match Self::evaluate_with_details(&mut context, &data, spec_name, spec_type) {
2305            Ok(eval_details) => make_result(context.result, eval_details),
2306            Err(e) => {
2307                log_error_to_statsig_and_console!(
2308                    &self.ops_stats,
2309                    TAG,
2310                    StatsigErr::EvaluationError(e.to_string())
2311                );
2312                make_empty_result(EvaluationDetails::error(&e.to_string()))
2313            }
2314        }
2315    }
2316
2317    fn evaluate_with_details(
2318        ctx: &mut EvaluatorContext,
2319        spec_store_data: &SpecStoreData,
2320        spec_name: &str,
2321        spec_type: &SpecType,
2322    ) -> Result<EvaluationDetails, StatsigErr> {
2323        let recognition = Evaluator::evaluate(ctx, spec_name, spec_type)?;
2324
2325        if recognition == Recognition::Unrecognized {
2326            return Ok(EvaluationDetails::unrecognized(
2327                &spec_store_data.source,
2328                spec_store_data.values.time,
2329                spec_store_data.time_received_at,
2330            ));
2331        }
2332
2333        if let Some(reason) = ctx.result.override_reason {
2334            return Ok(EvaluationDetails::recognized_but_overridden(
2335                spec_store_data.values.time,
2336                spec_store_data.time_received_at,
2337                reason,
2338                ctx.result.version,
2339            ));
2340        }
2341
2342        Ok(EvaluationDetails::recognized(
2343            &spec_store_data.source,
2344            spec_store_data.values.time,
2345            spec_store_data.time_received_at,
2346            &ctx.result,
2347        ))
2348    }
2349
2350    fn stringify_gcir_response<T: Serialize>(
2351        &self,
2352        input: Result<T, StatsigErr>,
2353        fallback: impl FnOnce() -> T,
2354    ) -> String {
2355        match input {
2356            Ok(value) => serde_json::to_string(&value).unwrap_or_default(),
2357            Err(e) => {
2358                log_error_to_statsig_and_console!(
2359                    &self.ops_stats,
2360                    TAG,
2361                    StatsigErr::GCIRError(e.to_string())
2362                );
2363                serde_json::to_string(&fallback()).unwrap_or_default()
2364            }
2365        }
2366    }
2367
2368    fn get_gate_evaluation(
2369        &self,
2370        user_internal: &StatsigUserInternal,
2371        gate_name: &str,
2372        disable_exposure_logging: Option<bool>,
2373    ) -> (EvaluationDetails, Option<GateEvaluation>) {
2374        self.evaluate_spec(
2375            user_internal,
2376            gate_name,
2377            |eval_details| (eval_details, None),
2378            |mut result, eval_details| {
2379                let evaluation = result_to_gate_eval(gate_name, &mut result);
2380                (eval_details, Some(evaluation))
2381            },
2382            &SpecType::Gate,
2383            disable_exposure_logging,
2384        )
2385    }
2386
2387    fn get_dynamic_config_impl(
2388        &self,
2389        user_internal: &StatsigUserInternal,
2390        config_name: &str,
2391        disable_exposure_logging: Option<bool>,
2392    ) -> DynamicConfig {
2393        self.evaluate_spec(
2394            user_internal,
2395            config_name,
2396            |eval_details| make_dynamic_config(config_name, None, eval_details),
2397            |mut result, eval_details| {
2398                let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
2399                make_dynamic_config(config_name, Some(evaluation), eval_details)
2400            },
2401            &SpecType::DynamicConfig,
2402            disable_exposure_logging,
2403        )
2404    }
2405
2406    fn get_experiment_impl(
2407        &self,
2408        user_internal: &StatsigUserInternal,
2409        experiment_name: &str,
2410        disable_exposure_logging: Option<bool>,
2411    ) -> Experiment {
2412        self.evaluate_spec(
2413            user_internal,
2414            experiment_name,
2415            |eval_details| make_experiment(experiment_name, None, eval_details),
2416            |mut result, eval_details| {
2417                let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
2418                make_experiment(experiment_name, Some(evaluation), eval_details)
2419            },
2420            &SpecType::Experiment,
2421            disable_exposure_logging,
2422        )
2423    }
2424
2425    fn get_layer_impl(
2426        &self,
2427        user_internal: StatsigUserInternal,
2428        layer_name: &str,
2429        evaluation_options: LayerEvaluationOptions,
2430    ) -> Layer {
2431        let disable_exposure_logging = evaluation_options.disable_exposure_logging;
2432
2433        if disable_exposure_logging {
2434            self.event_logger.increment_non_exposure_checks(layer_name);
2435        }
2436
2437        let mut layer = self.evaluate_spec(
2438            &user_internal,
2439            layer_name,
2440            |eval_details| {
2441                make_layer(
2442                    user_internal.to_loggable(),
2443                    layer_name,
2444                    None,
2445                    eval_details,
2446                    None,
2447                    disable_exposure_logging,
2448                )
2449            },
2450            |mut result, eval_details| {
2451                let evaluation = result_to_layer_eval(layer_name, &mut result);
2452                let event_logger_ptr = Arc::downgrade(&self.event_logger);
2453
2454                make_layer(
2455                    user_internal.to_loggable(),
2456                    layer_name,
2457                    Some(evaluation),
2458                    eval_details,
2459                    Some(event_logger_ptr),
2460                    disable_exposure_logging,
2461                )
2462            },
2463            &SpecType::Layer,
2464            Some(evaluation_options.disable_exposure_logging),
2465        );
2466
2467        layer = PersistentValuesManager::try_apply_sticky_value_to_layer(
2468            &self.persistent_values_manager,
2469            &user_internal,
2470            &evaluation_options,
2471            &self.spec_store,
2472            &self.ops_stats,
2473            layer,
2474        );
2475
2476        self.emit_layer_evaluated(&layer);
2477
2478        layer
2479    }
2480
2481    fn internalize_user<'s, 'u>(&'s self, user: &'u StatsigUser) -> StatsigUserInternal<'s, 'u> {
2482        StatsigUserInternal::new(user, Some(self))
2483    }
2484
2485    fn set_default_environment_from_server(&self) {
2486        let data = read_lock_or_else!(self.spec_store.data, {
2487            return;
2488        });
2489
2490        if let Some(default_env) = data.values.default_environment.as_ref() {
2491            let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
2492
2493            match self
2494                .fallback_environment
2495                .try_lock_for(Duration::from_secs(5))
2496            {
2497                Some(mut fallback_env) => {
2498                    *fallback_env = Some(env_map);
2499                }
2500                None => {
2501                    log_e!(TAG, "Failed to lock fallback_environment");
2502                }
2503            }
2504        }
2505    }
2506
2507    fn log_init_finish(
2508        &self,
2509        success: bool,
2510        error_message: &Option<String>,
2511        duration_ms: &u64,
2512        specs_info: &SpecsInfo,
2513    ) {
2514        let is_store_populated = specs_info.source != SpecsSource::NoValues;
2515        let source_str = specs_info.source.to_string();
2516        let metadata = StatsigMetadata::get_metadata();
2517
2518        let event = ObservabilityEvent::new_event(
2519            MetricType::Dist,
2520            "initialization".to_string(),
2521            *duration_ms as f64,
2522            Some(HashMap::from([
2523                ("init_success".to_owned(), success.to_string()),
2524                ("source".to_owned(), source_str.clone()),
2525                ("store_populated".to_owned(), is_store_populated.to_string()),
2526                (
2527                    "sdk_key".to_owned(),
2528                    get_loggable_sdk_key(self.sdk_key.as_str()),
2529                ),
2530                ("sdk_type".to_owned(), metadata.sdk_type),
2531                ("sdk_version".to_owned(), metadata.sdk_version),
2532                (
2533                    "init_source_api".to_owned(),
2534                    specs_info.source_api.clone().unwrap_or_default(),
2535                ),
2536            ])),
2537        );
2538
2539        self.ops_stats.log(event);
2540        self.ops_stats.add_marker(
2541            {
2542                let marker = Marker::new(KeyType::Overall, ActionType::End, None)
2543                    .with_is_success(success)
2544                    .with_config_spec_ready(specs_info.source != SpecsSource::NoValues)
2545                    .with_source(source_str);
2546
2547                if let Some(msg) = &error_message {
2548                    marker.with_message(msg.to_string())
2549                } else {
2550                    marker
2551                }
2552            },
2553            Some(ContextType::Initialize),
2554        );
2555        self.ops_stats
2556            .enqueue_diagnostics_event(None, Some(ContextType::Initialize));
2557    }
2558
2559    fn should_user_third_party_parser(&self) -> bool {
2560        self.options.use_third_party_ua_parser.unwrap_or(false)
2561    }
2562}
2563
2564fn initialize_event_logging_adapter(
2565    sdk_key: &str,
2566    options: &StatsigOptions,
2567) -> Arc<dyn EventLoggingAdapter> {
2568    options
2569        .event_logging_adapter
2570        .clone()
2571        .unwrap_or_else(|| Arc::new(StatsigHttpEventLoggingAdapter::new(sdk_key, Some(options))))
2572}
2573
2574fn initialize_specs_adapter(
2575    sdk_key: &str,
2576    data_store_key: &str,
2577    options: &StatsigOptions,
2578) -> SpecsAdapterHousing {
2579    if let Some(adapter) = options.specs_adapter.clone() {
2580        log_d!(TAG, "Using provided SpecsAdapter: {}", sdk_key);
2581        return SpecsAdapterHousing {
2582            inner: adapter,
2583            as_default_adapter: None,
2584        };
2585    }
2586
2587    if let Some(adapter_config) = options.spec_adapters_config.clone() {
2588        let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
2589            sdk_key,
2590            data_store_key,
2591            adapter_config,
2592            options,
2593        ));
2594
2595        return SpecsAdapterHousing {
2596            inner: adapter,
2597            as_default_adapter: None,
2598        };
2599    }
2600
2601    if let Some(data_store) = options.data_store.clone() {
2602        let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
2603            sdk_key,
2604            data_store_key,
2605            data_store,
2606            options,
2607        ));
2608
2609        return SpecsAdapterHousing {
2610            inner: adapter,
2611            as_default_adapter: None,
2612        };
2613    }
2614
2615    let adapter = Arc::new(StatsigHttpSpecsAdapter::new(sdk_key, Some(options), None));
2616
2617    SpecsAdapterHousing {
2618        inner: adapter.clone(),
2619        as_default_adapter: Some(adapter),
2620    }
2621}
2622
2623fn initialize_id_lists_adapter(sdk_key: &str, options: &StatsigOptions) -> IdListsAdapterHousing {
2624    if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
2625        return IdListsAdapterHousing {
2626            inner: Some(id_lists_adapter),
2627            as_default_adapter: None,
2628        };
2629    }
2630
2631    if options.enable_id_lists.unwrap_or(false) {
2632        let adapter = Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options));
2633
2634        return IdListsAdapterHousing {
2635            inner: Some(adapter.clone()),
2636            as_default_adapter: Some(adapter),
2637        };
2638    }
2639
2640    IdListsAdapterHousing {
2641        inner: None,
2642        as_default_adapter: None,
2643    }
2644}
2645
2646struct IdListsAdapterHousing {
2647    inner: Option<Arc<dyn IdListsAdapter>>,
2648    as_default_adapter: Option<Arc<StatsigHttpIdListsAdapter>>,
2649}
2650
2651struct SpecsAdapterHousing {
2652    inner: Arc<dyn SpecsAdapter>,
2653    as_default_adapter: Option<Arc<StatsigHttpSpecsAdapter>>,
2654}
2655
2656fn setup_ops_stats(
2657    sdk_key: &str,
2658    statsig_runtime: Arc<StatsigRuntime>,
2659    error_observer: &Arc<dyn OpsStatsEventObserver>,
2660    diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
2661    console_capture_observer: &Arc<dyn OpsStatsEventObserver>,
2662    external_observer: &Option<Weak<dyn ObservabilityClient>>,
2663) -> Arc<OpsStatsForInstance> {
2664    let ops_stat = OPS_STATS.get_for_instance(sdk_key);
2665    ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
2666    ops_stat.subscribe(
2667        statsig_runtime.clone(),
2668        Arc::downgrade(diagnostics_observer),
2669    );
2670    ops_stat.subscribe(
2671        statsig_runtime.clone(),
2672        Arc::downgrade(console_capture_observer),
2673    );
2674    if let Some(ob_client) = external_observer {
2675        if let Some(client) = ob_client.upgrade() {
2676            client.init();
2677            let as_observer = client.to_ops_stats_event_observer();
2678            ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
2679        }
2680    }
2681
2682    ops_stat
2683}