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