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