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