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