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