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