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