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, SpecType};
7use crate::evaluation::evaluator_context::EvaluatorContext;
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::ua_parser::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;
20use crate::event_logging_adapter::EventLoggingAdapter;
21use crate::event_logging_adapter::StatsigHttpEventLoggingAdapter;
22use crate::gcir::gcir_formatter::GCIRFormatter;
23use crate::hashing::HashUtil;
24use crate::initialize_response::InitializeResponse;
25use crate::observability::diagnostics_observer::DiagnosticsObserver;
26use crate::observability::observability_client_adapter::{MetricType, ObservabilityEvent};
27use crate::observability::ops_stats::{OpsStatsForInstance, OPS_STATS};
28use crate::observability::sdk_errors_observer::{ErrorBoundaryEvent, SDKErrorsObserver};
29use crate::output_logger::initialize_simple_output_logger;
30use crate::persistent_storage::persistent_values_manager::PersistentValuesManager;
31use crate::sdk_diagnostics::diagnostics::{ContextType, Diagnostics};
32use crate::sdk_diagnostics::marker::{ActionType, KeyType, Marker};
33use crate::spec_store::SpecStore;
34use crate::specs_adapter::{StatsigCustomizedSpecsAdapter, StatsigHttpSpecsAdapter};
35use crate::statsig_err::StatsigErr;
36use crate::statsig_metadata::StatsigMetadata;
37use crate::statsig_options::StatsigOptions;
38use crate::statsig_runtime::StatsigRuntime;
39use crate::statsig_type_factories::{
40 make_dynamic_config, make_experiment, make_feature_gate, make_layer,
41};
42use crate::statsig_types::{DynamicConfig, Experiment, FeatureGate, Layer, ParameterStore};
43use crate::user::StatsigUserInternal;
44use crate::{
45 dyn_value, log_d, log_e, log_w, read_lock_or_else, ClientInitResponseOptions,
46 GCIRResponseFormat, IdListsAdapter, ObservabilityClient, OpsStatsEventObserver,
47 OverrideAdapter, SpecsAdapter, SpecsInfo, SpecsSource, SpecsUpdateListener,
48 StatsigHttpIdListsAdapter, StatsigLocalOverrideAdapter, StatsigUser,
49};
50use crate::{
51 log_error_to_statsig_and_console,
52 statsig_core_api_options::{
53 DynamicConfigEvaluationOptions, ExperimentEvaluationOptions, FeatureGateEvaluationOptions,
54 LayerEvaluationOptions, ParameterStoreEvaluationOptions,
55 },
56};
57use serde::de::DeserializeOwned;
58use serde_json::json;
59use serde_json::Value;
60use std::borrow::Cow;
61use std::collections::HashMap;
62use std::sync::atomic::{AtomicBool, Ordering};
63use std::sync::Mutex;
64use std::sync::{Arc, Weak};
65use std::time::{Duration, Instant};
66use tokio::time::sleep;
67use tokio::try_join;
68
69const TAG: &str = stringify!(Statsig);
70const ERROR_SDK_KEY: &str = "__STATSIG_ERROR_SDK_KEY__";
71const INIT_IP_TAG: &str = "INIT_COUNTRY_LOOKUP";
72const INIT_UA_TAG: &str = "INIT_UA";
73
74lazy_static::lazy_static! {
75 static ref SHARED_INSTANCE: Mutex<Option<Arc<Statsig>>> = Mutex::new(None);
76}
77
78pub struct Statsig {
79 pub statsig_runtime: Arc<StatsigRuntime>,
80
81 sdk_key: String,
82 options: Arc<StatsigOptions>,
83 event_logger: Arc<EventLogger>,
84 specs_adapter: Arc<dyn SpecsAdapter>,
85 event_logging_adapter: Arc<dyn EventLoggingAdapter>,
86 id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
87 override_adapter: Option<Arc<dyn OverrideAdapter>>,
88 spec_store: Arc<SpecStore>,
89 hashing: Arc<HashUtil>,
90 gcir_formatter: Arc<GCIRFormatter>,
91 statsig_environment: Option<HashMap<String, DynamicValue>>,
92 fallback_environment: Mutex<Option<HashMap<String, DynamicValue>>>,
93 ops_stats: Arc<OpsStatsForInstance>,
94 error_observer: Arc<dyn OpsStatsEventObserver>,
95 diagnostics_observer: Arc<dyn OpsStatsEventObserver>,
96 background_tasks_started: Arc<AtomicBool>,
97 persistent_values_manager: Option<Arc<PersistentValuesManager>>,
98 initialize_details: Mutex<InitializeDetails>,
99}
100
101pub struct StatsigContext {
102 pub sdk_key: String,
103 pub options: Arc<StatsigOptions>,
104 pub local_override_adapter: Option<Arc<dyn OverrideAdapter>>,
105 pub error_observer: Arc<dyn OpsStatsEventObserver>,
106 pub diagnostics_observer: Arc<dyn OpsStatsEventObserver>,
107 pub spec_store: Arc<SpecStore>,
108}
109
110#[derive(Debug, Clone)]
111pub struct FailureDetails {
112 pub reason: String,
113 pub error: Option<StatsigErr>,
114}
115
116#[derive(Debug, Clone)]
117pub struct InitializeDetails {
118 pub duration: f64,
119 pub init_success: bool,
120 pub is_config_spec_ready: bool,
121 pub is_id_list_ready: Option<bool>,
122 pub source: SpecsSource,
123 pub failure_details: Option<FailureDetails>,
124}
125
126impl Default for InitializeDetails {
127 fn default() -> Self {
128 InitializeDetails {
129 duration: 0.0,
130 init_success: false,
131 is_config_spec_ready: false,
132 is_id_list_ready: None,
133 source: SpecsSource::Uninitialized,
134 failure_details: None,
135 }
136 }
137}
138
139impl InitializeDetails {
140 pub fn from_error(reason: &str, error: Option<StatsigErr>) -> Self {
141 InitializeDetails {
142 duration: 0.0,
143 init_success: false,
144 is_config_spec_ready: false,
145 is_id_list_ready: None,
146 source: SpecsSource::Uninitialized,
147 failure_details: Some(FailureDetails {
148 reason: reason.to_string(),
149 error,
150 }),
151 }
152 }
153}
154
155impl Statsig {
156 pub fn new(sdk_key: &str, options: Option<Arc<StatsigOptions>>) -> Self {
157 let statsig_runtime = StatsigRuntime::get_runtime();
158 let options = options.unwrap_or_default();
159
160 let hashing = Arc::new(HashUtil::new());
161
162 let specs_adapter = initialize_specs_adapter(sdk_key, &options, &hashing);
163 let id_lists_adapter = initialize_id_lists_adapter(sdk_key, &options);
164 let event_logging_adapter = initialize_event_logging_adapter(sdk_key, &options);
165 let override_adapter = match options.override_adapter.as_ref() {
166 Some(adapter) => Some(Arc::clone(adapter)),
167 None => Some(Arc::new(StatsigLocalOverrideAdapter::new()) as Arc<dyn OverrideAdapter>),
168 };
169
170 let event_logger =
171 EventLogger::new(sdk_key, &options, &event_logging_adapter, &statsig_runtime);
172
173 let diagnostics = Arc::new(Diagnostics::new(event_logger.clone(), sdk_key));
174 let diagnostics_observer: Arc<dyn OpsStatsEventObserver> =
175 Arc::new(DiagnosticsObserver::new(diagnostics));
176 let error_observer: Arc<dyn OpsStatsEventObserver> =
177 Arc::new(SDKErrorsObserver::new(sdk_key, &options));
178
179 let ops_stats = setup_ops_stats(
180 sdk_key,
181 &options,
182 statsig_runtime.clone(),
183 &error_observer,
184 &diagnostics_observer,
185 &options.observability_client,
186 );
187
188 let spec_store = Arc::new(SpecStore::new(
189 sdk_key,
190 hashing.sha256(sdk_key),
191 statsig_runtime.clone(),
192 options.data_store.clone(),
193 ));
194
195 let environment = options
196 .environment
197 .as_ref()
198 .map(|env| HashMap::from([("tier".into(), dyn_value!(env.as_str()))]));
199
200 let persistent_values_manager = options.persistent_storage.clone().map(|storage| {
201 Arc::new(PersistentValuesManager {
202 persistent_storage: storage,
203 })
204 });
205
206 StatsigMetadata::update_service_name(options.service_name.clone());
207
208 Statsig {
209 sdk_key: sdk_key.to_string(),
210 options,
211 gcir_formatter: Arc::new(GCIRFormatter::new(&spec_store, &override_adapter)),
212 hashing,
213 statsig_environment: environment,
214 fallback_environment: Mutex::new(None),
215 override_adapter,
216 spec_store,
217 specs_adapter,
218 event_logging_adapter,
219 event_logger,
220 id_lists_adapter,
221 statsig_runtime,
222 ops_stats,
223 error_observer,
224 diagnostics_observer,
225 background_tasks_started: Arc::new(AtomicBool::new(false)),
226 persistent_values_manager,
227 initialize_details: Mutex::new(InitializeDetails::default()),
228 }
229 }
230
231 pub async fn initialize(&self) -> Result<(), StatsigErr> {
246 let details = self.initialize_with_details().await?;
247
248 if let Some(failure_details) = details.failure_details {
249 Err(failure_details
250 .error
251 .unwrap_or(StatsigErr::InitializationError(failure_details.reason)))
252 } else {
253 Ok(())
254 }
255 }
256
257 pub async fn initialize_with_details(&self) -> Result<InitializeDetails, StatsigErr> {
266 self.ops_stats.add_marker(
267 Marker::new(KeyType::Overall, ActionType::Start, None),
268 Some(ContextType::Initialize),
269 );
270
271 let init_details = if let Some(timeout_ms) = self.options.init_timeout_ms {
272 self.apply_timeout_to_init(timeout_ms).await
273 } else {
274 self.initialize_impl_with_details().await
275 };
276 self.log_init_details(&init_details);
277 if let Ok(details) = &init_details {
278 if let Ok(mut curr_init_details) = self.initialize_details.try_lock() {
279 *curr_init_details = details.clone();
280 }
281 }
282 init_details
283 }
284
285 pub fn get_initialize_details(&self) -> InitializeDetails {
286 match self.initialize_details.lock() {
287 Ok(details) => details.clone(),
288 Err(poison_error) => InitializeDetails::from_error(
289 "Failed to lock initialize_details",
290 Some(StatsigErr::LockFailure(poison_error.to_string())),
291 ),
292 }
293 }
294
295 pub fn is_initialized(&self) -> bool {
296 match self.initialize_details.lock() {
297 Ok(details) => details.init_success,
298 Err(_) => false,
299 }
300 }
301
302 pub async fn shutdown(&self) -> Result<(), StatsigErr> {
303 self.shutdown_with_timeout(Duration::from_secs(3)).await
304 }
305
306 pub async fn shutdown_with_timeout(&self, timeout: Duration) -> Result<(), StatsigErr> {
307 log_d!(
308 TAG,
309 "Shutting down Statsig with timeout {}ms",
310 timeout.as_millis()
311 );
312
313 let start = Instant::now();
314 let shutdown_result = tokio::select! {
315 () = tokio::time::sleep(timeout) => {
316 log_w!(TAG, "Statsig shutdown timed out. {}", start.elapsed().as_millis());
317 Err(StatsigErr::ShutdownFailure(
318 "Shutdown timed out".to_string()
319 ))
320 }
321 sub_result = async {
322 let id_list_shutdown: Pin<Box<_>> = if let Some(adapter) = &self.id_lists_adapter {
323 adapter.shutdown(timeout)
324 } else {
325 Box::pin(async { Ok(()) })
326 };
327
328 try_join!(
329 id_list_shutdown,
330 self.event_logger.shutdown(),
331 self.specs_adapter.shutdown(timeout, &self.statsig_runtime),
332 )
333 } => {
334 match sub_result {
335 Ok(_) => {
336 log_d!(TAG, "All Statsig tasks shutdown successfully");
337 Ok(())
338 }
339 Err(e) => {
340 log_w!(TAG, "Error during shutdown: {:?}", e);
341 Err(e)
342 }
343 }
344 }
345 };
346
347 self.statsig_runtime.shutdown();
348 shutdown_result
349 }
350
351 async fn start_background_tasks(
352 statsig_runtime: Arc<StatsigRuntime>,
353 id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
354 specs_adapter: Arc<dyn SpecsAdapter>,
355 ops_stats: Arc<OpsStatsForInstance>,
356 bg_tasks_started: Arc<AtomicBool>,
357 ) -> bool {
358 if bg_tasks_started.load(Ordering::SeqCst) {
359 return true;
360 }
361
362 let mut success = true;
363
364 if let Some(adapter) = &id_lists_adapter {
365 if let Err(e) = adapter
366 .clone()
367 .schedule_background_sync(&statsig_runtime)
368 .await
369 {
370 success = false;
371 log_w!(TAG, "Failed to schedule idlist background job {}", e);
372 }
373 }
374
375 if let Err(e) = specs_adapter
376 .clone()
377 .schedule_background_sync(&statsig_runtime)
378 .await
379 {
380 success = false;
381 log_error_to_statsig_and_console!(
382 ops_stats,
383 TAG,
384 StatsigErr::SpecsAdapterSkipPoll(format!(
385 "Failed to schedule specs adapter background job: {}",
386 e
387 ))
388 );
389 }
390
391 bg_tasks_started.store(true, Ordering::SeqCst);
392
393 success
394 }
395
396 async fn apply_timeout_to_init(
397 &self,
398 timeout_ms: u64,
399 ) -> Result<InitializeDetails, StatsigErr> {
400 let timeout = Duration::from_millis(timeout_ms);
401
402 let init_future = self.initialize_impl_with_details();
403 let timeout_future = sleep(timeout);
404
405 let statsig_runtime = self.statsig_runtime.clone();
406 let id_lists_adapter = self.id_lists_adapter.clone();
407 let specs_adapter = self.specs_adapter.clone();
408 let ops_stats = self.ops_stats.clone();
409 let background_tasks_started = self.background_tasks_started.clone();
410 let statsig_runtime_for_closure = statsig_runtime.clone();
412
413 tokio::select! {
414 result = init_future => {
415 result
416 },
417 _ = timeout_future => {
418 statsig_runtime.spawn(
419 "start_background_tasks",
420 |_shutdown_notify| async move {
421 Self::start_background_tasks(
422 statsig_runtime_for_closure,
423 id_lists_adapter,
424 specs_adapter,
425 ops_stats,
426 background_tasks_started,
427 ).await;
428 }
429 );
430 Ok(self.timeout_failure(timeout_ms))
431 },
432 }
433 }
434
435 async fn initialize_impl_with_details(&self) -> Result<InitializeDetails, StatsigErr> {
436 let start_time = Instant::now();
437 self.spec_store.set_source(SpecsSource::Loading);
438 self.specs_adapter.initialize(self.spec_store.clone());
439
440 let mut error_message = None;
441 let mut id_list_ready = None;
442
443 let init_country_lookup = if !self.options.disable_country_lookup.unwrap_or_default() {
444 Some(self.statsig_runtime.spawn(INIT_IP_TAG, |_| async {
445 CountryLookup::load_country_lookup();
446 }))
447 } else {
448 None
449 };
450
451 let init_ua = if !self.options.disable_user_agent_parsing.unwrap_or_default() {
452 Some(self.statsig_runtime.spawn(INIT_UA_TAG, |_| async {
453 UserAgentParser::load_parser();
454 }))
455 } else {
456 None
457 };
458
459 let init_res = match self
460 .specs_adapter
461 .clone()
462 .start(&self.statsig_runtime)
463 .await
464 {
465 Ok(()) => Ok(()),
466 Err(e) => {
467 self.spec_store.set_source(SpecsSource::NoValues);
468 error_message = Some(format!("Failed to start specs adapter: {e}"));
469 Err(e)
470 }
471 };
472
473 if let Some(adapter) = &self.id_lists_adapter {
474 match adapter
475 .clone()
476 .start(&self.statsig_runtime, self.spec_store.clone())
477 .await
478 {
479 Ok(()) => {
480 id_list_ready = Some(true);
481 }
482 Err(e) => {
483 id_list_ready = Some(false);
484 error_message.get_or_insert_with(|| format!("Failed to sync ID lists: {e}"));
485 }
486 }
487 if let Err(e) = adapter
488 .clone()
489 .schedule_background_sync(&self.statsig_runtime)
490 .await
491 {
492 log_w!(TAG, "Failed to schedule id_list background job {}", e);
493 }
494 }
495
496 if let Err(e) = self
497 .event_logging_adapter
498 .clone()
499 .start(&self.statsig_runtime)
500 .await
501 {
502 log_error_to_statsig_and_console!(
503 self.ops_stats.clone(),
504 TAG,
505 StatsigErr::UnstartedAdapter(format!(
506 "Failed to start event logging adapter: {}",
507 e
508 ))
509 );
510 }
511
512 let spec_info = self.spec_store.get_current_specs_info();
513 let duration = start_time.elapsed().as_millis() as f64;
514
515 self.set_default_environment_from_server();
516
517 if self.options.wait_for_country_lookup_init.unwrap_or(false) {
518 if let Some(task_id) = init_country_lookup {
519 let _ = self
520 .statsig_runtime
521 .await_join_handle(INIT_IP_TAG, &task_id)
522 .await;
523 }
524 }
525 if self.options.wait_for_user_agent_init.unwrap_or(false) {
526 if let Some(task_id) = init_ua {
527 let _ = self
528 .statsig_runtime
529 .await_join_handle(INIT_UA_TAG, &task_id)
530 .await;
531 };
532 }
533
534 let error = init_res.clone().err();
535
536 let success = Self::start_background_tasks(
537 self.statsig_runtime.clone(),
538 self.id_lists_adapter.clone(),
539 self.specs_adapter.clone(),
540 self.ops_stats.clone(),
541 self.background_tasks_started.clone(),
542 )
543 .await;
544
545 Ok(InitializeDetails {
546 init_success: success,
547 is_config_spec_ready: matches!(spec_info.lcut, Some(v) if v != 0),
548 is_id_list_ready: id_list_ready,
549 source: spec_info.source,
550 failure_details: error.as_ref().map(|e| FailureDetails {
551 reason: e.to_string(),
552 error: Some(e.clone()),
553 }),
554 duration,
555 })
556 }
557
558 fn timeout_failure(&self, timeout_ms: u64) -> InitializeDetails {
559 InitializeDetails {
560 init_success: false,
561 is_config_spec_ready: false,
562 is_id_list_ready: None,
563 source: SpecsSource::Uninitialized,
564 failure_details: Some(FailureDetails {
565 reason: "Initialization timed out".to_string(),
566 error: None,
567 }),
568 duration: timeout_ms as f64,
569 }
570 }
571
572 fn log_init_details(&self, init_details: &Result<InitializeDetails, StatsigErr>) {
573 match init_details {
574 Ok(details) => {
575 self.log_init_finish(
576 details.init_success,
577 &None,
578 &details.duration,
579 &self.spec_store.get_current_specs_info(),
580 );
581 if let Some(failure) = &details.failure_details {
582 log_error_to_statsig_and_console!(
583 self.ops_stats,
584 TAG,
585 StatsigErr::InitializationError(failure.reason.clone())
586 );
587 }
588 }
589 Err(err) => {
590 log_w!(TAG, "Initialization error: {:?}", err);
591 }
592 }
593 }
594
595 pub fn get_context(&self) -> StatsigContext {
596 StatsigContext {
597 sdk_key: self.sdk_key.clone(),
598 options: self.options.clone(),
599 local_override_adapter: self.override_adapter.clone(),
600 error_observer: self.error_observer.clone(),
601 diagnostics_observer: self.diagnostics_observer.clone(),
602 spec_store: self.spec_store.clone(),
603 }
604 }
605
606 pub fn log_event(
607 &self,
608 user: &StatsigUser,
609 event_name: &str,
610 value: Option<String>,
611 metadata: Option<HashMap<String, String>>,
612 ) {
613 let user_internal = self.internalize_user(user);
614
615 self.event_logger.enqueue(EnqueuePassthroughOp {
616 event: StatsigEventInternal::new_custom_event(
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 log_event_with_number(
626 &self,
627 user: &StatsigUser,
628 event_name: &str,
629 value: Option<f64>,
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_custom_event(
635 user_internal.to_loggable(),
636 event_name.to_string(),
637 value.map(|v| json!(v)),
638 metadata,
639 ),
640 });
641 }
642
643 pub fn log_layer_param_exposure_with_layer_json(
644 &self,
645 layer_json: String,
646 parameter_name: String,
647 ) {
648 let layer = match serde_json::from_str::<Layer>(&layer_json) {
649 Ok(layer) => layer,
650 Err(e) => {
651 log_error_to_statsig_and_console!(
652 self.ops_stats.clone(),
653 TAG,
654 StatsigErr::ShutdownFailure(e.to_string())
655 );
656 return;
657 }
658 };
659
660 self.log_layer_param_exposure_with_layer(layer, parameter_name);
661 }
662
663 pub fn log_layer_param_exposure_with_layer(&self, layer: Layer, parameter_name: String) {
664 if layer.__disable_exposure {
665 self.event_logger.increment_non_exposure_checks(&layer.name);
666 return;
667 }
668
669 self.event_logger
670 .enqueue(EnqueueLayerParamExpoOp::LayerOwned(
671 layer,
672 parameter_name,
673 ExposureTrigger::Auto,
674 ));
675 }
676
677 pub async fn flush_events(&self) {
678 let _ = self.event_logger.flush_all_pending_events().await;
679 }
680
681 pub fn get_client_init_response(&self, user: &StatsigUser) -> InitializeResponse {
682 self.get_client_init_response_with_options(user, self.gcir_formatter.get_default_options())
683 }
684
685 pub fn get_client_init_response_with_options(
686 &self,
687 user: &StatsigUser,
688 options: &ClientInitResponseOptions,
689 ) -> InitializeResponse {
690 let user_internal = self.internalize_user(user);
691 self.gcir_formatter
692 .get_as_v1_format(user_internal, &self.hashing, options)
693 }
694
695 pub fn get_client_init_response_as_string(&self, user: &StatsigUser) -> String {
696 serde_json::to_string(&self.get_client_init_response(user)).unwrap_or_default()
697 }
698
699 pub fn get_client_init_response_with_options_as_string(
700 &self,
701 user: &StatsigUser,
702 options: &ClientInitResponseOptions,
703 ) -> String {
704 let user_internal = self.internalize_user(user);
705 let response = match options.response_format {
706 Some(GCIRResponseFormat::InitializeWithSecondaryExposureMapping) => {
707 json!(self
708 .gcir_formatter
709 .get_as_v2_format(user_internal, &self.hashing, options))
710 }
711 _ => json!(self
712 .gcir_formatter
713 .get_as_v1_format(user_internal, &self.hashing, options)),
714 };
715
716 json!(response).to_string()
717 }
718
719 pub fn get_string_parameter_from_store(
720 &self,
721 user: &StatsigUser,
722 parameter_store_name: &str,
723 parameter_name: &str,
724 fallback: Option<String>,
725 ) -> Option<String> {
726 self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
727 }
728
729 pub fn get_boolean_parameter_from_store(
730 &self,
731 user: &StatsigUser,
732 parameter_store_name: &str,
733 parameter_name: &str,
734 fallback: Option<bool>,
735 ) -> Option<bool> {
736 self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
737 }
738
739 pub fn get_float_parameter_from_store(
740 &self,
741 user: &StatsigUser,
742 parameter_store_name: &str,
743 parameter_name: &str,
744 fallback: Option<f64>,
745 ) -> Option<f64> {
746 self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
747 }
748
749 pub fn get_integer_parameter_from_store(
750 &self,
751 user: &StatsigUser,
752 parameter_store_name: &str,
753 parameter_name: &str,
754 fallback: Option<i64>,
755 ) -> Option<i64> {
756 self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
757 }
758
759 pub fn get_array_parameter_from_store(
760 &self,
761 user: &StatsigUser,
762 parameter_store_name: &str,
763 parameter_name: &str,
764 fallback: Option<Vec<Value>>,
765 ) -> Option<Vec<Value>> {
766 self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
767 }
768
769 pub fn get_object_parameter_from_store(
770 &self,
771 user: &StatsigUser,
772 parameter_store_name: &str,
773 parameter_name: &str,
774 fallback: Option<HashMap<String, Value>>,
775 ) -> Option<HashMap<String, Value>> {
776 self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
777 }
778
779 pub fn get_parameter_from_store<T: DeserializeOwned>(
780 &self,
781 user: &StatsigUser,
782 parameter_store_name: &str,
783 parameter_name: &str,
784 fallback: Option<T>,
785 ) -> Option<T> {
786 let store = self.get_parameter_store(parameter_store_name);
787 match fallback {
788 Some(fallback) => Some(store.get(user, parameter_name, fallback)),
789 None => store.get_opt(user, parameter_name),
790 }
791 }
792
793 pub fn get_parameter_store(&self, parameter_store_name: &str) -> ParameterStore {
794 self.get_parameter_store_with_options(
795 parameter_store_name,
796 ParameterStoreEvaluationOptions::default(),
797 )
798 }
799
800 pub fn get_parameter_store_with_options(
801 &self,
802 parameter_store_name: &str,
803 options: ParameterStoreEvaluationOptions,
804 ) -> ParameterStore {
805 self.event_logger
806 .increment_non_exposure_checks(parameter_store_name);
807
808 let data = read_lock_or_else!(self.spec_store.data, {
809 log_error_to_statsig_and_console!(
810 self.ops_stats.clone(),
811 TAG,
812 StatsigErr::LockFailure(
813 "Failed to acquire read lock for spec store data".to_string()
814 )
815 );
816 return ParameterStore {
817 name: parameter_store_name.to_string(),
818 parameters: HashMap::new(),
819 details: EvaluationDetails::unrecognized_no_data(),
820 options,
821 _statsig_ref: self,
822 };
823 });
824
825 let stores = &data.values.param_stores;
826 let store = match stores {
827 Some(stores) => stores.get(parameter_store_name),
828 None => {
829 return ParameterStore {
830 name: parameter_store_name.to_string(),
831 parameters: HashMap::new(),
832 details: EvaluationDetails::unrecognized(&data),
833 options,
834 _statsig_ref: self,
835 };
836 }
837 };
838 match store {
839 Some(store) => ParameterStore {
840 name: parameter_store_name.to_string(),
841 parameters: store.parameters.clone(),
842 details: EvaluationDetails::recognized(&data, &EvaluatorResult::default()),
843 options,
844 _statsig_ref: self,
845 },
846 None => ParameterStore {
847 name: parameter_store_name.to_string(),
848 parameters: HashMap::new(),
849 details: EvaluationDetails::unrecognized(&data),
850 options,
851 _statsig_ref: self,
852 },
853 }
854 }
855}
856
857impl Statsig {
862 pub fn identify(&self, user: &StatsigUser) {
863 let user_internal = self.internalize_user(user);
864
865 self.event_logger.enqueue(EnqueuePassthroughOp {
866 event: StatsigEventInternal::new_custom_event(
867 user_internal.to_loggable(),
868 "statsig::identify".to_string(),
869 None,
870 None,
871 ),
872 });
873 }
874}
875
876impl Statsig {
881 pub fn get_cmab_ranked_groups(
882 &self,
883 user: &StatsigUser,
884 cmab_name: &str,
885 ) -> Vec<CMABRankedGroup> {
886 self.event_logger.increment_non_exposure_checks(cmab_name);
887
888 let data = read_lock_or_else!(self.spec_store.data, {
889 log_error_to_statsig_and_console!(
890 self.ops_stats.clone(),
891 TAG,
892 StatsigErr::LockFailure(
893 "Failed to acquire read lock for spec store data".to_string()
894 )
895 );
896 return vec![];
897 });
898 let user_internal = self.internalize_user(user);
899 get_cmab_ranked_list(
900 &mut EvaluatorContext::new(
901 &user_internal,
902 &data,
903 &self.hashing,
904 data.values.app_id.as_ref(),
905 self.override_adapter.as_ref(),
906 ),
907 cmab_name,
908 )
909 }
910
911 pub fn log_cmab_exposure_for_group(
912 &self,
913 user: &StatsigUser,
914 cmab_name: &str,
915 group_id: String,
916 ) {
917 let user_internal = self.internalize_user(user);
918
919 let mut experiment = self.get_experiment_impl(&user_internal, cmab_name);
920 experiment.rule_id = group_id;
921
922 self.event_logger.enqueue(EnqueueExperimentExpoOp {
923 user: &user_internal,
924 experiment: &experiment,
925 trigger: ExposureTrigger::Manual,
926 });
927 }
928}
929
930impl Statsig {
935 pub fn shared() -> Arc<Statsig> {
936 let lock = match SHARED_INSTANCE.lock() {
937 Ok(lock) => lock,
938 Err(e) => {
939 log_e!(TAG, "Statsig::shared() mutex error: {}", e);
940 return Arc::new(Statsig::new(ERROR_SDK_KEY, None));
941 }
942 };
943
944 match lock.as_ref() {
945 Some(statsig) => statsig.clone(),
946 None => {
947 log_e!(
948 TAG,
949 "Statsig::shared() called, but no instance has been set with Statsig::new_shared(...)"
950 );
951 Arc::new(Statsig::new(ERROR_SDK_KEY, None))
952 }
953 }
954 }
955
956 pub fn new_shared(
957 sdk_key: &str,
958 options: Option<Arc<StatsigOptions>>,
959 ) -> Result<Arc<Statsig>, StatsigErr> {
960 match SHARED_INSTANCE.lock() {
961 Ok(mut lock) => {
962 if lock.is_some() {
963 let message = "Statsig shared instance already exists. Call Statsig::remove_shared() before creating a new instance.";
964 log_e!(TAG, "{}", message);
965 return Err(StatsigErr::SharedInstanceFailure(message.to_string()));
966 }
967
968 let statsig = Arc::new(Statsig::new(sdk_key, options));
969 *lock = Some(statsig.clone());
970 Ok(statsig)
971 }
972 Err(e) => {
973 let message = format!("Statsig::new_shared() mutex error: {}", e);
974 log_e!(TAG, "{}", message);
975 Err(StatsigErr::SharedInstanceFailure(message))
976 }
977 }
978 }
979
980 pub fn remove_shared() {
981 match SHARED_INSTANCE.lock() {
982 Ok(mut lock) => {
983 *lock = None;
984 }
985 Err(e) => {
986 log_e!(TAG, "Statsig::remove_shared() mutex error: {}", e);
987 }
988 }
989 }
990
991 pub fn has_shared_instance() -> bool {
992 match SHARED_INSTANCE.lock() {
993 Ok(lock) => lock.is_some(),
994 Err(_) => false,
995 }
996 }
997}
998
999impl Statsig {
1004 pub fn check_gate(&self, user: &StatsigUser, gate_name: &str) -> bool {
1005 self.check_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1006 }
1007
1008 pub fn check_gate_with_options(
1009 &self,
1010 user: &StatsigUser,
1011 gate_name: &str,
1012 options: FeatureGateEvaluationOptions,
1013 ) -> bool {
1014 let user_internal = self.internalize_user(user);
1015 let disable_exposure_logging = options.disable_exposure_logging;
1016 let (details, evaluation) = self.get_gate_evaluation(&user_internal, gate_name);
1017 let value = evaluation.as_ref().map(|e| e.value).unwrap_or_default();
1018
1019 if disable_exposure_logging {
1020 log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1021 self.event_logger.increment_non_exposure_checks(gate_name);
1022 } else {
1023 self.event_logger.enqueue(EnqueueGateExpoOp {
1024 user: &user_internal,
1025 evaluation: evaluation.map(Cow::Owned),
1026 details: details.clone(),
1027 trigger: ExposureTrigger::Auto,
1028 });
1029 }
1030
1031 value
1032 }
1033
1034 pub fn get_feature_gate(&self, user: &StatsigUser, gate_name: &str) -> FeatureGate {
1035 self.get_feature_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1036 }
1037
1038 pub fn get_feature_gate_with_options(
1039 &self,
1040 user: &StatsigUser,
1041 gate_name: &str,
1042 options: FeatureGateEvaluationOptions,
1043 ) -> FeatureGate {
1044 let user_internal = self.internalize_user(user);
1045 let disable_exposure_logging = options.disable_exposure_logging;
1046 let (details, evaluation) = self.get_gate_evaluation(&user_internal, gate_name);
1047
1048 if disable_exposure_logging {
1049 log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1050 self.event_logger.increment_non_exposure_checks(gate_name);
1051 } else {
1052 self.event_logger.enqueue(EnqueueGateExpoOp {
1053 user: &user_internal,
1054 evaluation: evaluation.as_ref().map(Cow::Borrowed),
1055 details: details.clone(),
1056 trigger: ExposureTrigger::Auto,
1057 });
1058 }
1059
1060 make_feature_gate(gate_name, evaluation, details)
1061 }
1062
1063 pub fn manually_log_gate_exposure(&self, user: &StatsigUser, gate_name: &str) {
1064 let user_internal = self.internalize_user(user);
1065 let (details, evaluation) = self.get_gate_evaluation(&user_internal, gate_name);
1066 self.event_logger.enqueue(EnqueueGateExpoOp {
1067 user: &user_internal,
1068 evaluation: evaluation.map(Cow::Owned),
1069 details: details.clone(),
1070 trigger: ExposureTrigger::Manual,
1071 });
1072 }
1073
1074 pub fn get_fields_needed_for_gate(&self, gate_name: &str) -> Vec<String> {
1075 let data = read_lock_or_else!(self.spec_store.data, {
1076 log_error_to_statsig_and_console!(
1077 self.ops_stats.clone(),
1078 TAG,
1079 StatsigErr::LockFailure(
1080 "Failed to acquire read lock for spec store data".to_string()
1081 )
1082 );
1083 return vec![];
1084 });
1085
1086 let gate = data.values.feature_gates.get(gate_name);
1087 match gate {
1088 Some(gate) => match &gate.fields_used {
1089 Some(fields) => fields.clone(),
1090 None => vec![],
1091 },
1092 None => vec![],
1093 }
1094 }
1095}
1096
1097impl Statsig {
1102 pub fn override_gate(&self, gate_name: &str, value: bool, id: Option<&str>) {
1103 if let Some(adapter) = &self.override_adapter {
1104 adapter.override_gate(gate_name, value, id);
1105 }
1106 }
1107
1108 pub fn override_dynamic_config(
1109 &self,
1110 config_name: &str,
1111 value: HashMap<String, serde_json::Value>,
1112 id: Option<&str>,
1113 ) {
1114 if let Some(adapter) = &self.override_adapter {
1115 adapter.override_dynamic_config(config_name, value, id);
1116 }
1117 }
1118
1119 pub fn override_layer(
1120 &self,
1121 layer_name: &str,
1122 value: HashMap<String, serde_json::Value>,
1123 id: Option<&str>,
1124 ) {
1125 if let Some(adapter) = &self.override_adapter {
1126 adapter.override_layer(layer_name, value, id);
1127 }
1128 }
1129
1130 pub fn override_experiment(
1131 &self,
1132 experiment_name: &str,
1133 value: HashMap<String, serde_json::Value>,
1134 id: Option<&str>,
1135 ) {
1136 if let Some(adapter) = &self.override_adapter {
1137 adapter.override_experiment(experiment_name, value, id);
1138 }
1139 }
1140
1141 pub fn override_experiment_by_group_name(
1142 &self,
1143 experiment_name: &str,
1144 group_name: &str,
1145 id: Option<&str>,
1146 ) {
1147 if let Some(adapter) = &self.override_adapter {
1148 adapter.override_experiment_by_group_name(experiment_name, group_name, id);
1149 }
1150 }
1151
1152 pub fn remove_gate_override(&self, gate_name: &str, id: Option<&str>) {
1153 if let Some(adapter) = &self.override_adapter {
1154 adapter.remove_gate_override(gate_name, id);
1155 }
1156 }
1157
1158 pub fn remove_dynamic_config_override(&self, config_name: &str, id: Option<&str>) {
1159 if let Some(adapter) = &self.override_adapter {
1160 adapter.remove_dynamic_config_override(config_name, id);
1161 }
1162 }
1163
1164 pub fn remove_experiment_override(&self, experiment_name: &str, id: Option<&str>) {
1165 if let Some(adapter) = &self.override_adapter {
1166 adapter.remove_experiment_override(experiment_name, id);
1167 }
1168 }
1169
1170 pub fn remove_layer_override(&self, layer_name: &str, id: Option<&str>) {
1171 if let Some(adapter) = &self.override_adapter {
1172 adapter.remove_layer_override(layer_name, id);
1173 }
1174 }
1175
1176 pub fn remove_all_overrides(&self) {
1177 if let Some(adapter) = &self.override_adapter {
1178 adapter.remove_all_overrides();
1179 }
1180 }
1181}
1182
1183impl Statsig {
1188 pub fn get_feature_gate_list(&self) -> Vec<String> {
1189 let data = read_lock_or_else!(self.spec_store.data, {
1190 log_error_to_statsig_and_console!(
1191 self.ops_stats.clone(),
1192 TAG,
1193 StatsigErr::LockFailure(
1194 "Failed to acquire read lock for spec store data".to_string()
1195 )
1196 );
1197 return vec![];
1198 });
1199
1200 data.values.feature_gates.keys().cloned().collect()
1201 }
1202
1203 pub fn get_dynamic_config_list(&self) -> Vec<String> {
1204 let data = read_lock_or_else!(self.spec_store.data, {
1205 log_error_to_statsig_and_console!(
1206 self.ops_stats.clone(),
1207 TAG,
1208 StatsigErr::LockFailure(
1209 "Failed to acquire read lock for spec store data".to_string()
1210 )
1211 );
1212 return vec![];
1213 });
1214
1215 data.values
1216 .dynamic_configs
1217 .iter()
1218 .filter(|(_, config)| config.entity == "dynamic_config")
1219 .map(|(name, _)| name.clone())
1220 .collect()
1221 }
1222
1223 pub fn get_experiment_list(&self) -> Vec<String> {
1224 let data = read_lock_or_else!(self.spec_store.data, {
1225 log_error_to_statsig_and_console!(
1226 self.ops_stats.clone(),
1227 TAG,
1228 StatsigErr::LockFailure(
1229 "Failed to acquire read lock for spec store data".to_string()
1230 )
1231 );
1232 return vec![];
1233 });
1234
1235 data.values
1236 .dynamic_configs
1237 .iter()
1238 .filter(|(_, config)| config.entity == "experiment")
1239 .map(|(name, _)| name.clone())
1240 .collect()
1241 }
1242
1243 pub fn get_parameter_store_list(&self) -> Vec<String> {
1244 let data = read_lock_or_else!(self.spec_store.data, {
1245 log_error_to_statsig_and_console!(
1246 self.ops_stats.clone(),
1247 TAG,
1248 StatsigErr::LockFailure(
1249 "Failed to acquire read lock for spec store data".to_string()
1250 )
1251 );
1252 return vec![];
1253 });
1254
1255 match &data.values.param_stores {
1256 Some(param_stores) => param_stores.keys().cloned().collect(),
1257 None => vec![],
1258 }
1259 }
1260}
1261
1262impl Statsig {
1267 pub fn get_dynamic_config(
1268 &self,
1269 user: &StatsigUser,
1270 dynamic_config_name: &str,
1271 ) -> DynamicConfig {
1272 self.get_dynamic_config_with_options(
1273 user,
1274 dynamic_config_name,
1275 DynamicConfigEvaluationOptions::default(),
1276 )
1277 }
1278
1279 pub fn get_dynamic_config_with_options(
1280 &self,
1281 user: &StatsigUser,
1282 dynamic_config_name: &str,
1283 options: DynamicConfigEvaluationOptions,
1284 ) -> DynamicConfig {
1285 let user_internal = self.internalize_user(user);
1286 let disable_exposure_logging = options.disable_exposure_logging;
1287 let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1288
1289 if disable_exposure_logging {
1290 log_d!(
1291 TAG,
1292 "Exposure logging is disabled for Dynamic Config {}",
1293 dynamic_config_name
1294 );
1295 self.event_logger
1296 .increment_non_exposure_checks(dynamic_config_name);
1297 } else {
1298 self.event_logger.enqueue(EnqueueConfigExpoOp {
1299 user: &user_internal,
1300 config: &dynamic_config,
1301 trigger: ExposureTrigger::Auto,
1302 });
1303 }
1304
1305 dynamic_config
1306 }
1307
1308 pub fn manually_log_dynamic_config_exposure(
1309 &self,
1310 user: &StatsigUser,
1311 dynamic_config_name: &str,
1312 ) {
1313 let user_internal = self.internalize_user(user);
1314 let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1315 self.event_logger.enqueue(EnqueueConfigExpoOp {
1316 user: &user_internal,
1317 config: &dynamic_config,
1318 trigger: ExposureTrigger::Manual,
1319 });
1320 }
1321
1322 pub fn get_fields_needed_for_dynamic_config(&self, config_name: &str) -> Vec<String> {
1323 let data = read_lock_or_else!(self.spec_store.data, {
1324 log_error_to_statsig_and_console!(
1325 self.ops_stats.clone(),
1326 TAG,
1327 StatsigErr::LockFailure(
1328 "Failed to acquire read lock for spec store data".to_string()
1329 )
1330 );
1331 return vec![];
1332 });
1333
1334 let config = data.values.dynamic_configs.get(config_name);
1335 match config {
1336 Some(config) => match &config.fields_used {
1337 Some(fields) => fields.clone(),
1338 None => vec![],
1339 },
1340 None => vec![],
1341 }
1342 }
1343}
1344
1345impl Statsig {
1350 pub fn get_experiment(&self, user: &StatsigUser, experiment_name: &str) -> Experiment {
1351 self.get_experiment_with_options(
1352 user,
1353 experiment_name,
1354 ExperimentEvaluationOptions::default(),
1355 )
1356 }
1357
1358 pub fn get_experiment_with_options(
1359 &self,
1360 user: &StatsigUser,
1361 experiment_name: &str,
1362 options: ExperimentEvaluationOptions,
1363 ) -> Experiment {
1364 let user_internal = self.internalize_user(user);
1365 let disable_exposure_logging = options.disable_exposure_logging;
1366 let mut experiment = self.get_experiment_impl(&user_internal, experiment_name);
1367 if let Some(persisted_experiment) = self.persistent_values_manager.as_ref().and_then(|m| {
1368 m.try_apply_sticky_value_to_experiment(&user_internal, &options, &experiment)
1369 }) {
1370 experiment = persisted_experiment
1371 }
1372
1373 if disable_exposure_logging {
1374 log_d!(
1375 TAG,
1376 "Exposure logging is disabled for experiment {}",
1377 experiment_name
1378 );
1379 self.event_logger
1380 .increment_non_exposure_checks(experiment_name);
1381 } else {
1382 self.event_logger.enqueue(EnqueueExperimentExpoOp {
1383 user: &user_internal,
1384 experiment: &experiment,
1385 trigger: ExposureTrigger::Auto,
1386 });
1387 }
1388
1389 experiment
1390 }
1391
1392 pub fn manually_log_experiment_exposure(&self, user: &StatsigUser, experiment_name: &str) {
1393 let user_internal = self.internalize_user(user);
1394 let experiment = self.get_experiment_impl(&user_internal, experiment_name);
1395 self.event_logger.enqueue(EnqueueExperimentExpoOp {
1396 user: &user_internal,
1397 experiment: &experiment,
1398 trigger: ExposureTrigger::Manual,
1399 });
1400 }
1401
1402 pub fn get_fields_needed_for_experiment(&self, experiment_name: &str) -> Vec<String> {
1403 let data = read_lock_or_else!(self.spec_store.data, {
1404 log_error_to_statsig_and_console!(
1405 self.ops_stats.clone(),
1406 TAG,
1407 StatsigErr::LockFailure(
1408 "Failed to acquire read lock for spec store data".to_string()
1409 )
1410 );
1411 return vec![];
1412 });
1413
1414 let config = data.values.dynamic_configs.get(experiment_name);
1415 match config {
1416 Some(config) => match &config.fields_used {
1417 Some(fields) => fields.clone(),
1418 None => vec![],
1419 },
1420 None => vec![],
1421 }
1422 }
1423}
1424
1425impl Statsig {
1430 pub fn get_layer(&self, user: &StatsigUser, layer_name: &str) -> Layer {
1431 self.get_layer_with_options(user, layer_name, LayerEvaluationOptions::default())
1432 }
1433
1434 pub fn get_layer_with_options(
1435 &self,
1436 user: &StatsigUser,
1437 layer_name: &str,
1438 options: LayerEvaluationOptions,
1439 ) -> Layer {
1440 let user_internal = self.internalize_user(user);
1441 self.get_layer_impl(user_internal, layer_name, options)
1442 }
1443
1444 pub fn manually_log_layer_parameter_exposure(
1445 &self,
1446 user: &StatsigUser,
1447 layer_name: &str,
1448 parameter_name: String,
1449 ) {
1450 let user_internal = self.internalize_user(user);
1451 let layer =
1452 self.get_layer_impl(user_internal, layer_name, LayerEvaluationOptions::default());
1453
1454 self.event_logger
1455 .enqueue(EnqueueLayerParamExpoOp::LayerOwned(
1456 layer,
1457 parameter_name,
1458 ExposureTrigger::Manual,
1459 ));
1460 }
1461
1462 pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> {
1463 let data = read_lock_or_else!(self.spec_store.data, {
1464 log_error_to_statsig_and_console!(
1465 self.ops_stats.clone(),
1466 TAG,
1467 StatsigErr::LockFailure(
1468 "Failed to acquire read lock for spec store data".to_string()
1469 )
1470 );
1471 return vec![];
1472 });
1473
1474 let layer = data.values.layer_configs.get(layer_name);
1475 match layer {
1476 Some(layer) => match &layer.fields_used {
1477 Some(fields) => fields.clone(),
1478 None => vec![],
1479 },
1480 None => vec![],
1481 }
1482 }
1483}
1484
1485impl Statsig {
1490 pub(crate) fn get_from_statsig_env(&self, key: &str) -> Option<DynamicValue> {
1491 if let Some(env) = &self.statsig_environment {
1492 return env.get(key).cloned();
1493 }
1494
1495 if let Ok(fallback_env) = self.fallback_environment.lock() {
1496 if let Some(env) = &*fallback_env {
1497 return env.get(key).cloned();
1498 }
1499 }
1500
1501 None
1502 }
1503
1504 pub(crate) fn get_value_from_global_custom_fields(&self, key: &str) -> Option<&DynamicValue> {
1505 if let Some(env) = &self.options.global_custom_fields {
1506 return env.get(key);
1507 }
1508
1509 None
1510 }
1511
1512 pub(crate) fn use_global_custom_fields<T>(
1513 &self,
1514 f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> Result<(), T>,
1515 ) -> Result<(), T> {
1516 f(self.options.global_custom_fields.as_ref())
1517 }
1518
1519 pub(crate) fn use_statsig_env<T>(
1520 &self,
1521 f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1522 ) -> T {
1523 if let Some(env) = &self.statsig_environment {
1524 return f(Some(env));
1525 }
1526
1527 if let Ok(fallback_env) = self.fallback_environment.lock() {
1528 if let Some(env) = &*fallback_env {
1529 return f(Some(env));
1530 }
1531 }
1532
1533 f(None)
1534 }
1535}
1536
1537impl Statsig {
1542 fn evaluate_spec<T>(
1543 &self,
1544 user_internal: &StatsigUserInternal,
1545 spec_name: &str,
1546 make_empty_result: impl FnOnce(EvaluationDetails) -> T,
1547 make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
1548 spec_type: &SpecType,
1549 ) -> T {
1550 let data = read_lock_or_else!(self.spec_store.data, {
1551 log_error_to_statsig_and_console!(
1552 &self.ops_stats,
1553 TAG,
1554 StatsigErr::LockFailure(
1555 "Failed to acquire read lock for spec store data".to_string()
1556 )
1557 );
1558 return make_empty_result(EvaluationDetails::unrecognized_no_data());
1559 });
1560 let app_id = data.values.app_id.as_ref();
1561 let mut context = EvaluatorContext::new(
1562 user_internal,
1563 &data,
1564 &self.hashing,
1565 app_id,
1566 self.override_adapter.as_ref(),
1567 );
1568
1569 match Evaluator::evaluate_with_details(&mut context, spec_name, spec_type) {
1570 Ok(eval_details) => make_result(context.result, eval_details),
1571 Err(e) => {
1572 log_error_to_statsig_and_console!(
1573 &self.ops_stats,
1574 TAG,
1575 StatsigErr::EvaluationError(e.to_string())
1576 );
1577 make_empty_result(EvaluationDetails::error(&e.to_string()))
1578 }
1579 }
1580 }
1581
1582 fn get_gate_evaluation(
1583 &self,
1584 user_internal: &StatsigUserInternal,
1585 gate_name: &str,
1586 ) -> (EvaluationDetails, Option<GateEvaluation>) {
1587 self.evaluate_spec(
1588 user_internal,
1589 gate_name,
1590 |eval_details| (eval_details, None),
1591 |mut result, eval_details| {
1592 let evaluation = result_to_gate_eval(gate_name, &mut result);
1593 (eval_details, Some(evaluation))
1594 },
1595 &SpecType::Gate,
1596 )
1597 }
1598
1599 fn get_dynamic_config_impl(
1600 &self,
1601 user_internal: &StatsigUserInternal,
1602 config_name: &str,
1603 ) -> DynamicConfig {
1604 self.evaluate_spec(
1605 user_internal,
1606 config_name,
1607 |eval_details| make_dynamic_config(config_name, None, eval_details),
1608 |mut result, eval_details| {
1609 let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
1610 make_dynamic_config(config_name, Some(evaluation), eval_details)
1611 },
1612 &SpecType::DynamicConfig,
1613 )
1614 }
1615
1616 fn get_experiment_impl(
1617 &self,
1618 user_internal: &StatsigUserInternal,
1619 experiment_name: &str,
1620 ) -> Experiment {
1621 self.evaluate_spec(
1622 user_internal,
1623 experiment_name,
1624 |eval_details| make_experiment(experiment_name, None, eval_details),
1625 |mut result, eval_details| {
1626 let data = read_lock_or_else!(self.spec_store.data, {
1627 let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
1628 return make_experiment(experiment_name, Some(evaluation), eval_details);
1629 });
1630 let evaluation = result_to_experiment_eval(
1631 experiment_name,
1632 data.values.dynamic_configs.get(experiment_name),
1633 &mut result,
1634 );
1635 make_experiment(experiment_name, Some(evaluation), eval_details)
1636 },
1637 &SpecType::Experiment,
1638 )
1639 }
1640
1641 fn get_layer_impl(
1642 &self,
1643 user_internal: StatsigUserInternal,
1644 layer_name: &str,
1645 evaluation_options: LayerEvaluationOptions,
1646 ) -> Layer {
1647 let disable_exposure_logging = evaluation_options.disable_exposure_logging;
1648
1649 if disable_exposure_logging {
1650 self.event_logger.increment_non_exposure_checks(layer_name);
1651 }
1652
1653 let mut layer = self.evaluate_spec(
1654 &user_internal,
1655 layer_name,
1656 |eval_details| {
1657 make_layer(
1658 user_internal.to_loggable(),
1659 layer_name,
1660 None,
1661 eval_details,
1662 None,
1663 disable_exposure_logging,
1664 )
1665 },
1666 |mut result, eval_details| {
1667 let evaluation = result_to_layer_eval(layer_name, &mut result);
1668 let event_logger_ptr = Arc::downgrade(&self.event_logger);
1669
1670 make_layer(
1671 user_internal.to_loggable(),
1672 layer_name,
1673 Some(evaluation),
1674 eval_details,
1675 Some(event_logger_ptr),
1676 disable_exposure_logging,
1677 )
1678 },
1679 &SpecType::Layer,
1680 );
1681 if let Some(persisted_layer) = self.persistent_values_manager.as_ref().and_then(|p| {
1682 let event_logger_ptr = Arc::downgrade(&self.event_logger);
1683 p.try_apply_sticky_value_to_layer(
1684 &user_internal,
1685 &evaluation_options,
1686 &layer,
1687 Some(event_logger_ptr),
1688 disable_exposure_logging,
1689 )
1690 }) {
1691 layer = persisted_layer
1692 }
1693 layer
1694 }
1695
1696 fn internalize_user<'s, 'u>(&'s self, user: &'u StatsigUser) -> StatsigUserInternal<'s, 'u> {
1697 StatsigUserInternal::new(user, Some(self))
1698 }
1699
1700 fn set_default_environment_from_server(&self) {
1701 let data = read_lock_or_else!(self.spec_store.data, {
1702 return;
1703 });
1704
1705 if let Some(default_env) = data.values.default_environment.as_ref() {
1706 let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
1707
1708 if let Ok(mut fallback_env) = self.fallback_environment.lock() {
1709 *fallback_env = Some(env_map);
1710 }
1711 }
1712 }
1713
1714 fn log_init_finish(
1715 &self,
1716 success: bool,
1717 error_message: &Option<String>,
1718 duration: &f64,
1719 specs_info: &SpecsInfo,
1720 ) {
1721 let is_store_populated = specs_info.source != SpecsSource::NoValues;
1722 let source_str = specs_info.source.to_string();
1723 self.ops_stats.log(ObservabilityEvent::new_event(
1724 MetricType::Dist,
1725 "initialization".to_string(),
1726 *duration,
1727 Some(HashMap::from([
1728 ("success".to_owned(), success.to_string()),
1729 ("source".to_owned(), source_str.clone()),
1730 ("store_populated".to_owned(), is_store_populated.to_string()),
1731 ])),
1732 ));
1733 self.ops_stats.add_marker(
1734 {
1735 let marker = Marker::new(KeyType::Overall, ActionType::End, None)
1736 .with_is_success(success)
1737 .with_config_spec_ready(specs_info.source != SpecsSource::NoValues)
1738 .with_source(source_str);
1739
1740 if let Some(msg) = &error_message {
1741 marker.with_message(msg.to_string())
1742 } else {
1743 marker
1744 }
1745 },
1746 Some(ContextType::Initialize),
1747 );
1748 self.ops_stats
1749 .enqueue_diagnostics_event(None, Some(ContextType::Initialize));
1750 }
1751}
1752
1753fn initialize_event_logging_adapter(
1754 sdk_key: &str,
1755 options: &StatsigOptions,
1756) -> Arc<dyn EventLoggingAdapter> {
1757 options
1758 .event_logging_adapter
1759 .clone()
1760 .unwrap_or_else(|| Arc::new(StatsigHttpEventLoggingAdapter::new(sdk_key, Some(options))))
1761}
1762
1763fn initialize_specs_adapter(
1764 sdk_key: &str,
1765 options: &StatsigOptions,
1766 hashing: &HashUtil,
1767) -> Arc<dyn SpecsAdapter> {
1768 if let Some(adapter) = options.specs_adapter.clone() {
1769 log_d!(TAG, "Using provided SpecsAdapter: {}", sdk_key);
1770 return adapter;
1771 }
1772
1773 if let Some(adapter_config) = options.spec_adapters_config.clone() {
1774 return Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
1775 sdk_key,
1776 adapter_config,
1777 options,
1778 hashing,
1779 ));
1780 }
1781
1782 if let Some(data_adapter) = options.data_store.clone() {
1783 return Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
1784 sdk_key,
1785 data_adapter,
1786 options,
1787 hashing,
1788 ));
1789 }
1790
1791 Arc::new(StatsigHttpSpecsAdapter::new(sdk_key, Some(options)))
1792}
1793
1794fn initialize_id_lists_adapter(
1795 sdk_key: &str,
1796 options: &StatsigOptions,
1797) -> Option<Arc<dyn IdListsAdapter>> {
1798 if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
1799 return Some(id_lists_adapter);
1800 }
1801
1802 if options.enable_id_lists.unwrap_or(false) {
1803 return Some(Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options)));
1804 }
1805
1806 None
1807}
1808
1809fn setup_ops_stats(
1810 sdk_key: &str,
1811 options: &StatsigOptions,
1812 statsig_runtime: Arc<StatsigRuntime>,
1813 error_observer: &Arc<dyn OpsStatsEventObserver>,
1814 diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
1815 external_observer: &Option<Weak<dyn ObservabilityClient>>,
1816) -> Arc<OpsStatsForInstance> {
1817 initialize_simple_output_logger(&options.output_log_level);
1819
1820 let ops_stat = OPS_STATS.get_for_instance(sdk_key);
1821 ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
1822 ops_stat.subscribe(
1823 statsig_runtime.clone(),
1824 Arc::downgrade(diagnostics_observer),
1825 );
1826
1827 if let Some(ob_client) = external_observer {
1828 if let Some(client) = ob_client.upgrade() {
1829 client.init();
1830 let as_observer = client.to_ops_stats_event_observer();
1831 ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
1832 }
1833 }
1834
1835 ops_stat
1836}