1use crate::evaluation::cmab_evaluator::{get_cmab_ranked_list, CMABRankedGroup};
2use crate::evaluation::country_lookup::CountryLookup;
3use crate::evaluation::dynamic_value::DynamicValue;
4use crate::evaluation::evaluation_details::EvaluationDetails;
5use crate::evaluation::evaluation_types::GateEvaluation;
6use crate::evaluation::evaluator::{Evaluator, Recognition, SpecType};
7use crate::evaluation::evaluator_context::{EvaluatorContext, IdListResolution};
8use crate::evaluation::evaluator_result::{
9 result_to_dynamic_config_eval, result_to_experiment_eval, result_to_gate_eval,
10 result_to_layer_eval, EvaluatorResult,
11};
12use crate::evaluation::user_agent_parsing::{ParsedUserAgentValue, UserAgentParser};
13use crate::event_logging::event_logger::{EventLogger, ExposureTrigger};
14use crate::event_logging::event_queue::queued_config_expo::EnqueueConfigExpoOp;
15use crate::event_logging::event_queue::queued_experiment_expo::EnqueueExperimentExpoOp;
16use crate::event_logging::event_queue::queued_gate_expo::EnqueueGateExpoOp;
17use crate::event_logging::event_queue::queued_layer_param_expo::EnqueueLayerParamExpoOp;
18use crate::event_logging::event_queue::queued_passthrough::EnqueuePassthroughOp;
19use crate::event_logging::statsig_event_internal::{StatsigEventInternal, StatsigLogLineLevel};
20use crate::event_logging_adapter::EventLoggingAdapter;
21use crate::event_logging_adapter::StatsigHttpEventLoggingAdapter;
22use crate::gcir::gcir_formatter::GCIRFormatter;
23use crate::gcir::target_app_id_utils::select_app_id_for_gcir;
24use crate::hashing::HashUtil;
25use crate::initialize_evaluations_response::InitializeEvaluationsResponse;
26use crate::initialize_response::InitializeResponse;
27use crate::networking::NetworkError;
28use crate::observability::diagnostics_observer::DiagnosticsObserver;
29use crate::observability::observability_client_adapter::{MetricType, ObservabilityEvent};
30use crate::observability::ops_stats::{OpsStatsForInstance, OPS_STATS};
31use crate::observability::sdk_errors_observer::{ErrorBoundaryEvent, SDKErrorsObserver};
32use crate::output_logger::{initialize_output_logger, shutdown_output_logger};
33use crate::persistent_storage::persistent_values_manager::PersistentValuesManager;
34use crate::sdk_diagnostics::diagnostics::{ContextType, Diagnostics};
35use crate::sdk_diagnostics::marker::{ActionType, KeyType, Marker};
36use crate::sdk_event_emitter::SdkEventEmitter;
37use crate::spec_store::{SpecStore, SpecStoreData};
38use crate::specs_adapter::{StatsigCustomizedSpecsAdapter, StatsigHttpSpecsAdapter};
39use crate::statsig_err::StatsigErr;
40use crate::statsig_metadata::StatsigMetadata;
41use crate::statsig_options::StatsigOptions;
42use crate::statsig_runtime::StatsigRuntime;
43use crate::statsig_type_factories::{
44 make_dynamic_config, make_experiment, make_feature_gate, make_layer,
45};
46use crate::statsig_types::{DynamicConfig, Experiment, FeatureGate, Layer, ParameterStore};
47use crate::user::StatsigUserInternal;
48use crate::{
49 dyn_value, log_d, log_e, log_w, read_lock_or_else, ClientInitResponseOptions,
50 GCIRResponseFormat, IdListsAdapter, ObservabilityClient, OpsStatsEventObserver,
51 OverrideAdapter, SpecsAdapter, SpecsInfo, SpecsSource, SpecsUpdateListener,
52 StatsigHttpIdListsAdapter, StatsigLocalOverrideAdapter, StatsigUser,
53};
54use crate::{
55 log_error_to_statsig_and_console,
56 statsig_core_api_options::{
57 DynamicConfigEvaluationOptions, ExperimentEvaluationOptions, FeatureGateEvaluationOptions,
58 LayerEvaluationOptions, ParameterStoreEvaluationOptions,
59 },
60};
61use chrono::Utc;
62use parking_lot::Mutex;
63use serde::de::DeserializeOwned;
64use serde::Serialize;
65use serde_json::json;
66use serde_json::Value;
67use std::borrow::Cow;
68use std::collections::HashMap;
69use std::sync::atomic::{AtomicBool, Ordering};
70use std::sync::{Arc, Weak};
71use std::time::{Duration, Instant};
72use tokio::time::sleep;
73use tokio::try_join;
74
75const TAG: &str = stringify!(Statsig);
76const ERROR_SDK_KEY: &str = "__STATSIG_ERROR_SDK_KEY__";
77const INIT_IP_TAG: &str = "INIT_COUNTRY_LOOKUP";
78const INIT_UA_TAG: &str = "INIT_UA";
79
80lazy_static::lazy_static! {
81 static ref SHARED_INSTANCE: Mutex<Option<Arc<Statsig>>> = Mutex::new(None);
82}
83
84pub struct Statsig {
85 pub statsig_runtime: Arc<StatsigRuntime>,
86 pub options: Arc<StatsigOptions>,
87 pub event_emitter: SdkEventEmitter,
88
89 sdk_key: String,
90 event_logger: Arc<EventLogger>,
91 specs_adapter: SpecsAdapterHousing,
92 event_logging_adapter: Arc<dyn EventLoggingAdapter>,
93 id_lists_adapter: IdListsAdapterHousing,
94 override_adapter: Option<Arc<dyn OverrideAdapter>>,
95 spec_store: Arc<SpecStore>,
96 hashing: Arc<HashUtil>,
97 statsig_environment: Option<HashMap<String, DynamicValue>>,
98 fallback_environment: Mutex<Option<HashMap<String, DynamicValue>>>,
99 ops_stats: Arc<OpsStatsForInstance>,
100 error_observer: Arc<dyn OpsStatsEventObserver>,
101 diagnostics_observer: Arc<dyn OpsStatsEventObserver>,
102 background_tasks_started: Arc<AtomicBool>,
103 persistent_values_manager: Option<Arc<PersistentValuesManager>>,
104 initialize_details: Mutex<InitializeDetails>,
105}
106
107pub struct StatsigContext {
108 pub sdk_key: String,
109 pub options: Arc<StatsigOptions>,
110 pub local_override_adapter: Option<Arc<dyn OverrideAdapter>>,
111 pub error_observer: Arc<dyn OpsStatsEventObserver>,
112 pub diagnostics_observer: Arc<dyn OpsStatsEventObserver>,
113 pub spec_store: Arc<SpecStore>,
114}
115
116#[derive(Debug, Clone, Serialize)]
117pub struct FailureDetails {
118 pub reason: String,
119 pub error: Option<StatsigErr>,
120}
121
122#[derive(Debug, Clone, Serialize)]
123pub struct InitializeDetails {
124 pub duration: f64,
125 pub init_success: bool,
126 pub is_config_spec_ready: bool,
127 pub is_id_list_ready: Option<bool>,
128 pub source: SpecsSource,
129 pub failure_details: Option<FailureDetails>,
130 pub spec_source_api: Option<String>,
131}
132
133impl Default for InitializeDetails {
134 fn default() -> Self {
135 InitializeDetails {
136 duration: 0.0,
137 init_success: false,
138 is_config_spec_ready: false,
139 is_id_list_ready: None,
140 source: SpecsSource::Uninitialized,
141 failure_details: None,
142 spec_source_api: None,
143 }
144 }
145}
146
147impl InitializeDetails {
148 pub fn from_error(reason: &str, error: Option<StatsigErr>) -> Self {
149 InitializeDetails {
150 duration: 0.0,
151 init_success: false,
152 is_config_spec_ready: false,
153 is_id_list_ready: None,
154 source: SpecsSource::Uninitialized,
155 failure_details: Some(FailureDetails {
156 reason: reason.to_string(),
157 error,
158 }),
159 spec_source_api: None,
160 }
161 }
162}
163
164impl Drop for Statsig {
165 fn drop(&mut self) {
166 self.event_logger.force_shutdown();
167
168 if let Some(adapter) = &self.id_lists_adapter.as_default_adapter {
169 adapter.force_shutdown();
170 }
171
172 if let Some(adapter) = &self.specs_adapter.as_default_adapter {
173 adapter.force_shutdown();
174 }
175
176 shutdown_output_logger();
177
178 log_d!(TAG, "Statsig instance dropped");
179 }
180}
181
182impl Statsig {
183 pub fn new(sdk_key: &str, options: Option<Arc<StatsigOptions>>) -> Self {
184 let statsig_runtime = StatsigRuntime::get_runtime();
185 let options = options.map(|o| o.validate_and_fix()).unwrap_or_default();
186
187 initialize_output_logger(
188 &options.output_log_level,
189 options.output_logger_provider.clone(),
190 );
191
192 let hashing = Arc::new(HashUtil::new());
193
194 let specs_adapter = initialize_specs_adapter(sdk_key, &options, &hashing);
195 let id_lists_adapter = initialize_id_lists_adapter(sdk_key, &options);
196 let event_logging_adapter = initialize_event_logging_adapter(sdk_key, &options);
197 let override_adapter = match options.override_adapter.as_ref() {
198 Some(adapter) => Some(Arc::clone(adapter)),
199 None => Some(Arc::new(StatsigLocalOverrideAdapter::new()) as Arc<dyn OverrideAdapter>),
200 };
201
202 let event_logger =
203 EventLogger::new(sdk_key, &options, &event_logging_adapter, &statsig_runtime);
204
205 let diagnostics = Arc::new(Diagnostics::new(event_logger.clone(), sdk_key));
206 let diagnostics_observer: Arc<dyn OpsStatsEventObserver> =
207 Arc::new(DiagnosticsObserver::new(diagnostics));
208 let error_observer: Arc<dyn OpsStatsEventObserver> =
209 Arc::new(SDKErrorsObserver::new(sdk_key, &options));
210
211 let ops_stats = setup_ops_stats(
212 sdk_key,
213 statsig_runtime.clone(),
214 &error_observer,
215 &diagnostics_observer,
216 &options.observability_client,
217 );
218
219 let spec_store = Arc::new(SpecStore::new(
220 sdk_key,
221 hashing.sha256(sdk_key),
222 statsig_runtime.clone(),
223 options.data_store.clone(),
224 ));
225
226 let environment = options
227 .environment
228 .as_ref()
229 .map(|env| HashMap::from([("tier".into(), dyn_value!(env.as_str()))]));
230
231 let persistent_values_manager = options.persistent_storage.clone().map(|storage| {
232 Arc::new(PersistentValuesManager {
233 persistent_storage: storage,
234 })
235 });
236
237 StatsigMetadata::update_service_name(options.service_name.clone());
238
239 Statsig {
240 sdk_key: sdk_key.to_string(),
241 options,
242 hashing,
243 statsig_environment: environment,
244 fallback_environment: Mutex::new(None),
245 override_adapter,
246 spec_store,
247 specs_adapter,
248 event_logging_adapter,
249 event_logger,
250 id_lists_adapter,
251 statsig_runtime,
252 ops_stats,
253 error_observer,
254 diagnostics_observer,
255 background_tasks_started: Arc::new(AtomicBool::new(false)),
256 persistent_values_manager,
257 initialize_details: Mutex::new(InitializeDetails::default()),
258 event_emitter: SdkEventEmitter::default(),
259 }
260 }
261
262 pub async fn initialize(&self) -> Result<(), StatsigErr> {
277 let details = self.initialize_with_details().await?;
278
279 if let Some(failure_details) = details.failure_details {
280 Err(failure_details
281 .error
282 .unwrap_or(StatsigErr::InitializationError(failure_details.reason)))
283 } else {
284 Ok(())
285 }
286 }
287
288 pub async fn initialize_with_details(&self) -> Result<InitializeDetails, StatsigErr> {
297 self.ops_stats.add_marker(
298 Marker::new(KeyType::Overall, ActionType::Start, None),
299 Some(ContextType::Initialize),
300 );
301
302 let init_details = if let Some(timeout_ms) = self.options.init_timeout_ms {
303 self.apply_timeout_to_init(timeout_ms).await
304 } else {
305 self.initialize_impl_with_details().await
306 };
307 self.log_init_details(&init_details);
308 if let Ok(details) = &init_details {
309 match self.initialize_details.try_lock_for(Duration::from_secs(5)) {
310 Some(mut curr_init_details) => {
311 *curr_init_details = details.clone();
312 }
313 None => {
314 log_e!(TAG, "Failed to lock initialize_details");
315 }
316 }
317 }
318 init_details
319 }
320
321 pub fn get_initialize_details(&self) -> InitializeDetails {
322 match self.initialize_details.try_lock_for(Duration::from_secs(5)) {
323 Some(details) => details.clone(),
324 None => InitializeDetails::from_error(
325 "Failed to lock initialize_details",
326 Some(StatsigErr::LockFailure(
327 "Failed to lock initialize_details".to_string(),
328 )),
329 ),
330 }
331 }
332
333 pub fn is_initialized(&self) -> bool {
334 match self.initialize_details.try_lock_for(Duration::from_secs(5)) {
335 Some(details) => details.init_success,
336 None => false,
337 }
338 }
339
340 pub async fn shutdown(&self) -> Result<(), StatsigErr> {
341 self.shutdown_with_timeout(Duration::from_secs(3)).await
342 }
343
344 pub async fn shutdown_with_timeout(&self, timeout: Duration) -> Result<(), StatsigErr> {
345 log_d!(
346 TAG,
347 "Shutting down Statsig with timeout {}ms",
348 timeout.as_millis()
349 );
350
351 let start = Instant::now();
352 let shutdown_result = tokio::select! {
353 () = tokio::time::sleep(timeout) => {
354 log_w!(TAG, "Statsig shutdown timed out. {}", start.elapsed().as_millis());
355 Err(StatsigErr::ShutdownFailure(
356 "Shutdown timed out".to_string()
357 ))
358 }
359 sub_result = async {
360 let id_list_shutdown: Pin<Box<_>> = if let Some(adapter) = &self.id_lists_adapter.inner {
361 adapter.shutdown(timeout)
362 } else {
363 Box::pin(async { Ok(()) })
364 };
365
366 shutdown_output_logger();
367
368 try_join!(
369 id_list_shutdown,
370 self.event_logger.shutdown(),
371 self.specs_adapter.inner.shutdown(timeout, &self.statsig_runtime),
372 )
373 } => {
374 match sub_result {
375 Ok(_) => {
376 log_d!(TAG, "All Statsig tasks shutdown successfully");
377 Ok(())
378 }
379 Err(e) => {
380 log_w!(TAG, "Error during shutdown: {:?}", e);
381 Err(e)
382 }
383 }
384 }
385 };
386
387 self.statsig_runtime.shutdown();
388 shutdown_result
389 }
390
391 async fn start_background_tasks(
392 statsig_runtime: Arc<StatsigRuntime>,
393 id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
394 specs_adapter: Arc<dyn SpecsAdapter>,
395 ops_stats: Arc<OpsStatsForInstance>,
396 bg_tasks_started: Arc<AtomicBool>,
397 ) -> bool {
398 if bg_tasks_started.load(Ordering::SeqCst) {
399 return true;
400 }
401
402 let mut success = true;
403
404 if let Some(adapter) = &id_lists_adapter {
405 if let Err(e) = adapter
406 .clone()
407 .schedule_background_sync(&statsig_runtime)
408 .await
409 {
410 success = false;
411 log_w!(TAG, "Failed to schedule idlist background job {}", e);
412 }
413 }
414
415 if let Err(e) = specs_adapter
416 .clone()
417 .schedule_background_sync(&statsig_runtime)
418 .await
419 {
420 success = false;
421 log_error_to_statsig_and_console!(
422 ops_stats,
423 TAG,
424 StatsigErr::SpecsAdapterSkipPoll(format!(
425 "Failed to schedule specs adapter background job: {e}"
426 ))
427 );
428 }
429
430 bg_tasks_started.store(true, Ordering::SeqCst);
431
432 success
433 }
434
435 async fn apply_timeout_to_init(
436 &self,
437 timeout_ms: u64,
438 ) -> Result<InitializeDetails, StatsigErr> {
439 let timeout = Duration::from_millis(timeout_ms);
440
441 let init_future = self.initialize_impl_with_details();
442 let timeout_future = sleep(timeout);
443
444 let statsig_runtime = self.statsig_runtime.clone();
445 let id_lists_adapter = self.id_lists_adapter.inner.clone();
446 let specs_adapter = self.specs_adapter.inner.clone();
447 let ops_stats = self.ops_stats.clone();
448 let background_tasks_started = self.background_tasks_started.clone();
449 let statsig_runtime_for_closure = statsig_runtime.clone();
451
452 tokio::select! {
453 result = init_future => {
454 result
455 },
456 _ = timeout_future => {
457 statsig_runtime.spawn(
458 "start_background_tasks",
459 |_shutdown_notify| async move {
460 Self::start_background_tasks(
461 statsig_runtime_for_closure,
462 id_lists_adapter,
463 specs_adapter,
464 ops_stats,
465 background_tasks_started,
466 ).await;
467 }
468 )?;
469 Ok(self.timeout_failure(timeout_ms))
470 },
471 }
472 }
473
474 async fn initialize_impl_with_details(&self) -> Result<InitializeDetails, StatsigErr> {
475 let start_time = Instant::now();
476 self.spec_store.set_source(SpecsSource::Loading);
477 self.specs_adapter.inner.initialize(self.spec_store.clone());
478 let use_third_party_ua_parser = self.should_user_third_party_parser();
479
480 let mut error_message = None;
481 let mut id_list_ready = None;
482
483 let init_country_lookup = if !self.options.disable_country_lookup.unwrap_or_default() {
484 Some(self.statsig_runtime.spawn(INIT_IP_TAG, |_| async {
485 CountryLookup::load_country_lookup();
486 }))
487 } else {
488 None
489 };
490
491 let init_ua = if use_third_party_ua_parser {
492 Some(self.statsig_runtime.spawn(INIT_UA_TAG, |_| async {
493 UserAgentParser::load_parser();
494 }))
495 } else {
496 None
497 };
498
499 let init_res = match self
500 .specs_adapter
501 .inner
502 .clone()
503 .start(&self.statsig_runtime)
504 .await
505 {
506 Ok(()) => Ok(()),
507 Err(e) => {
508 self.spec_store.set_source(SpecsSource::NoValues);
509 error_message = Some(format!("Failed to start specs adapter: {e}"));
510 Err(e)
511 }
512 };
513
514 if let Some(adapter) = &self.id_lists_adapter.inner {
515 match adapter
516 .clone()
517 .start(&self.statsig_runtime, self.spec_store.clone())
518 .await
519 {
520 Ok(()) => {
521 id_list_ready = Some(true);
522 }
523 Err(e) => {
524 id_list_ready = Some(false);
525 error_message.get_or_insert_with(|| format!("Failed to sync ID lists: {e}"));
526 }
527 }
528 if let Err(e) = adapter
529 .clone()
530 .schedule_background_sync(&self.statsig_runtime)
531 .await
532 {
533 log_w!(TAG, "Failed to schedule id_list background job {}", e);
534 }
535 }
536
537 if let Err(e) = self
538 .event_logging_adapter
539 .clone()
540 .start(&self.statsig_runtime)
541 .await
542 {
543 log_error_to_statsig_and_console!(
544 self.ops_stats.clone(),
545 TAG,
546 StatsigErr::UnstartedAdapter(format!("Failed to start event logging adapter: {e}"))
547 );
548 }
549
550 let spec_info = self.spec_store.get_current_specs_info();
551 let duration = start_time.elapsed().as_millis() as f64;
552
553 self.set_default_environment_from_server();
554
555 if self.options.wait_for_country_lookup_init.unwrap_or(false) {
556 match init_country_lookup {
557 Some(Ok(task_id)) => {
558 let _ = self
559 .statsig_runtime
560 .await_join_handle(INIT_IP_TAG, &task_id)
561 .await;
562 }
563 Some(Err(e)) => {
564 log_error_to_statsig_and_console!(
565 self.ops_stats.clone(),
566 TAG,
567 StatsigErr::UnstartedAdapter(format!(
568 "Failed to spawn country lookup task: {e}"
569 ))
570 );
571 }
572 _ => {}
573 }
574 }
575 if self.options.wait_for_user_agent_init.unwrap_or(false) {
576 match init_ua {
577 Some(Ok(task_id)) => {
578 let _ = self
579 .statsig_runtime
580 .await_join_handle(INIT_UA_TAG, &task_id)
581 .await;
582 }
583 Some(Err(e)) => {
584 log_error_to_statsig_and_console!(
585 self.ops_stats.clone(),
586 TAG,
587 StatsigErr::UnstartedAdapter(format!(
588 "Failed to spawn user agent parser task: {e}"
589 ))
590 );
591 }
592 _ => {}
593 }
594 }
595
596 let error = init_res.clone().err();
597
598 let success = Self::start_background_tasks(
599 self.statsig_runtime.clone(),
600 self.id_lists_adapter.inner.clone(),
601 self.specs_adapter.inner.clone(),
602 self.ops_stats.clone(),
603 self.background_tasks_started.clone(),
604 )
605 .await;
606
607 Ok(self.construct_initialize_details(success, duration, spec_info, id_list_ready, error))
608 }
609
610 fn construct_initialize_details(
611 &self,
612 init_success: bool,
613 duration: f64,
614 specs_info: SpecsInfo,
615 is_id_list_ready: Option<bool>,
616 error: Option<StatsigErr>,
617 ) -> InitializeDetails {
618 let is_config_spec_ready = matches!(specs_info.lcut, Some(v) if v != 0);
619
620 let failure_details =
621 if let Some(StatsigErr::NetworkError(NetworkError::DisableNetworkOn(_))) = error {
622 None
623 } else {
624 error.as_ref().map(|e| FailureDetails {
625 reason: e.to_string(),
626 error: Some(e.clone()),
627 })
628 };
629
630 InitializeDetails {
631 init_success,
632 is_config_spec_ready,
633 is_id_list_ready,
634 source: specs_info.source.clone(),
635 failure_details,
636 duration,
637 spec_source_api: specs_info.source_api.clone(),
638 }
639 }
640
641 fn timeout_failure(&self, timeout_ms: u64) -> InitializeDetails {
642 InitializeDetails {
643 init_success: false,
644 is_config_spec_ready: false,
645 is_id_list_ready: None,
646 source: SpecsSource::Uninitialized,
647 failure_details: Some(FailureDetails {
648 reason: "Initialization timed out".to_string(),
649 error: None,
650 }),
651 duration: timeout_ms as f64,
652 spec_source_api: None,
653 }
654 }
655
656 fn log_init_details(&self, init_details: &Result<InitializeDetails, StatsigErr>) {
657 match init_details {
658 Ok(details) => {
659 self.log_init_finish(
660 details.init_success,
661 &None,
662 &details.duration,
663 &self.spec_store.get_current_specs_info(),
664 );
665 if let Some(failure) = &details.failure_details {
666 log_error_to_statsig_and_console!(
667 self.ops_stats,
668 TAG,
669 StatsigErr::InitializationError(failure.reason.clone())
670 );
671 }
672 }
673 Err(err) => {
674 log_w!(TAG, "Initialization error: {:?}", err);
676 }
677 }
678 }
679
680 pub fn get_context(&self) -> StatsigContext {
681 StatsigContext {
682 sdk_key: self.sdk_key.clone(),
683 options: self.options.clone(),
684 local_override_adapter: self.override_adapter.clone(),
685 error_observer: self.error_observer.clone(),
686 diagnostics_observer: self.diagnostics_observer.clone(),
687 spec_store: self.spec_store.clone(),
688 }
689 }
690
691 pub fn log_event(
692 &self,
693 user: &StatsigUser,
694 event_name: &str,
695 value: Option<String>,
696 metadata: Option<HashMap<String, String>>,
697 ) {
698 let user_internal = self.internalize_user(user);
699
700 self.event_logger.enqueue(EnqueuePassthroughOp {
701 event: StatsigEventInternal::new_custom_event(
702 user_internal.to_loggable(),
703 event_name.to_string(),
704 value.map(|v| json!(v)),
705 metadata,
706 ),
707 });
708 }
709
710 pub fn log_event_with_number(
711 &self,
712 user: &StatsigUser,
713 event_name: &str,
714 value: Option<f64>,
715 metadata: Option<HashMap<String, String>>,
716 ) {
717 let user_internal = self.internalize_user(user);
718 self.event_logger.enqueue(EnqueuePassthroughOp {
719 event: StatsigEventInternal::new_custom_event(
720 user_internal.to_loggable(),
721 event_name.to_string(),
722 value.map(|v| json!(v)),
723 metadata,
724 ),
725 });
726 }
727
728 pub fn forward_log_line_event(
729 &self,
730 user: &StatsigUser,
731 log_level: StatsigLogLineLevel,
732 value: Option<String>,
733 metadata: Option<HashMap<String, String>>,
734 ) {
735 let user_internal = self.internalize_user(user);
736 self.event_logger.enqueue(EnqueuePassthroughOp {
737 event: StatsigEventInternal::new_statsig_log_line_event(
738 user_internal.to_loggable(),
739 log_level,
740 value,
741 metadata,
742 ),
743 });
744 }
745
746 pub fn log_layer_param_exposure_with_layer_json(
747 &self,
748 layer_json: String,
749 parameter_name: String,
750 ) {
751 let layer = match serde_json::from_str::<Layer>(&layer_json) {
752 Ok(layer) => layer,
753 Err(e) => {
754 log_error_to_statsig_and_console!(
755 self.ops_stats.clone(),
756 TAG,
757 StatsigErr::ShutdownFailure(e.to_string())
758 );
759 return;
760 }
761 };
762
763 self.log_layer_param_exposure_with_layer(layer, parameter_name);
764 }
765
766 pub fn log_layer_param_exposure_with_layer(&self, layer: Layer, parameter_name: String) {
767 if layer.__disable_exposure {
768 self.event_logger.increment_non_exposure_checks(&layer.name);
769 return;
770 }
771
772 self.event_logger
773 .enqueue(EnqueueLayerParamExpoOp::LayerOwned(
774 Utc::now().timestamp_millis() as u64,
775 Box::new(layer),
776 parameter_name,
777 ExposureTrigger::Auto,
778 ));
779 }
780
781 pub async fn flush_events(&self) {
782 let _ = self.event_logger.flush_all_pending_events().await;
783 }
784
785 pub fn get_client_init_response(&self, user: &StatsigUser) -> InitializeResponse {
786 self.get_client_init_response_with_options(user, &ClientInitResponseOptions::default())
787 }
788
789 pub fn get_client_init_response_with_options(
790 &self,
791 user: &StatsigUser,
792 options: &ClientInitResponseOptions,
793 ) -> InitializeResponse {
794 let user_internal = self.internalize_user(user);
795
796 let data = read_lock_or_else!(self.spec_store.data, {
797 log_error_to_statsig_and_console!(
798 &self.ops_stats,
799 TAG,
800 StatsigErr::LockFailure(
801 "Failed to acquire read lock for spec store data".to_string()
802 )
803 );
804 return InitializeResponse::blank(user_internal);
805 });
806
807 let mut context = self.create_gcir_eval_context(&user_internal, &data, options);
808
809 match GCIRFormatter::generate_v1_format(&mut context, options) {
810 Ok(response) => response,
811 Err(e) => {
812 log_error_to_statsig_and_console!(
813 &self.ops_stats,
814 TAG,
815 StatsigErr::GCIRError(e.to_string())
816 );
817 InitializeResponse::blank(user_internal)
818 }
819 }
820 }
821
822 pub fn get_client_init_response_as_string(&self, user: &StatsigUser) -> String {
823 serde_json::to_string(&self.get_client_init_response(user)).unwrap_or_default()
824 }
825
826 pub fn get_client_init_response_with_options_as_string(
827 &self,
828 user: &StatsigUser,
829 options: &ClientInitResponseOptions,
830 ) -> String {
831 let user_internal = self.internalize_user(user);
832
833 let data = read_lock_or_else!(self.spec_store.data, {
834 log_error_to_statsig_and_console!(
835 &self.ops_stats,
836 TAG,
837 StatsigErr::LockFailure(
838 "Failed to acquire read lock for spec store data".to_string()
839 )
840 );
841 return String::new();
842 });
843
844 let mut context = self.create_gcir_eval_context(&user_internal, &data, options);
845
846 match options.response_format {
847 Some(GCIRResponseFormat::InitializeWithSecondaryExposureMapping) => self
848 .stringify_gcir_response(
849 GCIRFormatter::generate_v2_format(&mut context, options),
850 || InitializeEvaluationsResponse::blank(user_internal),
851 ),
852 _ => self.stringify_gcir_response(
853 GCIRFormatter::generate_v1_format(&mut context, options),
854 || InitializeResponse::blank(user_internal),
855 ),
856 }
857 }
858
859 pub fn get_string_parameter_from_store(
860 &self,
861 user: &StatsigUser,
862 parameter_store_name: &str,
863 parameter_name: &str,
864 fallback: Option<String>,
865 options: Option<ParameterStoreEvaluationOptions>,
866 ) -> Option<String> {
867 self.get_parameter_from_store(
868 user,
869 parameter_store_name,
870 parameter_name,
871 fallback,
872 options,
873 )
874 }
875
876 pub fn get_boolean_parameter_from_store(
877 &self,
878 user: &StatsigUser,
879 parameter_store_name: &str,
880 parameter_name: &str,
881 fallback: Option<bool>,
882 options: Option<ParameterStoreEvaluationOptions>,
883 ) -> Option<bool> {
884 self.get_parameter_from_store(
885 user,
886 parameter_store_name,
887 parameter_name,
888 fallback,
889 options,
890 )
891 }
892
893 pub fn get_float_parameter_from_store(
894 &self,
895 user: &StatsigUser,
896 parameter_store_name: &str,
897 parameter_name: &str,
898 fallback: Option<f64>,
899 options: Option<ParameterStoreEvaluationOptions>,
900 ) -> Option<f64> {
901 self.get_parameter_from_store(
902 user,
903 parameter_store_name,
904 parameter_name,
905 fallback,
906 options,
907 )
908 }
909
910 pub fn get_integer_parameter_from_store(
911 &self,
912 user: &StatsigUser,
913 parameter_store_name: &str,
914 parameter_name: &str,
915 fallback: Option<i64>,
916 options: Option<ParameterStoreEvaluationOptions>,
917 ) -> Option<i64> {
918 self.get_parameter_from_store(
919 user,
920 parameter_store_name,
921 parameter_name,
922 fallback,
923 options,
924 )
925 }
926
927 pub fn get_array_parameter_from_store(
928 &self,
929 user: &StatsigUser,
930 parameter_store_name: &str,
931 parameter_name: &str,
932 fallback: Option<Vec<Value>>,
933 options: Option<ParameterStoreEvaluationOptions>,
934 ) -> Option<Vec<Value>> {
935 self.get_parameter_from_store(
936 user,
937 parameter_store_name,
938 parameter_name,
939 fallback,
940 options,
941 )
942 }
943
944 pub fn get_object_parameter_from_store(
945 &self,
946 user: &StatsigUser,
947 parameter_store_name: &str,
948 parameter_name: &str,
949 fallback: Option<HashMap<String, Value>>,
950 options: Option<ParameterStoreEvaluationOptions>,
951 ) -> Option<HashMap<String, Value>> {
952 self.get_parameter_from_store(
953 user,
954 parameter_store_name,
955 parameter_name,
956 fallback,
957 options,
958 )
959 }
960
961 pub fn get_parameter_from_store<T: DeserializeOwned>(
962 &self,
963 user: &StatsigUser,
964 parameter_store_name: &str,
965 parameter_name: &str,
966 fallback: Option<T>,
967 options: Option<ParameterStoreEvaluationOptions>,
968 ) -> Option<T> {
969 let store = self
970 .get_parameter_store_with_options(parameter_store_name, options.unwrap_or_default());
971 match fallback {
972 Some(fallback) => Some(store.get(user, parameter_name, fallback)),
973 None => store.get_opt(user, parameter_name),
974 }
975 }
976
977 pub fn get_parameter_store(&self, parameter_store_name: &str) -> ParameterStore<'_> {
978 self.get_parameter_store_with_options(
979 parameter_store_name,
980 ParameterStoreEvaluationOptions::default(),
981 )
982 }
983
984 pub fn get_parameter_store_with_options(
985 &self,
986 parameter_store_name: &str,
987 options: ParameterStoreEvaluationOptions,
988 ) -> ParameterStore<'_> {
989 self.event_logger
990 .increment_non_exposure_checks(parameter_store_name);
991
992 let data = read_lock_or_else!(self.spec_store.data, {
993 log_error_to_statsig_and_console!(
994 self.ops_stats.clone(),
995 TAG,
996 StatsigErr::LockFailure(
997 "Failed to acquire read lock for spec store data".to_string()
998 )
999 );
1000 return ParameterStore {
1001 name: parameter_store_name.to_string(),
1002 parameters: HashMap::new(),
1003 details: EvaluationDetails::unrecognized_no_data(),
1004 options,
1005 _statsig_ref: self,
1006 };
1007 });
1008
1009 let stores = &data.values.param_stores;
1010 let store = match stores {
1011 Some(stores) => stores.get(parameter_store_name),
1012 None => {
1013 return ParameterStore {
1014 name: parameter_store_name.to_string(),
1015 parameters: HashMap::new(),
1016 details: EvaluationDetails::unrecognized(
1017 &data.source,
1018 data.values.time,
1019 data.time_received_at,
1020 ),
1021 options,
1022 _statsig_ref: self,
1023 };
1024 }
1025 };
1026 match store {
1027 Some(store) => ParameterStore {
1028 name: parameter_store_name.to_string(),
1029 parameters: store.parameters.clone(),
1030 details: EvaluationDetails::recognized(
1031 &data.source,
1032 data.values.time,
1033 data.time_received_at,
1034 &EvaluatorResult::default(),
1035 ),
1036 options,
1037 _statsig_ref: self,
1038 },
1039 None => ParameterStore {
1040 name: parameter_store_name.to_string(),
1041 parameters: HashMap::new(),
1042 details: EvaluationDetails::unrecognized(
1043 &data.source,
1044 data.values.time,
1045 data.time_received_at,
1046 ),
1047 options,
1048 _statsig_ref: self,
1049 },
1050 }
1051 }
1052}
1053
1054impl Statsig {
1059 pub fn identify(&self, user: &StatsigUser) {
1060 let user_internal = self.internalize_user(user);
1061
1062 self.event_logger.enqueue(EnqueuePassthroughOp {
1063 event: StatsigEventInternal::new_custom_event(
1064 user_internal.to_loggable(),
1065 "statsig::identify".to_string(),
1066 None,
1067 None,
1068 ),
1069 });
1070 }
1071}
1072
1073impl Statsig {
1078 pub fn get_cmab_ranked_groups(
1079 &self,
1080 user: &StatsigUser,
1081 cmab_name: &str,
1082 ) -> Vec<CMABRankedGroup> {
1083 self.event_logger.increment_non_exposure_checks(cmab_name);
1084
1085 let data = read_lock_or_else!(self.spec_store.data, {
1086 log_error_to_statsig_and_console!(
1087 self.ops_stats.clone(),
1088 TAG,
1089 StatsigErr::LockFailure(
1090 "Failed to acquire read lock for spec store data".to_string()
1091 )
1092 );
1093 return vec![];
1094 });
1095 let user_internal = self.internalize_user(user);
1096 let mut context = self.create_standard_eval_context(
1097 &user_internal,
1098 &data,
1099 data.values.app_id.as_ref(),
1100 self.override_adapter.as_ref(),
1101 );
1102 get_cmab_ranked_list(&mut context, cmab_name)
1103 }
1104
1105 pub fn log_cmab_exposure_for_group(
1106 &self,
1107 user: &StatsigUser,
1108 cmab_name: &str,
1109 group_id: String,
1110 ) {
1111 let user_internal = self.internalize_user(user);
1112
1113 let mut experiment = self.get_experiment_impl(&user_internal, cmab_name);
1114 experiment.rule_id = group_id;
1115
1116 self.event_logger.enqueue(EnqueueExperimentExpoOp {
1117 exposure_time: Utc::now().timestamp_millis() as u64,
1118 user: &user_internal,
1119 experiment: &experiment,
1120 trigger: ExposureTrigger::Manual,
1121 });
1122 }
1123}
1124
1125impl Statsig {
1130 pub fn shared() -> Arc<Statsig> {
1131 let lock = match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
1132 Some(lock) => lock,
1133 None => {
1134 log_e!(
1135 TAG,
1136 "Statsig::shared() mutex error: Failed to lock SHARED_INSTANCE"
1137 );
1138 return Arc::new(Statsig::new(ERROR_SDK_KEY, None));
1139 }
1140 };
1141
1142 match lock.as_ref() {
1143 Some(statsig) => statsig.clone(),
1144 None => {
1145 log_e!(
1146 TAG,
1147 "Statsig::shared() called, but no instance has been set with Statsig::new_shared(...)"
1148 );
1149 Arc::new(Statsig::new(ERROR_SDK_KEY, None))
1150 }
1151 }
1152 }
1153
1154 pub fn new_shared(
1155 sdk_key: &str,
1156 options: Option<Arc<StatsigOptions>>,
1157 ) -> Result<Arc<Statsig>, StatsigErr> {
1158 match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
1159 Some(mut lock) => {
1160 if lock.is_some() {
1161 let message = "Statsig shared instance already exists. Call Statsig::remove_shared() before creating a new instance.";
1162 log_e!(TAG, "{}", message);
1163 return Err(StatsigErr::SharedInstanceFailure(message.to_string()));
1164 }
1165
1166 let statsig = Arc::new(Statsig::new(sdk_key, options));
1167 *lock = Some(statsig.clone());
1168 Ok(statsig)
1169 }
1170 None => {
1171 let message = "Statsig::new_shared() mutex error: Failed to lock SHARED_INSTANCE";
1172 log_e!(TAG, "{}", message);
1173 Err(StatsigErr::SharedInstanceFailure(message.to_string()))
1174 }
1175 }
1176 }
1177
1178 pub fn remove_shared() {
1179 match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
1180 Some(mut lock) => {
1181 *lock = None;
1182 }
1183 None => {
1184 log_e!(
1185 TAG,
1186 "Statsig::remove_shared() mutex error: Failed to lock SHARED_INSTANCE"
1187 );
1188 }
1189 }
1190 }
1191
1192 pub fn has_shared_instance() -> bool {
1193 match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
1194 Some(lock) => lock.is_some(),
1195 None => false,
1196 }
1197 }
1198}
1199
1200impl Statsig {
1205 pub fn check_gate(&self, user: &StatsigUser, gate_name: &str) -> bool {
1206 self.check_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1207 }
1208
1209 pub fn check_gate_with_options(
1210 &self,
1211 user: &StatsigUser,
1212 gate_name: &str,
1213 options: FeatureGateEvaluationOptions,
1214 ) -> bool {
1215 let user_internal = self.internalize_user(user);
1216 let disable_exposure_logging = options.disable_exposure_logging;
1217 let (details, evaluation) = self.get_gate_evaluation(&user_internal, gate_name);
1218
1219 let value = evaluation.as_ref().map(|e| e.value).unwrap_or_default();
1220 let rule_id = evaluation
1221 .as_ref()
1222 .map(|e| e.base.rule_id.clone())
1223 .unwrap_or_default();
1224
1225 if disable_exposure_logging {
1226 log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1227 self.event_logger.increment_non_exposure_checks(gate_name);
1228 } else {
1229 self.event_logger.enqueue(EnqueueGateExpoOp {
1230 exposure_time: Utc::now().timestamp_millis() as u64,
1231 user: &user_internal,
1232 queried_gate_name: gate_name,
1233 evaluation: evaluation.map(Cow::Owned),
1234 details: details.clone(),
1235 trigger: ExposureTrigger::Auto,
1236 });
1237 }
1238
1239 self.emit_gate_evaluated(gate_name, rule_id.as_str(), value, &details.reason);
1240
1241 value
1242 }
1243
1244 pub fn get_feature_gate(&self, user: &StatsigUser, gate_name: &str) -> FeatureGate {
1245 self.get_feature_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1246 }
1247
1248 pub fn get_feature_gate_with_options(
1249 &self,
1250 user: &StatsigUser,
1251 gate_name: &str,
1252 options: FeatureGateEvaluationOptions,
1253 ) -> FeatureGate {
1254 let user_internal = self.internalize_user(user);
1255 let disable_exposure_logging = options.disable_exposure_logging;
1256 let (details, evaluation) = self.get_gate_evaluation(&user_internal, gate_name);
1257
1258 if disable_exposure_logging {
1259 log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1260 self.event_logger.increment_non_exposure_checks(gate_name);
1261 } else {
1262 self.event_logger.enqueue(EnqueueGateExpoOp {
1263 exposure_time: Utc::now().timestamp_millis() as u64,
1264 user: &user_internal,
1265 queried_gate_name: gate_name,
1266 evaluation: evaluation.as_ref().map(Cow::Borrowed),
1267 details: details.clone(),
1268 trigger: ExposureTrigger::Auto,
1269 });
1270 }
1271
1272 let gate = make_feature_gate(gate_name, evaluation, details);
1273 self.emit_gate_evaluated(gate_name, &gate.rule_id, gate.value, &gate.details.reason);
1274 gate
1275 }
1276
1277 pub fn manually_log_gate_exposure(&self, user: &StatsigUser, gate_name: &str) {
1278 let user_internal = self.internalize_user(user);
1279 let (details, evaluation) = self.get_gate_evaluation(&user_internal, gate_name);
1280 self.event_logger.enqueue(EnqueueGateExpoOp {
1281 exposure_time: Utc::now().timestamp_millis() as u64,
1282 user: &user_internal,
1283 queried_gate_name: gate_name,
1284 evaluation: evaluation.map(Cow::Owned),
1285 details: details.clone(),
1286 trigger: ExposureTrigger::Manual,
1287 });
1288 }
1289
1290 pub fn get_fields_needed_for_gate(&self, gate_name: &str) -> Vec<String> {
1291 let data = read_lock_or_else!(self.spec_store.data, {
1292 log_error_to_statsig_and_console!(
1293 self.ops_stats.clone(),
1294 TAG,
1295 StatsigErr::LockFailure(
1296 "Failed to acquire read lock for spec store data".to_string()
1297 )
1298 );
1299 return vec![];
1300 });
1301
1302 let gate = data.values.feature_gates.get(gate_name);
1303 match gate {
1304 Some(gate) => match &gate.spec.fields_used {
1305 Some(fields) => fields.iter().map(|f| f.unperformant_to_string()).collect(),
1306 None => vec![],
1307 },
1308 None => vec![],
1309 }
1310 }
1311}
1312
1313impl Statsig {
1318 pub fn override_gate(&self, gate_name: &str, value: bool, id: Option<&str>) {
1319 if let Some(adapter) = &self.override_adapter {
1320 adapter.override_gate(gate_name, value, id);
1321 }
1322 }
1323
1324 pub fn override_dynamic_config(
1325 &self,
1326 config_name: &str,
1327 value: HashMap<String, serde_json::Value>,
1328 id: Option<&str>,
1329 ) {
1330 if let Some(adapter) = &self.override_adapter {
1331 adapter.override_dynamic_config(config_name, value, id);
1332 }
1333 }
1334
1335 pub fn override_layer(
1336 &self,
1337 layer_name: &str,
1338 value: HashMap<String, serde_json::Value>,
1339 id: Option<&str>,
1340 ) {
1341 if let Some(adapter) = &self.override_adapter {
1342 adapter.override_layer(layer_name, value, id);
1343 }
1344 }
1345
1346 pub fn override_experiment(
1347 &self,
1348 experiment_name: &str,
1349 value: HashMap<String, serde_json::Value>,
1350 id: Option<&str>,
1351 ) {
1352 if let Some(adapter) = &self.override_adapter {
1353 adapter.override_experiment(experiment_name, value, id);
1354 }
1355 }
1356
1357 pub fn override_experiment_by_group_name(
1358 &self,
1359 experiment_name: &str,
1360 group_name: &str,
1361 id: Option<&str>,
1362 ) {
1363 if let Some(adapter) = &self.override_adapter {
1364 adapter.override_experiment_by_group_name(experiment_name, group_name, id);
1365 }
1366 }
1367
1368 pub fn remove_gate_override(&self, gate_name: &str, id: Option<&str>) {
1369 if let Some(adapter) = &self.override_adapter {
1370 adapter.remove_gate_override(gate_name, id);
1371 }
1372 }
1373
1374 pub fn remove_dynamic_config_override(&self, config_name: &str, id: Option<&str>) {
1375 if let Some(adapter) = &self.override_adapter {
1376 adapter.remove_dynamic_config_override(config_name, id);
1377 }
1378 }
1379
1380 pub fn remove_experiment_override(&self, experiment_name: &str, id: Option<&str>) {
1381 if let Some(adapter) = &self.override_adapter {
1382 adapter.remove_experiment_override(experiment_name, id);
1383 }
1384 }
1385
1386 pub fn remove_layer_override(&self, layer_name: &str, id: Option<&str>) {
1387 if let Some(adapter) = &self.override_adapter {
1388 adapter.remove_layer_override(layer_name, id);
1389 }
1390 }
1391
1392 pub fn remove_all_overrides(&self) {
1393 if let Some(adapter) = &self.override_adapter {
1394 adapter.remove_all_overrides();
1395 }
1396 }
1397}
1398
1399impl Statsig {
1404 pub fn get_feature_gate_list(&self) -> Vec<String> {
1405 let data = read_lock_or_else!(self.spec_store.data, {
1406 log_error_to_statsig_and_console!(
1407 &self.ops_stats,
1408 TAG,
1409 StatsigErr::LockFailure(
1410 "Failed to acquire read lock for spec store data".to_string()
1411 )
1412 );
1413 return vec![];
1414 });
1415
1416 data.values.feature_gates.unperformant_keys()
1417 }
1418
1419 pub fn get_dynamic_config_list(&self) -> Vec<String> {
1420 let data = read_lock_or_else!(self.spec_store.data, {
1421 log_error_to_statsig_and_console!(
1422 &self.ops_stats,
1423 TAG,
1424 StatsigErr::LockFailure(
1425 "Failed to acquire read lock for spec store data".to_string()
1426 )
1427 );
1428 return vec![];
1429 });
1430
1431 data.values
1432 .dynamic_configs
1433 .unperformant_keys_entity_filter("dynamic_config")
1434 }
1435
1436 pub fn get_experiment_list(&self) -> Vec<String> {
1437 let data = read_lock_or_else!(self.spec_store.data, {
1438 log_error_to_statsig_and_console!(
1439 &self.ops_stats,
1440 TAG,
1441 StatsigErr::LockFailure(
1442 "Failed to acquire read lock for spec store data".to_string()
1443 )
1444 );
1445 return vec![];
1446 });
1447
1448 data.values
1449 .dynamic_configs
1450 .unperformant_keys_entity_filter("experiment")
1451 }
1452
1453 pub fn get_autotune_list(&self) -> Vec<String> {
1454 let data = read_lock_or_else!(self.spec_store.data, {
1455 log_error_to_statsig_and_console!(
1456 &self.ops_stats,
1457 TAG,
1458 StatsigErr::LockFailure(
1459 "Failed to acquire read lock for spec store data".to_string()
1460 )
1461 );
1462 return vec![];
1463 });
1464
1465 data.values
1466 .dynamic_configs
1467 .unperformant_keys_entity_filter("autotune")
1468 }
1469
1470 pub fn get_parameter_store_list(&self) -> Vec<String> {
1471 let data = read_lock_or_else!(self.spec_store.data, {
1472 log_error_to_statsig_and_console!(
1473 &self.ops_stats,
1474 TAG,
1475 StatsigErr::LockFailure(
1476 "Failed to acquire read lock for spec store data".to_string()
1477 )
1478 );
1479 return vec![];
1480 });
1481
1482 match &data.values.param_stores {
1483 Some(param_stores) => param_stores.keys().cloned().collect(),
1484 None => vec![],
1485 }
1486 }
1487
1488 pub fn get_layer_list(&self) -> Vec<String> {
1489 let data = read_lock_or_else!(self.spec_store.data, {
1490 log_error_to_statsig_and_console!(
1491 &self.ops_stats,
1492 TAG,
1493 StatsigErr::LockFailure(
1494 "Failed to acquire read lock for spec store data".to_string()
1495 )
1496 );
1497 return vec![];
1498 });
1499
1500 data.values.layer_configs.unperformant_keys()
1501 }
1502
1503 pub fn __get_parsed_user_agent_value(
1504 &self,
1505 user: &StatsigUser,
1506 ) -> Option<ParsedUserAgentValue> {
1507 UserAgentParser::get_parsed_user_agent_value_for_user(user, &self.options)
1508 }
1509}
1510
1511impl Statsig {
1516 pub fn get_dynamic_config(
1517 &self,
1518 user: &StatsigUser,
1519 dynamic_config_name: &str,
1520 ) -> DynamicConfig {
1521 self.get_dynamic_config_with_options(
1522 user,
1523 dynamic_config_name,
1524 DynamicConfigEvaluationOptions::default(),
1525 )
1526 }
1527
1528 pub fn get_dynamic_config_with_options(
1529 &self,
1530 user: &StatsigUser,
1531 dynamic_config_name: &str,
1532 options: DynamicConfigEvaluationOptions,
1533 ) -> DynamicConfig {
1534 let user_internal = self.internalize_user(user);
1535 let disable_exposure_logging = options.disable_exposure_logging;
1536 let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1537
1538 if disable_exposure_logging {
1539 log_d!(
1540 TAG,
1541 "Exposure logging is disabled for Dynamic Config {}",
1542 dynamic_config_name
1543 );
1544 self.event_logger
1545 .increment_non_exposure_checks(dynamic_config_name);
1546 } else {
1547 self.event_logger.enqueue(EnqueueConfigExpoOp {
1548 exposure_time: Utc::now().timestamp_millis() as u64,
1549 user: &user_internal,
1550 config: &dynamic_config,
1551 trigger: ExposureTrigger::Auto,
1552 });
1553 }
1554
1555 self.emit_dynamic_config_evaluated(&dynamic_config);
1556
1557 dynamic_config
1558 }
1559
1560 pub fn manually_log_dynamic_config_exposure(
1561 &self,
1562 user: &StatsigUser,
1563 dynamic_config_name: &str,
1564 ) {
1565 let user_internal = self.internalize_user(user);
1566 let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1567 self.event_logger.enqueue(EnqueueConfigExpoOp {
1568 exposure_time: Utc::now().timestamp_millis() as u64,
1569 user: &user_internal,
1570 config: &dynamic_config,
1571 trigger: ExposureTrigger::Manual,
1572 });
1573 }
1574
1575 pub fn get_fields_needed_for_dynamic_config(&self, config_name: &str) -> Vec<String> {
1576 let data = read_lock_or_else!(self.spec_store.data, {
1577 log_error_to_statsig_and_console!(
1578 self.ops_stats.clone(),
1579 TAG,
1580 StatsigErr::LockFailure(
1581 "Failed to acquire read lock for spec store data".to_string()
1582 )
1583 );
1584 return vec![];
1585 });
1586
1587 let config = data.values.dynamic_configs.get(config_name);
1588 match config {
1589 Some(config) => match &config.spec.fields_used {
1590 Some(fields) => fields.iter().map(|f| f.unperformant_to_string()).collect(),
1591 None => vec![],
1592 },
1593 None => vec![],
1594 }
1595 }
1596}
1597
1598impl Statsig {
1603 pub fn get_experiment(&self, user: &StatsigUser, experiment_name: &str) -> Experiment {
1604 self.get_experiment_with_options(
1605 user,
1606 experiment_name,
1607 ExperimentEvaluationOptions::default(),
1608 )
1609 }
1610
1611 pub fn get_experiment_with_options(
1612 &self,
1613 user: &StatsigUser,
1614 experiment_name: &str,
1615 options: ExperimentEvaluationOptions,
1616 ) -> Experiment {
1617 let user_internal = self.internalize_user(user);
1618 let disable_exposure_logging = options.disable_exposure_logging;
1619 let mut experiment = self.get_experiment_impl(&user_internal, experiment_name);
1620 if let Some(persisted_experiment) = self.persistent_values_manager.as_ref().and_then(|m| {
1621 m.try_apply_sticky_value_to_experiment(&user_internal, &options, &experiment)
1622 }) {
1623 experiment = persisted_experiment
1624 }
1625
1626 if disable_exposure_logging {
1627 log_d!(
1628 TAG,
1629 "Exposure logging is disabled for experiment {}",
1630 experiment_name
1631 );
1632 self.event_logger
1633 .increment_non_exposure_checks(experiment_name);
1634 } else {
1635 self.event_logger.enqueue(EnqueueExperimentExpoOp {
1636 exposure_time: Utc::now().timestamp_millis() as u64,
1637 user: &user_internal,
1638 experiment: &experiment,
1639 trigger: ExposureTrigger::Auto,
1640 });
1641 }
1642
1643 self.emit_experiment_evaluated(&experiment);
1644
1645 experiment
1646 }
1647
1648 pub fn manually_log_experiment_exposure(&self, user: &StatsigUser, experiment_name: &str) {
1649 let user_internal = self.internalize_user(user);
1650 let experiment = self.get_experiment_impl(&user_internal, experiment_name);
1651 self.event_logger.enqueue(EnqueueExperimentExpoOp {
1652 exposure_time: Utc::now().timestamp_millis() as u64,
1653 user: &user_internal,
1654 experiment: &experiment,
1655 trigger: ExposureTrigger::Manual,
1656 });
1657 }
1658
1659 pub fn get_fields_needed_for_experiment(&self, experiment_name: &str) -> Vec<String> {
1660 let data = read_lock_or_else!(self.spec_store.data, {
1661 log_error_to_statsig_and_console!(
1662 self.ops_stats.clone(),
1663 TAG,
1664 StatsigErr::LockFailure(
1665 "Failed to acquire read lock for spec store data".to_string()
1666 )
1667 );
1668 return vec![];
1669 });
1670
1671 let config = data.values.dynamic_configs.get(experiment_name);
1672 match config {
1673 Some(config) => match &config.spec.fields_used {
1674 Some(fields) => fields.iter().map(|f| f.unperformant_to_string()).collect(),
1675 None => vec![],
1676 },
1677 None => vec![],
1678 }
1679 }
1680
1681 pub fn get_experiment_by_group_name(
1682 &self,
1683 experiment_name: &str,
1684 group_name: &str,
1685 ) -> Experiment {
1686 let data = read_lock_or_else!(self.spec_store.data, {
1687 log_error_to_statsig_and_console!(
1688 self.ops_stats.clone(),
1689 TAG,
1690 StatsigErr::LockFailure(
1691 "Failed to acquire read lock for spec store data".to_string()
1692 )
1693 );
1694 return make_experiment(
1695 experiment_name,
1696 None,
1697 EvaluationDetails::error("Failed to acquire read lock for spec store data"),
1698 );
1699 });
1700
1701 let Some(exp) = data.values.dynamic_configs.get(experiment_name) else {
1702 return make_experiment(
1703 experiment_name,
1704 None,
1705 EvaluationDetails::unrecognized(
1706 &data.source,
1707 data.values.time,
1708 data.time_received_at,
1709 ),
1710 );
1711 };
1712
1713 if let Some(rule) = exp
1714 .spec
1715 .rules
1716 .iter()
1717 .find(|rule| rule.group_name.as_deref() == Some(group_name))
1718 {
1719 let value = rule.return_value.get_json().unwrap_or_default();
1720 let rule_id = String::from(rule.id.as_str());
1721 let id_type = rule.id_type.value.unperformant_to_string();
1722 let group_name = rule.group_name.as_ref().map(|g| g.unperformant_to_string());
1723
1724 return Experiment {
1725 name: experiment_name.to_string(),
1726 value,
1727 rule_id,
1728 id_type,
1729 group_name,
1730 details: EvaluationDetails::recognized_without_eval_result(
1731 &data.source,
1732 data.values.time,
1733 data.time_received_at,
1734 ),
1735 is_experiment_active: exp.spec.is_active.unwrap_or(false),
1736 __evaluation: None,
1737 };
1738 }
1739
1740 make_experiment(
1741 experiment_name,
1742 None,
1743 EvaluationDetails::unrecognized(&data.source, data.values.time, data.time_received_at),
1744 )
1745 }
1746}
1747
1748impl Statsig {
1753 pub fn get_layer(&self, user: &StatsigUser, layer_name: &str) -> Layer {
1754 self.get_layer_with_options(user, layer_name, LayerEvaluationOptions::default())
1755 }
1756
1757 pub fn get_layer_with_options(
1758 &self,
1759 user: &StatsigUser,
1760 layer_name: &str,
1761 options: LayerEvaluationOptions,
1762 ) -> Layer {
1763 let user_internal = self.internalize_user(user);
1764 self.get_layer_impl(user_internal, layer_name, options)
1765 }
1766
1767 pub fn get_prompt(&self, user: &StatsigUser, prompt_name: &str) -> Layer {
1768 self.get_layer(user, prompt_name)
1769 }
1770
1771 pub fn get_prompt_with_options(
1772 &self,
1773 user: &StatsigUser,
1774 prompt_name: &str,
1775 options: LayerEvaluationOptions,
1776 ) -> Layer {
1777 self.get_layer_with_options(user, prompt_name, options)
1778 }
1779
1780 pub fn manually_log_layer_parameter_exposure(
1781 &self,
1782 user: &StatsigUser,
1783 layer_name: &str,
1784 parameter_name: String,
1785 ) {
1786 let user_internal = self.internalize_user(user);
1787 let layer =
1788 self.get_layer_impl(user_internal, layer_name, LayerEvaluationOptions::default());
1789
1790 self.event_logger
1791 .enqueue(EnqueueLayerParamExpoOp::LayerOwned(
1792 Utc::now().timestamp_millis() as u64,
1793 Box::new(layer),
1794 parameter_name,
1795 ExposureTrigger::Manual,
1796 ));
1797 }
1798
1799 pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> {
1800 let data = read_lock_or_else!(self.spec_store.data, {
1801 log_error_to_statsig_and_console!(
1802 self.ops_stats.clone(),
1803 TAG,
1804 StatsigErr::LockFailure(
1805 "Failed to acquire read lock for spec store data".to_string()
1806 )
1807 );
1808 return vec![];
1809 });
1810
1811 let layer = data.values.layer_configs.get(layer_name);
1812 match layer {
1813 Some(layer) => match &layer.spec.fields_used {
1814 Some(fields) => fields.iter().map(|f| f.unperformant_to_string()).collect(),
1815 None => vec![],
1816 },
1817 None => vec![],
1818 }
1819 }
1820}
1821
1822impl Statsig {
1827 pub(crate) fn get_from_statsig_env(&self, key: &str) -> Option<DynamicValue> {
1828 if let Some(env) = &self.statsig_environment {
1829 return env.get(key).cloned();
1830 }
1831
1832 if let Some(fallback_env) = self
1833 .fallback_environment
1834 .try_lock_for(Duration::from_secs(5))
1835 {
1836 if let Some(env) = &*fallback_env {
1837 return env.get(key).cloned();
1838 }
1839 }
1840
1841 None
1842 }
1843
1844 pub(crate) fn get_value_from_global_custom_fields(&self, key: &str) -> Option<&DynamicValue> {
1845 if let Some(env) = &self.options.global_custom_fields {
1846 return env.get(key);
1847 }
1848
1849 None
1850 }
1851
1852 pub(crate) fn use_global_custom_fields<T>(
1853 &self,
1854 f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1855 ) -> T {
1856 f(self.options.global_custom_fields.as_ref())
1857 }
1858
1859 pub(crate) fn use_statsig_env<T>(
1860 &self,
1861 f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1862 ) -> T {
1863 if let Some(env) = &self.statsig_environment {
1864 return f(Some(env));
1865 }
1866
1867 if let Some(fallback_env) = self
1868 .fallback_environment
1869 .try_lock_for(Duration::from_secs(5))
1870 {
1871 if let Some(env) = &*fallback_env {
1872 return f(Some(env));
1873 }
1874 }
1875
1876 f(None)
1877 }
1878}
1879
1880impl Statsig {
1885 fn create_standard_eval_context<'a>(
1886 &'a self,
1887 user_internal: &'a StatsigUserInternal,
1888 data: &'a SpecStoreData,
1889 app_id: Option<&'a DynamicValue>,
1890 override_adapter: Option<&'a Arc<dyn OverrideAdapter>>,
1891 ) -> EvaluatorContext<'a> {
1892 EvaluatorContext::new(
1893 user_internal,
1894 &data.values,
1895 IdListResolution::MapLookup(&data.id_lists),
1896 &self.hashing,
1897 app_id,
1898 override_adapter,
1899 self.should_user_third_party_parser(),
1900 )
1901 }
1902
1903 fn create_gcir_eval_context<'a>(
1904 &'a self,
1905 user_internal: &'a StatsigUserInternal,
1906 data: &'a SpecStoreData,
1907 options: &'a ClientInitResponseOptions,
1908 ) -> EvaluatorContext<'a> {
1909 let app_id = select_app_id_for_gcir(options, &data.values, &self.hashing);
1910 let override_adapter = match options.include_local_overrides {
1911 Some(true) => self.override_adapter.as_ref(),
1912 _ => None,
1913 };
1914
1915 EvaluatorContext::new(
1916 user_internal,
1917 &data.values,
1918 IdListResolution::MapLookup(&data.id_lists),
1919 &self.hashing,
1920 app_id,
1921 override_adapter,
1922 self.should_user_third_party_parser(),
1923 )
1924 }
1925
1926 fn evaluate_spec<T>(
1927 &self,
1928 user_internal: &StatsigUserInternal,
1929 spec_name: &str,
1930 make_empty_result: impl FnOnce(EvaluationDetails) -> T,
1931 make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
1932 spec_type: &SpecType,
1933 ) -> T {
1934 let data = read_lock_or_else!(self.spec_store.data, {
1935 log_error_to_statsig_and_console!(
1936 &self.ops_stats,
1937 TAG,
1938 StatsigErr::LockFailure(
1939 "Failed to acquire read lock for spec store data".to_string()
1940 )
1941 );
1942 return make_empty_result(EvaluationDetails::unrecognized_no_data());
1943 });
1944
1945 let mut context = self.create_standard_eval_context(
1946 user_internal,
1947 &data,
1948 data.values.app_id.as_ref(),
1949 self.override_adapter.as_ref(),
1950 );
1951
1952 match Self::evaluate_with_details(&mut context, &data, spec_name, spec_type) {
1953 Ok(eval_details) => make_result(context.result, eval_details),
1954 Err(e) => {
1955 log_error_to_statsig_and_console!(
1956 &self.ops_stats,
1957 TAG,
1958 StatsigErr::EvaluationError(e.to_string())
1959 );
1960 make_empty_result(EvaluationDetails::error(&e.to_string()))
1961 }
1962 }
1963 }
1964
1965 fn evaluate_with_details(
1966 ctx: &mut EvaluatorContext,
1967 spec_store_data: &SpecStoreData,
1968 spec_name: &str,
1969 spec_type: &SpecType,
1970 ) -> Result<EvaluationDetails, StatsigErr> {
1971 let recognition = Evaluator::evaluate(ctx, spec_name, spec_type)?;
1972
1973 if recognition == Recognition::Unrecognized {
1974 return Ok(EvaluationDetails::unrecognized(
1975 &spec_store_data.source,
1976 spec_store_data.values.time,
1977 spec_store_data.time_received_at,
1978 ));
1979 }
1980
1981 if let Some(reason) = ctx.result.override_reason {
1982 return Ok(EvaluationDetails::recognized_but_overridden(
1983 spec_store_data.values.time,
1984 spec_store_data.time_received_at,
1985 reason,
1986 ));
1987 }
1988
1989 Ok(EvaluationDetails::recognized(
1990 &spec_store_data.source,
1991 spec_store_data.values.time,
1992 spec_store_data.time_received_at,
1993 &ctx.result,
1994 ))
1995 }
1996
1997 fn stringify_gcir_response<T: Serialize>(
1998 &self,
1999 input: Result<T, StatsigErr>,
2000 fallback: impl FnOnce() -> T,
2001 ) -> String {
2002 match input {
2003 Ok(value) => serde_json::to_string(&value).unwrap_or_default(),
2004 Err(e) => {
2005 log_error_to_statsig_and_console!(
2006 &self.ops_stats,
2007 TAG,
2008 StatsigErr::GCIRError(e.to_string())
2009 );
2010 serde_json::to_string(&fallback()).unwrap_or_default()
2011 }
2012 }
2013 }
2014
2015 fn get_gate_evaluation(
2016 &self,
2017 user_internal: &StatsigUserInternal,
2018 gate_name: &str,
2019 ) -> (EvaluationDetails, Option<GateEvaluation>) {
2020 self.evaluate_spec(
2021 user_internal,
2022 gate_name,
2023 |eval_details| (eval_details, None),
2024 |mut result, eval_details| {
2025 let evaluation = result_to_gate_eval(gate_name, &mut result);
2026 (eval_details, Some(evaluation))
2027 },
2028 &SpecType::Gate,
2029 )
2030 }
2031
2032 fn get_dynamic_config_impl(
2033 &self,
2034 user_internal: &StatsigUserInternal,
2035 config_name: &str,
2036 ) -> DynamicConfig {
2037 self.evaluate_spec(
2038 user_internal,
2039 config_name,
2040 |eval_details| make_dynamic_config(config_name, None, eval_details),
2041 |mut result, eval_details| {
2042 let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
2043 make_dynamic_config(config_name, Some(evaluation), eval_details)
2044 },
2045 &SpecType::DynamicConfig,
2046 )
2047 }
2048
2049 fn get_experiment_impl(
2050 &self,
2051 user_internal: &StatsigUserInternal,
2052 experiment_name: &str,
2053 ) -> Experiment {
2054 self.evaluate_spec(
2055 user_internal,
2056 experiment_name,
2057 |eval_details| make_experiment(experiment_name, None, eval_details),
2058 |mut result, eval_details| {
2059 let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
2060 make_experiment(experiment_name, Some(evaluation), eval_details)
2061 },
2062 &SpecType::Experiment,
2063 )
2064 }
2065
2066 fn get_layer_impl(
2067 &self,
2068 user_internal: StatsigUserInternal,
2069 layer_name: &str,
2070 evaluation_options: LayerEvaluationOptions,
2071 ) -> Layer {
2072 let disable_exposure_logging = evaluation_options.disable_exposure_logging;
2073
2074 if disable_exposure_logging {
2075 self.event_logger.increment_non_exposure_checks(layer_name);
2076 }
2077
2078 let mut layer = self.evaluate_spec(
2079 &user_internal,
2080 layer_name,
2081 |eval_details| {
2082 make_layer(
2083 user_internal.to_loggable(),
2084 layer_name,
2085 None,
2086 eval_details,
2087 None,
2088 disable_exposure_logging,
2089 )
2090 },
2091 |mut result, eval_details| {
2092 let evaluation = result_to_layer_eval(layer_name, &mut result);
2093 let event_logger_ptr = Arc::downgrade(&self.event_logger);
2094
2095 make_layer(
2096 user_internal.to_loggable(),
2097 layer_name,
2098 Some(evaluation),
2099 eval_details,
2100 Some(event_logger_ptr),
2101 disable_exposure_logging,
2102 )
2103 },
2104 &SpecType::Layer,
2105 );
2106
2107 let data = read_lock_or_else!(self.spec_store.data, {
2108 log_error_to_statsig_and_console!(
2109 &self.ops_stats,
2110 TAG,
2111 StatsigErr::LockFailure(
2112 "Failed to acquire read lock for spec store data".to_string()
2113 )
2114 );
2115 return layer;
2116 });
2117 if let Some(persisted_layer) = self.persistent_values_manager.as_ref().and_then(|p| {
2118 let event_logger_ptr = Arc::downgrade(&self.event_logger);
2119 p.try_apply_sticky_value_to_layer(
2120 &user_internal,
2121 &evaluation_options,
2122 &layer,
2123 Some(event_logger_ptr),
2124 disable_exposure_logging,
2125 &data,
2126 )
2127 }) {
2128 layer = persisted_layer
2129 }
2130
2131 self.emit_layer_evaluated(&layer);
2132
2133 layer
2134 }
2135
2136 fn internalize_user<'s, 'u>(&'s self, user: &'u StatsigUser) -> StatsigUserInternal<'s, 'u> {
2137 StatsigUserInternal::new(user, Some(self))
2138 }
2139
2140 fn set_default_environment_from_server(&self) {
2141 let data = read_lock_or_else!(self.spec_store.data, {
2142 return;
2143 });
2144
2145 if let Some(default_env) = data.values.default_environment.as_ref() {
2146 let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
2147
2148 match self
2149 .fallback_environment
2150 .try_lock_for(Duration::from_secs(5))
2151 {
2152 Some(mut fallback_env) => {
2153 *fallback_env = Some(env_map);
2154 }
2155 None => {
2156 log_e!(TAG, "Failed to lock fallback_environment");
2157 }
2158 }
2159 }
2160 }
2161
2162 fn log_init_finish(
2163 &self,
2164 success: bool,
2165 error_message: &Option<String>,
2166 duration: &f64,
2167 specs_info: &SpecsInfo,
2168 ) {
2169 let is_store_populated = specs_info.source != SpecsSource::NoValues;
2170 let source_str = specs_info.source.to_string();
2171
2172 let event = ObservabilityEvent::new_event(
2173 MetricType::Dist,
2174 "initialization".to_string(),
2175 *duration,
2176 Some(HashMap::from([
2177 ("success".to_owned(), success.to_string()),
2178 ("source".to_owned(), source_str.clone()),
2179 ("store_populated".to_owned(), is_store_populated.to_string()),
2180 (
2181 "spec_source_api".to_owned(),
2182 specs_info.source_api.clone().unwrap_or_default(),
2183 ),
2184 ])),
2185 );
2186
2187 self.ops_stats.log(event);
2188 self.ops_stats.add_marker(
2189 {
2190 let marker = Marker::new(KeyType::Overall, ActionType::End, None)
2191 .with_is_success(success)
2192 .with_config_spec_ready(specs_info.source != SpecsSource::NoValues)
2193 .with_source(source_str);
2194
2195 if let Some(msg) = &error_message {
2196 marker.with_message(msg.to_string())
2197 } else {
2198 marker
2199 }
2200 },
2201 Some(ContextType::Initialize),
2202 );
2203 self.ops_stats
2204 .enqueue_diagnostics_event(None, Some(ContextType::Initialize));
2205 }
2206
2207 fn should_user_third_party_parser(&self) -> bool {
2208 self.options.use_third_party_ua_parser.unwrap_or(false)
2209 }
2210}
2211
2212fn initialize_event_logging_adapter(
2213 sdk_key: &str,
2214 options: &StatsigOptions,
2215) -> Arc<dyn EventLoggingAdapter> {
2216 options
2217 .event_logging_adapter
2218 .clone()
2219 .unwrap_or_else(|| Arc::new(StatsigHttpEventLoggingAdapter::new(sdk_key, Some(options))))
2220}
2221
2222fn initialize_specs_adapter(
2223 sdk_key: &str,
2224 options: &StatsigOptions,
2225 hashing: &HashUtil,
2226) -> SpecsAdapterHousing {
2227 if let Some(adapter) = options.specs_adapter.clone() {
2228 log_d!(TAG, "Using provided SpecsAdapter: {}", sdk_key);
2229 return SpecsAdapterHousing {
2230 inner: adapter,
2231 as_default_adapter: None,
2232 };
2233 }
2234
2235 if let Some(adapter_config) = options.spec_adapters_config.clone() {
2236 let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
2237 sdk_key,
2238 adapter_config,
2239 options,
2240 hashing,
2241 ));
2242
2243 return SpecsAdapterHousing {
2244 inner: adapter,
2245 as_default_adapter: None,
2246 };
2247 }
2248
2249 if let Some(data_adapter) = options.data_store.clone() {
2250 let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
2251 sdk_key,
2252 data_adapter,
2253 options,
2254 hashing,
2255 ));
2256
2257 return SpecsAdapterHousing {
2258 inner: adapter,
2259 as_default_adapter: None,
2260 };
2261 }
2262
2263 let adapter = Arc::new(StatsigHttpSpecsAdapter::new(sdk_key, Some(options), None));
2264
2265 SpecsAdapterHousing {
2266 inner: adapter.clone(),
2267 as_default_adapter: Some(adapter),
2268 }
2269}
2270
2271fn initialize_id_lists_adapter(sdk_key: &str, options: &StatsigOptions) -> IdListsAdapterHousing {
2272 if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
2273 return IdListsAdapterHousing {
2274 inner: Some(id_lists_adapter),
2275 as_default_adapter: None,
2276 };
2277 }
2278
2279 if options.enable_id_lists.unwrap_or(false) {
2280 let adapter = Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options));
2281
2282 return IdListsAdapterHousing {
2283 inner: Some(adapter.clone()),
2284 as_default_adapter: Some(adapter),
2285 };
2286 }
2287
2288 IdListsAdapterHousing {
2289 inner: None,
2290 as_default_adapter: None,
2291 }
2292}
2293
2294struct IdListsAdapterHousing {
2295 inner: Option<Arc<dyn IdListsAdapter>>,
2296 as_default_adapter: Option<Arc<StatsigHttpIdListsAdapter>>,
2297}
2298
2299struct SpecsAdapterHousing {
2300 inner: Arc<dyn SpecsAdapter>,
2301 as_default_adapter: Option<Arc<StatsigHttpSpecsAdapter>>,
2302}
2303
2304fn setup_ops_stats(
2305 sdk_key: &str,
2306 statsig_runtime: Arc<StatsigRuntime>,
2307 error_observer: &Arc<dyn OpsStatsEventObserver>,
2308 diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
2309 external_observer: &Option<Weak<dyn ObservabilityClient>>,
2310) -> Arc<OpsStatsForInstance> {
2311 let ops_stat = OPS_STATS.get_for_instance(sdk_key);
2312 ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
2313 ops_stat.subscribe(
2314 statsig_runtime.clone(),
2315 Arc::downgrade(diagnostics_observer),
2316 );
2317
2318 if let Some(ob_client) = external_observer {
2319 if let Some(client) = ob_client.upgrade() {
2320 client.init();
2321 let as_observer = client.to_ops_stats_event_observer();
2322 ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
2323 }
2324 }
2325
2326 ops_stat
2327}