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