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