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