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