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