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