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