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