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