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,
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.event_logger
642 .increment_non_exposure_checks_count(parameter_store_name.to_string());
643 let data = read_lock_or_else!(self.spec_store.data, {
644 log_error_to_statsig_and_console!(
645 self.ops_stats.clone(),
646 TAG,
647 "Failed to acquire read lock for spec store data"
648 );
649 return ParameterStore {
650 name: parameter_store_name.to_string(),
651 parameters: HashMap::new(),
652 details: EvaluationDetails::unrecognized_no_data(),
653 _statsig_ref: self,
654 };
655 });
656
657 let stores = &data.values.param_stores;
658 let store = match stores {
659 Some(stores) => stores.get(parameter_store_name),
660 None => {
661 return ParameterStore {
662 name: parameter_store_name.to_string(),
663 parameters: HashMap::new(),
664 details: EvaluationDetails::unrecognized(&data),
665 _statsig_ref: self,
666 };
667 }
668 };
669 match store {
670 Some(store) => ParameterStore {
671 name: parameter_store_name.to_string(),
672 parameters: store.parameters.clone(),
673 details: EvaluationDetails::recognized(&data, &EvaluatorResult::default()),
674 _statsig_ref: self,
675 },
676 None => ParameterStore {
677 name: parameter_store_name.to_string(),
678 parameters: HashMap::new(),
679 details: EvaluationDetails::unrecognized(&data),
680 _statsig_ref: self,
681 },
682 }
683 }
684}
685
686impl Statsig {
691 pub fn get_cmab_ranked_groups(
692 &self,
693 user: &StatsigUser,
694 cmab_name: &str,
695 ) -> Vec<CMABRankedGroup> {
696 self.event_logger
697 .increment_non_exposure_checks_count(cmab_name.to_string());
698 let data = read_lock_or_else!(self.spec_store.data, {
699 log_error_to_statsig_and_console!(
700 self.ops_stats.clone(),
701 TAG,
702 "Failed to acquire read lock for spec store data"
703 );
704 return vec![];
705 });
706 let user_internal = self.internalize_user(user);
707 get_cmab_ranked_list(
708 &mut EvaluatorContext::new(
709 &user_internal,
710 &data,
711 &self.hashing,
712 &data.values.app_id.as_ref(),
713 &self.override_adapter,
714 ),
715 cmab_name,
716 )
717 }
718
719 pub fn log_cmab_exposure_for_group(
720 &self,
721 user: &StatsigUser,
722 cmab_name: &str,
723 group_id: String,
724 ) {
725 let user_internal = self.internalize_user(user);
726 let experiment = self.get_experiment_impl(&user_internal, cmab_name);
727 let sampling_info = match experiment.__evaluation {
728 Some(ref eval) => eval.base.sampling_info.clone(),
729 None => None,
730 };
731 let base_eval = BaseEvaluation {
732 name: cmab_name.to_string(),
733 rule_id: group_id.clone(),
734 secondary_exposures: match experiment.__evaluation {
735 Some(ref eval) => eval.base.secondary_exposures.clone(),
736 None => vec![],
737 },
738 sampling_info,
739 };
740 let experiment_eval = ExperimentEvaluation {
741 base: base_eval.clone(),
742 id_type: experiment.id_type.clone(),
743 value: HashMap::new(),
744 group: group_id,
745 is_device_based: false,
746 is_in_layer: false,
747 explicit_parameters: None,
748 group_name: None,
749 is_experiment_active: Some(true),
750 is_user_in_experiment: Some(true),
751 };
752
753 let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
754 &user_internal,
755 Some(&AnyEvaluation::from(&experiment_eval)),
756 None,
757 );
758
759 if !sampling_details.should_send_exposure {
760 return;
761 }
762
763 self.event_logger
764 .enqueue(QueuedEventPayload::ConfigExposure(ConfigExposure {
765 user: user_internal,
766 evaluation: Some(base_eval),
767 evaluation_details: experiment.details.clone(),
768 config_name: cmab_name.to_string(),
769 rule_passed: None,
770 version: experiment.__version,
771 is_manual_exposure: true,
772 sampling_details,
773 override_config_name: experiment.__override_config_name.clone(),
774 }));
775 }
776}
777
778impl Statsig {
783 pub fn shared() -> Arc<Statsig> {
784 let lock = match SHARED_INSTANCE.lock() {
785 Ok(lock) => lock,
786 Err(e) => {
787 log_e!(TAG, "Statsig::shared() mutex error: {}", e);
788 return Arc::new(Statsig::new(ERROR_SDK_KEY, None));
789 }
790 };
791
792 match lock.as_ref() {
793 Some(statsig) => statsig.clone(),
794 None => {
795 log_e!(
796 TAG,
797 "Statsig::shared() called, but no instance has been set with Statsig::new_shared(...)"
798 );
799 Arc::new(Statsig::new(ERROR_SDK_KEY, None))
800 }
801 }
802 }
803
804 pub fn new_shared(
805 sdk_key: &str,
806 options: Option<Arc<StatsigOptions>>,
807 ) -> Result<Arc<Statsig>, StatsigErr> {
808 match SHARED_INSTANCE.lock() {
809 Ok(mut lock) => {
810 if lock.is_some() {
811 let message = "Statsig shared instance already exists. Call Statsig::remove_shared() before creating a new instance.";
812 log_e!(TAG, "{}", message);
813 return Err(StatsigErr::SharedInstanceFailure(message.to_string()));
814 }
815
816 let statsig = Arc::new(Statsig::new(sdk_key, options));
817 *lock = Some(statsig.clone());
818 Ok(statsig)
819 }
820 Err(e) => {
821 let message = format!("Statsig::new_shared() mutex error: {}", e);
822 log_e!(TAG, "{}", message);
823 Err(StatsigErr::SharedInstanceFailure(message))
824 }
825 }
826 }
827
828 pub fn remove_shared() {
829 match SHARED_INSTANCE.lock() {
830 Ok(mut lock) => {
831 *lock = None;
832 }
833 Err(e) => {
834 log_e!(TAG, "Statsig::remove_shared() mutex error: {}", e);
835 }
836 }
837 }
838
839 pub fn has_shared_instance() -> bool {
840 match SHARED_INSTANCE.lock() {
841 Ok(lock) => lock.is_some(),
842 Err(_) => false,
843 }
844 }
845}
846
847impl Statsig {
852 pub fn check_gate(&self, user: &StatsigUser, gate_name: &str) -> bool {
853 self.check_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
854 }
855
856 pub fn check_gate_with_options(
857 &self,
858 user: &StatsigUser,
859 gate_name: &str,
860 options: FeatureGateEvaluationOptions,
861 ) -> bool {
862 self.get_feature_gate_with_options(user, gate_name, options)
863 .value
864 }
865
866 pub fn get_feature_gate(&self, user: &StatsigUser, gate_name: &str) -> FeatureGate {
867 self.get_feature_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
868 }
869
870 pub fn get_feature_gate_with_options(
871 &self,
872 user: &StatsigUser,
873 gate_name: &str,
874 options: FeatureGateEvaluationOptions,
875 ) -> FeatureGate {
876 log_d!(TAG, "Get Feature Gate {}", gate_name);
877
878 let user_internal = self.internalize_user(user);
879
880 let disable_exposure_logging = options.disable_exposure_logging;
881 let gate = self.get_feature_gate_impl(&user_internal, gate_name);
882
883 if disable_exposure_logging {
884 log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
885 self.event_logger
886 .increment_non_exposure_checks_count(gate_name.to_string());
887 } else {
888 self.log_gate_exposure(user_internal, gate_name, &gate, false);
889 }
890
891 gate
892 }
893
894 pub fn manually_log_gate_exposure(&self, user: &StatsigUser, gate_name: &str) {
895 let user_internal = self.internalize_user(user);
896 let gate = self.get_feature_gate_impl(&user_internal, gate_name);
897 self.log_gate_exposure(user_internal, gate_name, &gate, true);
898 }
899
900 pub fn get_fields_needed_for_gate(&self, gate_name: &str) -> Vec<String> {
901 let data = read_lock_or_else!(self.spec_store.data, {
902 log_error_to_statsig_and_console!(
903 self.ops_stats.clone(),
904 TAG,
905 "Failed to acquire read lock for spec store data"
906 );
907 return vec![];
908 });
909
910 let gate = data.values.feature_gates.get(gate_name);
911 match gate {
912 Some(gate) => match &gate.fields_used {
913 Some(fields) => fields.clone(),
914 None => vec![],
915 },
916 None => vec![],
917 }
918 }
919
920 pub fn override_gate(
921 &self,
922 gate_name: &str,
923 value: bool,
924 _adapter: Option<&OverrideAdapterType>,
925 ) {
926 if let Some(adapter) = &self.override_adapter {
927 adapter.override_gate(gate_name, value);
928 }
929 }
930
931 pub fn override_dynamic_config(
932 &self,
933 config_name: &str,
934 value: HashMap<String, serde_json::Value>,
935 _adapter: Option<&OverrideAdapterType>,
936 ) {
937 if let Some(adapter) = &self.override_adapter {
938 adapter.override_dynamic_config(config_name, value);
939 }
940 }
941
942 pub fn override_layer(
943 &self,
944 layer_name: &str,
945 value: HashMap<String, serde_json::Value>,
946 _adapter: Option<&OverrideAdapterType>,
947 ) {
948 if let Some(adapter) = &self.override_adapter {
949 adapter.override_layer(layer_name, value);
950 }
951 }
952
953 pub fn override_experiment(
954 &self,
955 experiment_name: &str,
956 value: HashMap<String, serde_json::Value>,
957 _adapter: Option<&OverrideAdapterType>,
958 ) {
959 if let Some(adapter) = &self.override_adapter {
960 adapter.override_experiment(experiment_name, value);
961 }
962 }
963
964 pub fn override_experiment_by_group_name(
965 &self,
966 experiment_name: &str,
967 group_name: &str,
968 _adapter: Option<&OverrideAdapterType>,
969 ) {
970 if let Some(adapter) = &self.override_adapter {
971 adapter.override_experiment_by_group_name(experiment_name, group_name);
972 }
973 }
974}
975
976impl Statsig {
981 pub fn get_dynamic_config(
982 &self,
983 user: &StatsigUser,
984 dynamic_config_name: &str,
985 ) -> DynamicConfig {
986 self.get_dynamic_config_with_options(
987 user,
988 dynamic_config_name,
989 DynamicConfigEvaluationOptions::default(),
990 )
991 }
992
993 pub fn get_dynamic_config_with_options(
994 &self,
995 user: &StatsigUser,
996 dynamic_config_name: &str,
997 options: DynamicConfigEvaluationOptions,
998 ) -> DynamicConfig {
999 let user_internal = self.internalize_user(user);
1000 let disable_exposure_logging = options.disable_exposure_logging;
1001 let config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1002
1003 if disable_exposure_logging {
1004 log_d!(
1005 TAG,
1006 "Exposure logging is disabled for Dynamic Config {}",
1007 dynamic_config_name
1008 );
1009 self.event_logger
1010 .increment_non_exposure_checks_count(dynamic_config_name.to_string());
1011 } else {
1012 self.log_dynamic_config_exposure(user_internal, dynamic_config_name, &config, false);
1013 }
1014
1015 config
1016 }
1017
1018 pub fn manually_log_dynamic_config_exposure(
1019 &self,
1020 user: &StatsigUser,
1021 dynamic_config_name: &str,
1022 ) {
1023 let user_internal = self.internalize_user(user);
1024 let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1025 self.log_dynamic_config_exposure(user_internal, dynamic_config_name, &dynamic_config, true);
1026 }
1027
1028 pub fn get_fields_needed_for_dynamic_config(&self, config_name: &str) -> Vec<String> {
1029 let data = read_lock_or_else!(self.spec_store.data, {
1030 log_error_to_statsig_and_console!(
1031 self.ops_stats.clone(),
1032 TAG,
1033 "Failed to acquire read lock for spec store data"
1034 );
1035 return vec![];
1036 });
1037
1038 let config = data.values.dynamic_configs.get(config_name);
1039 match config {
1040 Some(config) => match &config.fields_used {
1041 Some(fields) => fields.clone(),
1042 None => vec![],
1043 },
1044 None => vec![],
1045 }
1046 }
1047}
1048
1049impl Statsig {
1054 pub fn get_experiment(&self, user: &StatsigUser, experiment_name: &str) -> Experiment {
1055 self.get_experiment_with_options(
1056 user,
1057 experiment_name,
1058 ExperimentEvaluationOptions::default(),
1059 )
1060 }
1061
1062 pub fn get_experiment_with_options(
1063 &self,
1064 user: &StatsigUser,
1065 experiment_name: &str,
1066 options: ExperimentEvaluationOptions,
1067 ) -> Experiment {
1068 let user_internal = self.internalize_user(user);
1069 let disable_exposure_logging = options.disable_exposure_logging;
1070 let experiment = self.get_experiment_impl(&user_internal, experiment_name);
1071
1072 if disable_exposure_logging {
1073 log_d!(
1074 TAG,
1075 "Exposure logging is disabled for experiment {}",
1076 experiment_name
1077 );
1078 self.event_logger
1079 .increment_non_exposure_checks_count(experiment_name.to_string());
1080 } else {
1081 self.log_experiment_exposure(user_internal, experiment_name, &experiment, false);
1082 }
1083
1084 experiment
1085 }
1086
1087 pub fn manually_log_experiment_exposure(&self, user: &StatsigUser, experiment_name: &str) {
1088 let user_internal = self.internalize_user(user);
1089 let experiment = self.get_experiment_impl(&user_internal, experiment_name);
1090 self.log_experiment_exposure(user_internal, experiment_name, &experiment, true);
1091 }
1092
1093 pub fn get_fields_needed_for_experiment(&self, experiment_name: &str) -> Vec<String> {
1094 let data = read_lock_or_else!(self.spec_store.data, {
1095 log_error_to_statsig_and_console!(
1096 self.ops_stats.clone(),
1097 TAG,
1098 "Failed to acquire read lock for spec store data"
1099 );
1100 return vec![];
1101 });
1102
1103 let config = data.values.dynamic_configs.get(experiment_name);
1104 match config {
1105 Some(config) => match &config.fields_used {
1106 Some(fields) => fields.clone(),
1107 None => vec![],
1108 },
1109 None => vec![],
1110 }
1111 }
1112}
1113
1114impl Statsig {
1119 pub fn get_layer(&self, user: &StatsigUser, layer_name: &str) -> Layer {
1120 self.get_layer_with_options(user, layer_name, LayerEvaluationOptions::default())
1121 }
1122
1123 pub fn get_layer_with_options(
1124 &self,
1125 user: &StatsigUser,
1126 layer_name: &str,
1127 options: LayerEvaluationOptions,
1128 ) -> Layer {
1129 let user_internal = self.internalize_user(user);
1130 self.get_layer_impl(&user_internal, layer_name, options)
1131 }
1132
1133 pub fn manually_log_layer_parameter_exposure(
1134 &self,
1135 user: &StatsigUser,
1136 layer_name: &str,
1137 parameter_name: String,
1138 ) {
1139 let user_internal = self.internalize_user(user);
1140 let layer = self.get_layer_impl(
1141 &user_internal,
1142 layer_name,
1143 LayerEvaluationOptions::default(),
1144 );
1145
1146 let layer_eval = layer.__evaluation.as_ref();
1147
1148 let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1149 &layer.__user,
1150 layer_eval.map(AnyEvaluation::from).as_ref(),
1151 Some(parameter_name.as_str()),
1152 );
1153
1154 if !sampling_details.should_send_exposure {
1155 return;
1156 }
1157
1158 self.event_logger
1159 .enqueue(QueuedEventPayload::LayerExposure(LayerExposure {
1160 user: layer.__user,
1161 parameter_name,
1162 evaluation: layer.__evaluation,
1163 layer_name: layer.name,
1164 evaluation_details: layer.details,
1165 version: layer.__version,
1166 is_manual_exposure: true,
1167 sampling_details,
1168 override_config_name: layer.__override_config_name.clone(),
1169 }));
1170 }
1171
1172 pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> {
1173 let data = read_lock_or_else!(self.spec_store.data, {
1174 log_error_to_statsig_and_console!(
1175 self.ops_stats.clone(),
1176 TAG,
1177 "Failed to acquire read lock for spec store data"
1178 );
1179 return vec![];
1180 });
1181
1182 let layer = data.values.layer_configs.get(layer_name);
1183 match layer {
1184 Some(layer) => match &layer.fields_used {
1185 Some(fields) => fields.clone(),
1186 None => vec![],
1187 },
1188 None => vec![],
1189 }
1190 }
1191}
1192
1193impl Statsig {
1198 fn evaluate_spec<T>(
1199 &self,
1200 user_internal: &StatsigUserInternal,
1201 spec_name: &str,
1202 make_empty_result: impl FnOnce(EvaluationDetails) -> T,
1203 make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
1204 spec_type: &SpecType,
1205 ) -> T {
1206 let data = read_lock_or_else!(self.spec_store.data, {
1207 log_error_to_statsig_and_console!(
1208 &self.ops_stats,
1209 TAG,
1210 "Failed to acquire read lock for spec store data"
1211 );
1212 return make_empty_result(EvaluationDetails::unrecognized_no_data());
1213 });
1214 let app_id = data.values.app_id.as_ref();
1215 let mut context = EvaluatorContext::new(
1216 user_internal,
1217 &data,
1218 &self.hashing,
1219 &app_id,
1220 &self.override_adapter,
1221 );
1222
1223 match Evaluator::evaluate_with_details(&mut context, spec_name, spec_type) {
1224 Ok(eval_details) => make_result(context.result, eval_details),
1225 Err(e) => {
1226 log_error_to_statsig_and_console!(&self.ops_stats, TAG, "Error evaluating: {}", e);
1227 make_empty_result(EvaluationDetails::error(&e.to_string()))
1228 }
1229 }
1230 }
1231
1232 fn get_feature_gate_impl(
1233 &self,
1234 user_internal: &StatsigUserInternal,
1235 gate_name: &str,
1236 ) -> FeatureGate {
1237 self.evaluate_spec(
1238 user_internal,
1239 gate_name,
1240 |eval_details| make_feature_gate(gate_name, None, eval_details, None, None),
1241 |mut result, eval_details| {
1242 let evaluation = result_to_gate_eval(gate_name, &mut result);
1243 make_feature_gate(
1244 gate_name,
1245 Some(evaluation),
1246 eval_details,
1247 result.version,
1248 result.override_config_name,
1249 )
1250 },
1251 &SpecType::Gate,
1252 )
1253 }
1254
1255 fn get_dynamic_config_impl(
1256 &self,
1257 user_internal: &StatsigUserInternal,
1258 config_name: &str,
1259 ) -> DynamicConfig {
1260 self.evaluate_spec(
1261 user_internal,
1262 config_name,
1263 |eval_details| make_dynamic_config(config_name, None, eval_details, None, None),
1264 |mut result, eval_details| {
1265 let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
1266 make_dynamic_config(
1267 config_name,
1268 Some(evaluation),
1269 eval_details,
1270 result.version,
1271 result.override_config_name,
1272 )
1273 },
1274 &SpecType::DynamicConfig,
1275 )
1276 }
1277
1278 fn get_experiment_impl(
1279 &self,
1280 user_internal: &StatsigUserInternal,
1281 experiment_name: &str,
1282 ) -> Experiment {
1283 self.evaluate_spec(
1284 user_internal,
1285 experiment_name,
1286 |eval_details| make_experiment(experiment_name, None, eval_details, None, None),
1287 |mut result, eval_details| {
1288 let data = read_lock_or_else!(self.spec_store.data, {
1289 let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
1290 return make_experiment(
1291 experiment_name,
1292 Some(evaluation),
1293 eval_details,
1294 result.version,
1295 result.override_config_name,
1296 );
1297 });
1298 let evaluation = result_to_experiment_eval(
1299 experiment_name,
1300 data.values.dynamic_configs.get(experiment_name),
1301 &mut result,
1302 );
1303 make_experiment(
1304 experiment_name,
1305 Some(evaluation),
1306 eval_details,
1307 result.version,
1308 result.override_config_name,
1309 )
1310 },
1311 &SpecType::Experiment,
1312 )
1313 }
1314
1315 fn get_layer_impl(
1316 &self,
1317 user_internal: &StatsigUserInternal,
1318 layer_name: &str,
1319 evaluation_options: LayerEvaluationOptions,
1320 ) -> Layer {
1321 let disable_exposure_logging = evaluation_options.disable_exposure_logging;
1322 if disable_exposure_logging {
1323 self.event_logger
1324 .increment_non_exposure_checks_count(layer_name.to_string());
1325 }
1326
1327 self.evaluate_spec(
1328 user_internal,
1329 layer_name,
1330 |eval_details| {
1331 make_layer(
1332 user_internal,
1333 layer_name,
1334 None,
1335 eval_details,
1336 None,
1337 None,
1338 disable_exposure_logging,
1339 None,
1340 None,
1341 )
1342 },
1343 |mut result, eval_details| {
1344 let evaluation = result_to_layer_eval(layer_name, &mut result);
1345 let event_logger_ptr = Arc::downgrade(&self.event_logger);
1346 let sampling_processor_ptr = Arc::downgrade(&self.sampling_processor);
1347
1348 make_layer(
1349 user_internal,
1350 layer_name,
1351 Some(evaluation),
1352 eval_details,
1353 Some(event_logger_ptr),
1354 result.version,
1355 disable_exposure_logging,
1356 Some(sampling_processor_ptr),
1357 result.override_config_name,
1358 )
1359 },
1360 &SpecType::Layer,
1361 )
1362 }
1363
1364 fn log_gate_exposure(
1365 &self,
1366 user_internal: StatsigUserInternal,
1367 gate_name: &str,
1368 gate: &FeatureGate,
1369 is_manual: bool,
1370 ) {
1371 let gate_eval = gate.__evaluation.as_ref();
1372 let secondary_exposures = gate_eval.map(|eval| &eval.base.secondary_exposures);
1373
1374 let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1375 &user_internal,
1376 gate_eval.map(AnyEvaluation::from).as_ref(),
1377 None,
1378 );
1379
1380 if !sampling_details.should_send_exposure {
1381 return;
1382 }
1383
1384 self.event_logger
1385 .enqueue(QueuedEventPayload::GateExposure(GateExposure {
1386 user: user_internal,
1387 gate_name: gate_name.to_string(),
1388 value: gate.value,
1389 rule_id: Some(gate.rule_id.clone()),
1390 secondary_exposures: secondary_exposures.cloned(),
1391 evaluation_details: gate.details.clone(),
1392 version: gate.__version,
1393 is_manual_exposure: is_manual,
1394 sampling_details,
1395 override_config_name: gate.__override_config_name.clone(),
1396 }));
1397 }
1398
1399 fn log_dynamic_config_exposure(
1400 &self,
1401 user_internal: StatsigUserInternal,
1402 dynamic_config_name: &str,
1403 dynamic_config: &DynamicConfig,
1404 is_manual: bool,
1405 ) {
1406 let config_eval = dynamic_config.__evaluation.as_ref();
1407 let base_eval = config_eval.map(|eval| eval.base.clone());
1408
1409 let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1410 &user_internal,
1411 config_eval.map(AnyEvaluation::from).as_ref(),
1412 None,
1413 );
1414
1415 if !sampling_details.should_send_exposure {
1416 return;
1417 }
1418
1419 self.event_logger
1420 .enqueue(QueuedEventPayload::ConfigExposure(ConfigExposure {
1421 user: user_internal,
1422 evaluation: base_eval,
1423 evaluation_details: dynamic_config.details.clone(),
1424 config_name: dynamic_config_name.to_string(),
1425 rule_passed: dynamic_config.__evaluation.as_ref().map(|eval| eval.passed),
1426 version: dynamic_config.__version,
1427 is_manual_exposure: is_manual,
1428 sampling_details,
1429 override_config_name: dynamic_config.__override_config_name.clone(),
1430 }));
1431 }
1432
1433 fn log_experiment_exposure(
1434 &self,
1435 user_internal: StatsigUserInternal,
1436 experiment_name: &str,
1437 experiment: &Experiment,
1438 is_manual: bool,
1439 ) {
1440 let experiment_eval = experiment.__evaluation.as_ref();
1441 let base_eval = experiment_eval.map(|eval| eval.base.clone());
1442
1443 let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1444 &user_internal,
1445 experiment_eval.map(AnyEvaluation::from).as_ref(),
1446 None,
1447 );
1448
1449 if !sampling_details.should_send_exposure {
1450 return;
1451 }
1452
1453 self.event_logger
1454 .enqueue(QueuedEventPayload::ConfigExposure(ConfigExposure {
1455 user: user_internal,
1456 evaluation: base_eval,
1457 evaluation_details: experiment.details.clone(),
1458 config_name: experiment_name.to_string(),
1459 rule_passed: None,
1460 version: experiment.__version,
1461 is_manual_exposure: is_manual,
1462 sampling_details,
1463 override_config_name: experiment.__override_config_name.clone(),
1464 }));
1465 }
1466
1467 fn internalize_user(&self, user: &StatsigUser) -> StatsigUserInternal {
1468 StatsigUserInternal::new(
1469 user,
1470 self.get_statsig_env(),
1471 self.options.global_custom_fields.clone(),
1472 )
1473 }
1474
1475 fn get_statsig_env(&self) -> Option<HashMap<String, DynamicValue>> {
1476 if let Some(env) = &self.statsig_environment {
1477 return Some(env.clone());
1478 }
1479
1480 if let Ok(fallback_env) = self.fallback_environment.lock() {
1481 if let Some(env) = &*fallback_env {
1482 return Some(env.clone());
1483 }
1484 }
1485
1486 None
1487 }
1488
1489 fn set_default_environment_from_server(&self) {
1490 let data = read_lock_or_else!(self.spec_store.data, {
1491 return;
1492 });
1493
1494 if let Some(default_env) = data.values.default_environment.as_ref() {
1495 let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
1496
1497 if let Ok(mut fallback_env) = self.fallback_environment.lock() {
1498 *fallback_env = Some(env_map);
1499 }
1500 }
1501 }
1502
1503 fn log_init_finish(
1504 &self,
1505 success: bool,
1506 error_message: &Option<String>,
1507 duration: &f64,
1508 specs_info: &SpecsInfo,
1509 ) {
1510 let is_store_populated = specs_info.source != SpecsSource::NoValues;
1511 let source_str = specs_info.source.to_string();
1512 self.ops_stats.log(ObservabilityEvent::new_event(
1513 MetricType::Dist,
1514 "initialization".to_string(),
1515 *duration,
1516 Some(HashMap::from([
1517 ("success".to_owned(), success.to_string()),
1518 ("source".to_owned(), source_str.clone()),
1519 ("store_populated".to_owned(), is_store_populated.to_string()),
1520 ])),
1521 ));
1522 self.ops_stats.add_marker(
1523 {
1524 let marker =
1525 Marker::new(KeyType::Overall, ActionType::End, Some(StepType::Process))
1526 .with_is_success(success)
1527 .with_config_spec_ready(specs_info.source != SpecsSource::NoValues)
1528 .with_source(source_str);
1529
1530 if let Some(msg) = &error_message {
1531 marker.with_message(msg.to_string())
1532 } else {
1533 marker
1534 }
1535 },
1536 Some(ContextType::Initialize),
1537 );
1538 self.ops_stats
1539 .enqueue_diagnostics_event(None, Some(ContextType::Initialize));
1540 }
1541}
1542
1543fn initialize_event_logging_adapter(
1544 sdk_key: &str,
1545 options: &StatsigOptions,
1546) -> Arc<dyn EventLoggingAdapter> {
1547 let adapter = options.event_logging_adapter.clone().unwrap_or_else(|| {
1548 Arc::new(StatsigHttpEventLoggingAdapter::new(
1549 sdk_key,
1550 options.log_event_url.as_ref(),
1551 options.disable_network,
1552 ))
1553 });
1554 adapter
1555}
1556
1557fn initialize_specs_adapter(
1558 sdk_key: &str,
1559 options: &StatsigOptions,
1560 hashing: &HashUtil,
1561) -> Arc<dyn SpecsAdapter> {
1562 if let Some(adapter) = options.specs_adapter.clone() {
1563 log_d!(TAG, "Using provided SpecsAdapter: {}", sdk_key);
1564 return adapter;
1565 }
1566
1567 if let Some(adapter_config) = options.spec_adapters_config.clone() {
1568 return Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
1569 sdk_key,
1570 adapter_config,
1571 options,
1572 hashing,
1573 ));
1574 }
1575
1576 if let Some(data_adapter) = options.data_store.clone() {
1577 return Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
1578 sdk_key,
1579 data_adapter,
1580 options,
1581 hashing,
1582 ));
1583 }
1584
1585 Arc::new(StatsigHttpSpecsAdapter::new(
1586 sdk_key,
1587 options.specs_url.as_ref(),
1588 options.fallback_to_statsig_api.unwrap_or(false),
1589 options.specs_sync_interval_ms,
1590 options.disable_network,
1591 ))
1592}
1593
1594fn initialize_id_lists_adapter(
1595 sdk_key: &str,
1596 options: &StatsigOptions,
1597) -> Option<Arc<dyn IdListsAdapter>> {
1598 if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
1599 return Some(id_lists_adapter);
1600 }
1601
1602 if options.enable_id_lists.unwrap_or(false) {
1603 return Some(Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options)));
1604 }
1605
1606 None
1607}
1608
1609fn setup_ops_stats(
1610 sdk_key: &str,
1611 options: &StatsigOptions,
1612 statsig_runtime: Arc<StatsigRuntime>,
1613 error_observer: &Arc<dyn OpsStatsEventObserver>,
1614 diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
1615 external_observer: &Option<Weak<dyn ObservabilityClient>>,
1616) -> Arc<OpsStatsForInstance> {
1617 initialize_simple_output_logger(&options.output_log_level);
1619
1620 let ops_stat = OPS_STATS.get_for_instance(sdk_key);
1621 ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
1622 ops_stat.subscribe(
1623 statsig_runtime.clone(),
1624 Arc::downgrade(diagnostics_observer),
1625 );
1626
1627 if let Some(ob_client) = external_observer {
1628 if let Some(client) = ob_client.upgrade() {
1629 client.init();
1630 let as_observer = client.to_ops_stats_event_observer();
1631 ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
1632 }
1633 }
1634
1635 ops_stat
1636}
1637
1638#[cfg(test)]
1639mod tests {
1640 use super::*;
1641 use crate::hashing::djb2;
1642 use crate::{
1643 evaluation::evaluation_types::AnyConfigEvaluation, output_logger::LogLevel,
1644 StatsigHttpIdListsAdapter,
1645 };
1646 use std::env;
1647
1648 impl Statsig {}
1649
1650 fn get_sdk_key() -> String {
1651 let key = env::var("test_api_key").expect("test_api_key environment variable not set");
1652 assert!(key.starts_with("secret-9IWf"));
1653 key
1654 }
1655
1656 #[tokio::test]
1657 async fn test_check_gate() {
1658 let user = StatsigUser {
1659 email: Some(dyn_value!("daniel@statsig.com")),
1660 ..StatsigUser::with_user_id("a-user".to_string())
1661 };
1662
1663 let statsig = Statsig::new(&get_sdk_key(), None);
1664 statsig.initialize().await.unwrap();
1665
1666 let gate_result = statsig.check_gate(&user, "test_50_50");
1667
1668 assert!(gate_result);
1669 }
1670
1671 #[tokio::test]
1672 async fn test_check_gate_id_list() {
1673 let user = StatsigUser {
1674 custom_ids: Some(HashMap::from([(
1675 "companyID".to_string(),
1676 dyn_value!("marcos_1"),
1677 )])),
1678 ..StatsigUser::with_user_id("marcos_1".to_string())
1679 };
1680
1681 let mut opts = StatsigOptions::new();
1682
1683 let adapter = Arc::new(StatsigHttpIdListsAdapter::new(&get_sdk_key(), &opts));
1684 opts.id_lists_adapter = Some(adapter);
1685
1686 let statsig = Statsig::new(&get_sdk_key(), Some(Arc::new(opts)));
1687 statsig.initialize().await.unwrap();
1688
1689 let gate_result = statsig.check_gate(&user, "test_id_list");
1690
1691 assert!(gate_result);
1692 }
1693
1694 #[tokio::test]
1695 async fn test_get_experiment() {
1696 let user = StatsigUser {
1697 email: Some(dyn_value!("daniel@statsig.com")),
1698 ..StatsigUser::with_user_id("a-user".to_string())
1699 };
1700
1701 let statsig = Statsig::new(&get_sdk_key(), None);
1702 statsig.initialize().await.unwrap();
1703
1704 let experiment = statsig.get_experiment(&user, "running_exp_in_unlayered_with_holdout");
1705 let _ = statsig.shutdown().await;
1706
1707 assert_ne!(experiment.value.len(), 0);
1708 }
1709
1710 #[tokio::test]
1711 async fn test_gcir() {
1712 let user = StatsigUser {
1713 email: Some(dyn_value!("daniel@statsig.com")),
1714 ..StatsigUser::with_user_id("a-user".to_string())
1715 };
1716 let opts = StatsigOptions {
1717 output_log_level: Some(LogLevel::Debug),
1718 ..StatsigOptions::new()
1719 };
1720
1721 let statsig = Statsig::new(&get_sdk_key(), Some(Arc::new(opts)));
1722 statsig.initialize().await.unwrap();
1723
1724 let response = statsig.get_client_init_response(&user);
1725 let _ = statsig.shutdown().await;
1726
1727 let gates = response.feature_gates;
1728 assert_eq!(gates.len(), 69);
1729
1730 let configs = response.dynamic_configs.len();
1731 assert_eq!(configs, 62);
1732
1733 let a_config_opt = response.dynamic_configs.get(&djb2("big_number"));
1734 let a_config = match a_config_opt {
1735 Some(v) => match v {
1736 AnyConfigEvaluation::DynamicConfig(config) => &config.value,
1737 AnyConfigEvaluation::Experiment(exp) => &exp.value,
1738 },
1739 None => panic!("Should have values"),
1740 };
1741
1742 assert!(!a_config.is_empty());
1743 }
1744}