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