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