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