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 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.clone(),
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.clone(),
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.clone(),
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_parameter_store_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.clone(),
1326 TAG,
1327 StatsigErr::LockFailure(
1328 "Failed to acquire read lock for spec store data".to_string()
1329 )
1330 );
1331 return vec![];
1332 });
1333
1334 match &data.values.param_stores {
1335 Some(param_stores) => param_stores.keys().cloned().collect(),
1336 None => vec![],
1337 }
1338 }
1339}
1340
1341impl Statsig {
1346 pub fn get_dynamic_config(
1347 &self,
1348 user: &StatsigUser,
1349 dynamic_config_name: &str,
1350 ) -> DynamicConfig {
1351 self.get_dynamic_config_with_options(
1352 user,
1353 dynamic_config_name,
1354 DynamicConfigEvaluationOptions::default(),
1355 )
1356 }
1357
1358 pub fn get_dynamic_config_with_options(
1359 &self,
1360 user: &StatsigUser,
1361 dynamic_config_name: &str,
1362 options: DynamicConfigEvaluationOptions,
1363 ) -> DynamicConfig {
1364 let user_internal = self.internalize_user(user);
1365 let disable_exposure_logging = options.disable_exposure_logging;
1366 let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1367
1368 if disable_exposure_logging {
1369 log_d!(
1370 TAG,
1371 "Exposure logging is disabled for Dynamic Config {}",
1372 dynamic_config_name
1373 );
1374 self.event_logger
1375 .increment_non_exposure_checks(dynamic_config_name);
1376 } else {
1377 self.event_logger.enqueue(EnqueueConfigExpoOp {
1378 user: &user_internal,
1379 config: &dynamic_config,
1380 trigger: ExposureTrigger::Auto,
1381 });
1382 }
1383
1384 dynamic_config
1385 }
1386
1387 pub fn manually_log_dynamic_config_exposure(
1388 &self,
1389 user: &StatsigUser,
1390 dynamic_config_name: &str,
1391 ) {
1392 let user_internal = self.internalize_user(user);
1393 let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1394 self.event_logger.enqueue(EnqueueConfigExpoOp {
1395 user: &user_internal,
1396 config: &dynamic_config,
1397 trigger: ExposureTrigger::Manual,
1398 });
1399 }
1400
1401 pub fn get_fields_needed_for_dynamic_config(&self, config_name: &str) -> Vec<String> {
1402 let data = read_lock_or_else!(self.spec_store.data, {
1403 log_error_to_statsig_and_console!(
1404 self.ops_stats.clone(),
1405 TAG,
1406 StatsigErr::LockFailure(
1407 "Failed to acquire read lock for spec store data".to_string()
1408 )
1409 );
1410 return vec![];
1411 });
1412
1413 let config = data.values.dynamic_configs.get(config_name);
1414 match config {
1415 Some(config) => match &config.spec.fields_used {
1416 Some(fields) => fields.clone(),
1417 None => vec![],
1418 },
1419 None => vec![],
1420 }
1421 }
1422}
1423
1424impl Statsig {
1429 pub fn get_experiment(&self, user: &StatsigUser, experiment_name: &str) -> Experiment {
1430 self.get_experiment_with_options(
1431 user,
1432 experiment_name,
1433 ExperimentEvaluationOptions::default(),
1434 )
1435 }
1436
1437 pub fn get_experiment_with_options(
1438 &self,
1439 user: &StatsigUser,
1440 experiment_name: &str,
1441 options: ExperimentEvaluationOptions,
1442 ) -> Experiment {
1443 let user_internal = self.internalize_user(user);
1444 let disable_exposure_logging = options.disable_exposure_logging;
1445 let mut experiment = self.get_experiment_impl(&user_internal, experiment_name);
1446 if let Some(persisted_experiment) = self.persistent_values_manager.as_ref().and_then(|m| {
1447 m.try_apply_sticky_value_to_experiment(&user_internal, &options, &experiment)
1448 }) {
1449 experiment = persisted_experiment
1450 }
1451
1452 if disable_exposure_logging {
1453 log_d!(
1454 TAG,
1455 "Exposure logging is disabled for experiment {}",
1456 experiment_name
1457 );
1458 self.event_logger
1459 .increment_non_exposure_checks(experiment_name);
1460 } else {
1461 self.event_logger.enqueue(EnqueueExperimentExpoOp {
1462 user: &user_internal,
1463 experiment: &experiment,
1464 trigger: ExposureTrigger::Auto,
1465 });
1466 }
1467
1468 experiment
1469 }
1470
1471 pub fn manually_log_experiment_exposure(&self, user: &StatsigUser, experiment_name: &str) {
1472 let user_internal = self.internalize_user(user);
1473 let experiment = self.get_experiment_impl(&user_internal, experiment_name);
1474 self.event_logger.enqueue(EnqueueExperimentExpoOp {
1475 user: &user_internal,
1476 experiment: &experiment,
1477 trigger: ExposureTrigger::Manual,
1478 });
1479 }
1480
1481 pub fn get_fields_needed_for_experiment(&self, experiment_name: &str) -> Vec<String> {
1482 let data = read_lock_or_else!(self.spec_store.data, {
1483 log_error_to_statsig_and_console!(
1484 self.ops_stats.clone(),
1485 TAG,
1486 StatsigErr::LockFailure(
1487 "Failed to acquire read lock for spec store data".to_string()
1488 )
1489 );
1490 return vec![];
1491 });
1492
1493 let config = data.values.dynamic_configs.get(experiment_name);
1494 match config {
1495 Some(config) => match &config.spec.fields_used {
1496 Some(fields) => fields.clone(),
1497 None => vec![],
1498 },
1499 None => vec![],
1500 }
1501 }
1502}
1503
1504impl Statsig {
1509 pub fn get_layer(&self, user: &StatsigUser, layer_name: &str) -> Layer {
1510 self.get_layer_with_options(user, layer_name, LayerEvaluationOptions::default())
1511 }
1512
1513 pub fn get_layer_with_options(
1514 &self,
1515 user: &StatsigUser,
1516 layer_name: &str,
1517 options: LayerEvaluationOptions,
1518 ) -> Layer {
1519 let user_internal = self.internalize_user(user);
1520 self.get_layer_impl(user_internal, layer_name, options)
1521 }
1522
1523 pub fn manually_log_layer_parameter_exposure(
1524 &self,
1525 user: &StatsigUser,
1526 layer_name: &str,
1527 parameter_name: String,
1528 ) {
1529 let user_internal = self.internalize_user(user);
1530 let layer =
1531 self.get_layer_impl(user_internal, layer_name, LayerEvaluationOptions::default());
1532
1533 self.event_logger
1534 .enqueue(EnqueueLayerParamExpoOp::LayerOwned(
1535 layer,
1536 parameter_name,
1537 ExposureTrigger::Manual,
1538 ));
1539 }
1540
1541 pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> {
1542 let data = read_lock_or_else!(self.spec_store.data, {
1543 log_error_to_statsig_and_console!(
1544 self.ops_stats.clone(),
1545 TAG,
1546 StatsigErr::LockFailure(
1547 "Failed to acquire read lock for spec store data".to_string()
1548 )
1549 );
1550 return vec![];
1551 });
1552
1553 let layer = data.values.layer_configs.get(layer_name);
1554 match layer {
1555 Some(layer) => match &layer.spec.fields_used {
1556 Some(fields) => fields.clone(),
1557 None => vec![],
1558 },
1559 None => vec![],
1560 }
1561 }
1562}
1563
1564impl Statsig {
1569 pub(crate) fn get_from_statsig_env(&self, key: &str) -> Option<DynamicValue> {
1570 if let Some(env) = &self.statsig_environment {
1571 return env.get(key).cloned();
1572 }
1573
1574 if let Ok(fallback_env) = self.fallback_environment.lock() {
1575 if let Some(env) = &*fallback_env {
1576 return env.get(key).cloned();
1577 }
1578 }
1579
1580 None
1581 }
1582
1583 pub(crate) fn get_value_from_global_custom_fields(&self, key: &str) -> Option<&DynamicValue> {
1584 if let Some(env) = &self.options.global_custom_fields {
1585 return env.get(key);
1586 }
1587
1588 None
1589 }
1590
1591 pub(crate) fn use_global_custom_fields<T>(
1592 &self,
1593 f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1594 ) -> T {
1595 f(self.options.global_custom_fields.as_ref())
1596 }
1597
1598 pub(crate) fn use_statsig_env<T>(
1599 &self,
1600 f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1601 ) -> T {
1602 if let Some(env) = &self.statsig_environment {
1603 return f(Some(env));
1604 }
1605
1606 if let Ok(fallback_env) = self.fallback_environment.lock() {
1607 if let Some(env) = &*fallback_env {
1608 return f(Some(env));
1609 }
1610 }
1611
1612 f(None)
1613 }
1614}
1615
1616impl Statsig {
1621 fn evaluate_spec<T>(
1622 &self,
1623 user_internal: &StatsigUserInternal,
1624 spec_name: &str,
1625 make_empty_result: impl FnOnce(EvaluationDetails) -> T,
1626 make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
1627 spec_type: &SpecType,
1628 ) -> T {
1629 let data = read_lock_or_else!(self.spec_store.data, {
1630 log_error_to_statsig_and_console!(
1631 &self.ops_stats,
1632 TAG,
1633 StatsigErr::LockFailure(
1634 "Failed to acquire read lock for spec store data".to_string()
1635 )
1636 );
1637 return make_empty_result(EvaluationDetails::unrecognized_no_data());
1638 });
1639 let app_id = data.values.app_id.as_ref();
1640 let mut context = EvaluatorContext::new(
1641 user_internal,
1642 &data,
1643 &self.hashing,
1644 app_id,
1645 self.override_adapter.as_ref(),
1646 );
1647
1648 match Evaluator::evaluate_with_details(&mut context, spec_name, spec_type) {
1649 Ok(eval_details) => make_result(context.result, eval_details),
1650 Err(e) => {
1651 log_error_to_statsig_and_console!(
1652 &self.ops_stats,
1653 TAG,
1654 StatsigErr::EvaluationError(e.to_string())
1655 );
1656 make_empty_result(EvaluationDetails::error(&e.to_string()))
1657 }
1658 }
1659 }
1660
1661 fn get_gate_evaluation(
1662 &self,
1663 user_internal: &StatsigUserInternal,
1664 gate_name: &str,
1665 ) -> (EvaluationDetails, Option<GateEvaluation>) {
1666 self.evaluate_spec(
1667 user_internal,
1668 gate_name,
1669 |eval_details| (eval_details, None),
1670 |mut result, eval_details| {
1671 let evaluation = result_to_gate_eval(gate_name, &mut result);
1672 (eval_details, Some(evaluation))
1673 },
1674 &SpecType::Gate,
1675 )
1676 }
1677
1678 fn get_dynamic_config_impl(
1679 &self,
1680 user_internal: &StatsigUserInternal,
1681 config_name: &str,
1682 ) -> DynamicConfig {
1683 self.evaluate_spec(
1684 user_internal,
1685 config_name,
1686 |eval_details| make_dynamic_config(config_name, None, eval_details),
1687 |mut result, eval_details| {
1688 let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
1689 make_dynamic_config(config_name, Some(evaluation), eval_details)
1690 },
1691 &SpecType::DynamicConfig,
1692 )
1693 }
1694
1695 fn get_experiment_impl(
1696 &self,
1697 user_internal: &StatsigUserInternal,
1698 experiment_name: &str,
1699 ) -> Experiment {
1700 self.evaluate_spec(
1701 user_internal,
1702 experiment_name,
1703 |eval_details| make_experiment(experiment_name, None, eval_details),
1704 |mut result, eval_details| {
1705 let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
1706 make_experiment(experiment_name, Some(evaluation), eval_details)
1707 },
1708 &SpecType::Experiment,
1709 )
1710 }
1711
1712 fn get_layer_impl(
1713 &self,
1714 user_internal: StatsigUserInternal,
1715 layer_name: &str,
1716 evaluation_options: LayerEvaluationOptions,
1717 ) -> Layer {
1718 let disable_exposure_logging = evaluation_options.disable_exposure_logging;
1719
1720 if disable_exposure_logging {
1721 self.event_logger.increment_non_exposure_checks(layer_name);
1722 }
1723
1724 let mut layer = self.evaluate_spec(
1725 &user_internal,
1726 layer_name,
1727 |eval_details| {
1728 make_layer(
1729 user_internal.to_loggable(),
1730 layer_name,
1731 None,
1732 eval_details,
1733 None,
1734 disable_exposure_logging,
1735 )
1736 },
1737 |mut result, eval_details| {
1738 let evaluation = result_to_layer_eval(layer_name, &mut result);
1739 let event_logger_ptr = Arc::downgrade(&self.event_logger);
1740
1741 make_layer(
1742 user_internal.to_loggable(),
1743 layer_name,
1744 Some(evaluation),
1745 eval_details,
1746 Some(event_logger_ptr),
1747 disable_exposure_logging,
1748 )
1749 },
1750 &SpecType::Layer,
1751 );
1752 if let Some(persisted_layer) = self.persistent_values_manager.as_ref().and_then(|p| {
1753 let event_logger_ptr = Arc::downgrade(&self.event_logger);
1754 p.try_apply_sticky_value_to_layer(
1755 &user_internal,
1756 &evaluation_options,
1757 &layer,
1758 Some(event_logger_ptr),
1759 disable_exposure_logging,
1760 )
1761 }) {
1762 layer = persisted_layer
1763 }
1764 layer
1765 }
1766
1767 fn internalize_user<'s, 'u>(&'s self, user: &'u StatsigUser) -> StatsigUserInternal<'s, 'u> {
1768 StatsigUserInternal::new(user, Some(self))
1769 }
1770
1771 fn set_default_environment_from_server(&self) {
1772 let data = read_lock_or_else!(self.spec_store.data, {
1773 return;
1774 });
1775
1776 if let Some(default_env) = data.values.default_environment.as_ref() {
1777 let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
1778
1779 if let Ok(mut fallback_env) = self.fallback_environment.lock() {
1780 *fallback_env = Some(env_map);
1781 }
1782 }
1783 }
1784
1785 fn log_init_finish(
1786 &self,
1787 success: bool,
1788 error_message: &Option<String>,
1789 duration: &f64,
1790 specs_info: &SpecsInfo,
1791 ) {
1792 let is_store_populated = specs_info.source != SpecsSource::NoValues;
1793 let source_str = specs_info.source.to_string();
1794
1795 let event = ObservabilityEvent::new_event(
1796 MetricType::Dist,
1797 "initialization".to_string(),
1798 *duration,
1799 Some(HashMap::from([
1800 ("success".to_owned(), success.to_string()),
1801 ("source".to_owned(), source_str.clone()),
1802 ("store_populated".to_owned(), is_store_populated.to_string()),
1803 (
1804 "spec_source_api".to_owned(),
1805 specs_info.source_api.clone().unwrap_or_default(),
1806 ),
1807 ])),
1808 );
1809
1810 self.ops_stats.log(event);
1811 self.ops_stats.add_marker(
1812 {
1813 let marker = Marker::new(KeyType::Overall, ActionType::End, None)
1814 .with_is_success(success)
1815 .with_config_spec_ready(specs_info.source != SpecsSource::NoValues)
1816 .with_source(source_str);
1817
1818 if let Some(msg) = &error_message {
1819 marker.with_message(msg.to_string())
1820 } else {
1821 marker
1822 }
1823 },
1824 Some(ContextType::Initialize),
1825 );
1826 self.ops_stats
1827 .enqueue_diagnostics_event(None, Some(ContextType::Initialize));
1828 }
1829}
1830
1831fn initialize_event_logging_adapter(
1832 sdk_key: &str,
1833 options: &StatsigOptions,
1834) -> Arc<dyn EventLoggingAdapter> {
1835 options
1836 .event_logging_adapter
1837 .clone()
1838 .unwrap_or_else(|| Arc::new(StatsigHttpEventLoggingAdapter::new(sdk_key, Some(options))))
1839}
1840
1841fn initialize_specs_adapter(
1842 sdk_key: &str,
1843 options: &StatsigOptions,
1844 hashing: &HashUtil,
1845) -> Arc<dyn SpecsAdapter> {
1846 if let Some(adapter) = options.specs_adapter.clone() {
1847 log_d!(TAG, "Using provided SpecsAdapter: {}", sdk_key);
1848 return adapter;
1849 }
1850
1851 if let Some(adapter_config) = options.spec_adapters_config.clone() {
1852 return Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
1853 sdk_key,
1854 adapter_config,
1855 options,
1856 hashing,
1857 ));
1858 }
1859
1860 if let Some(data_adapter) = options.data_store.clone() {
1861 return Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
1862 sdk_key,
1863 data_adapter,
1864 options,
1865 hashing,
1866 ));
1867 }
1868
1869 Arc::new(StatsigHttpSpecsAdapter::new(sdk_key, Some(options), None))
1870}
1871
1872fn initialize_id_lists_adapter(
1873 sdk_key: &str,
1874 options: &StatsigOptions,
1875) -> Option<Arc<dyn IdListsAdapter>> {
1876 if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
1877 return Some(id_lists_adapter);
1878 }
1879
1880 if options.enable_id_lists.unwrap_or(false) {
1881 return Some(Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options)));
1882 }
1883
1884 None
1885}
1886
1887fn setup_ops_stats(
1888 sdk_key: &str,
1889 statsig_runtime: Arc<StatsigRuntime>,
1890 error_observer: &Arc<dyn OpsStatsEventObserver>,
1891 diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
1892 external_observer: &Option<Weak<dyn ObservabilityClient>>,
1893) -> Arc<OpsStatsForInstance> {
1894 let ops_stat = OPS_STATS.get_for_instance(sdk_key);
1895 ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
1896 ops_stat.subscribe(
1897 statsig_runtime.clone(),
1898 Arc::downgrade(diagnostics_observer),
1899 );
1900
1901 if let Some(ob_client) = external_observer {
1902 if let Some(client) = ob_client.upgrade() {
1903 client.init();
1904 let as_observer = client.to_ops_stats_event_observer();
1905 ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
1906 }
1907 }
1908
1909 ops_stat
1910}