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