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