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_for_user(
818 &self,
819 user: &StatsigUser,
820 parameter_store_name: &str,
821 ) -> ParameterStore<'_> {
822 self.get_parameter_store_with_user_and_options(
823 Some(user),
824 parameter_store_name,
825 ParameterStoreEvaluationOptions::default(),
826 )
827 }
828
829 pub fn get_parameter_store_with_options(
830 &self,
831 parameter_store_name: &str,
832 options: ParameterStoreEvaluationOptions,
833 ) -> ParameterStore<'_> {
834 self.get_parameter_store_with_user_and_options(None, parameter_store_name, options)
835 }
836
837 fn get_parameter_store_with_user_and_options(
838 &self,
839 user: Option<&StatsigUser>,
840 parameter_store_name: &str,
841 options: ParameterStoreEvaluationOptions,
842 ) -> ParameterStore<'_> {
843 let store_name_intern = InternedString::from_str_ref(parameter_store_name);
844
845 self.event_logger
846 .increment_non_exposure_checks(parameter_store_name);
847
848 let data = read_lock_or_else!(self.spec_store.data, {
849 log_error_to_statsig_and_console!(
850 self.ops_stats.clone(),
851 TAG,
852 StatsigErr::LockFailure(
853 "Failed to acquire read lock for spec store data".to_string()
854 )
855 );
856 return ParameterStore {
857 name: parameter_store_name.to_string(),
858 parameters: HashMap::new(),
859 details: EvaluationDetails::unrecognized_no_data(),
860 options,
861 _statsig_ref: self,
862 };
863 });
864
865 if let Some(user) = user {
866 if let Some((override_result, parameters)) =
867 self.get_parameter_store_override(user, parameter_store_name)
868 {
869 let details = EvaluationDetails::recognized_but_overridden(
870 data.values.time,
871 data.time_received_at,
872 override_result.override_reason.unwrap_or("Override"),
873 override_result.version,
874 );
875
876 return ParameterStore {
877 name: parameter_store_name.to_string(),
878 parameters,
879 details,
880 options,
881 _statsig_ref: self,
882 };
883 }
884 }
885
886 let stores = &data.values.param_stores;
887 let store = match stores {
888 Some(stores) => stores.get(&store_name_intern),
889 None => {
890 return ParameterStore {
891 name: parameter_store_name.to_string(),
892 parameters: HashMap::new(),
893 details: EvaluationDetails::unrecognized(
894 &data.source,
895 data.values.time,
896 data.time_received_at,
897 ),
898 options,
899 _statsig_ref: self,
900 };
901 }
902 };
903 match store {
904 Some(store) => ParameterStore {
905 name: parameter_store_name.to_string(),
906 parameters: store.parameters.clone(),
907 details: EvaluationDetails::recognized(
908 &data.source,
909 data.values.time,
910 data.time_received_at,
911 &EvaluatorResult::default(),
912 ),
913 options,
914 _statsig_ref: self,
915 },
916 None => ParameterStore {
917 name: parameter_store_name.to_string(),
918 parameters: HashMap::new(),
919 details: EvaluationDetails::unrecognized(
920 &data.source,
921 data.values.time,
922 data.time_received_at,
923 ),
924 options,
925 _statsig_ref: self,
926 },
927 }
928 }
929
930 pub(crate) fn get_parameter_store_override(
931 &self,
932 user: &StatsigUser,
933 parameter_store_name: &str,
934 ) -> Option<(EvaluatorResult, HashMap<String, Parameter>)> {
935 let adapter = self.override_adapter.as_ref()?;
936
937 let mut result = EvaluatorResult::default();
938 if !adapter.get_parameter_store_override(user, parameter_store_name, &mut result) {
939 return None;
940 }
941
942 let mut parameters = HashMap::new();
943 if let Some(json_value) = &result.json_value {
944 if let Some(map) = json_value.get_json() {
945 for (param_name, param_value) in map {
946 if let Ok(parameter) = serde_json::from_value::<Parameter>(param_value) {
947 parameters.insert(param_name, parameter);
948 }
949 }
950 }
951 }
952
953 Some((result, parameters))
954 }
955}
956
957impl Statsig {
960 pub fn identify(&self, user: &StatsigUser) {
961 let user_internal = self.internalize_user(user);
962
963 self.event_logger.enqueue(EnqueuePassthroughOp {
964 event: StatsigEventInternal::new_custom_event(
965 user_internal.to_loggable(),
966 "statsig::identify".to_string(),
967 None,
968 None,
969 ),
970 });
971 }
972}
973
974impl Statsig {
977 pub fn get_cmab_ranked_groups(
978 &self,
979 user: &StatsigUser,
980 cmab_name: &str,
981 ) -> Vec<CMABRankedGroup> {
982 self.event_logger.increment_non_exposure_checks(cmab_name);
983
984 let data = read_lock_or_else!(self.spec_store.data, {
985 log_error_to_statsig_and_console!(
986 self.ops_stats.clone(),
987 TAG,
988 StatsigErr::LockFailure(
989 "Failed to acquire read lock for spec store data".to_string()
990 )
991 );
992 return vec![];
993 });
994 let user_internal = self.internalize_user(user);
995 let mut context = self.create_standard_eval_context(
996 &user_internal,
997 &data,
998 data.values.app_id.as_ref(),
999 self.override_adapter.as_ref(),
1000 true,
1001 );
1002 get_cmab_ranked_list(&mut context, cmab_name)
1003 }
1004
1005 pub fn log_cmab_exposure_for_group(
1006 &self,
1007 user: &StatsigUser,
1008 cmab_name: &str,
1009 group_id: String,
1010 ) {
1011 let user_internal = self.internalize_user(user);
1012
1013 let mut experiment = self.get_experiment_impl(&user_internal, cmab_name, None);
1014 experiment.rule_id = group_id;
1015
1016 self.event_logger.enqueue(EnqueueExperimentExpoOp {
1017 exposure_time: Utc::now().timestamp_millis() as u64,
1018 user: &user_internal,
1019 experiment: &experiment,
1020 trigger: ExposureTrigger::Manual,
1021 });
1022 }
1023}
1024
1025impl Statsig {
1028 pub fn override_gate(&self, gate_name: &str, value: bool, id: Option<&str>) {
1029 if let Some(adapter) = &self.override_adapter {
1030 adapter.override_gate(gate_name, value, id);
1031 }
1032 }
1033
1034 pub fn override_dynamic_config(
1035 &self,
1036 config_name: &str,
1037 value: HashMap<String, serde_json::Value>,
1038 id: Option<&str>,
1039 ) {
1040 if let Some(adapter) = &self.override_adapter {
1041 adapter.override_dynamic_config(config_name, value, id);
1042 }
1043 }
1044
1045 pub fn override_layer(
1046 &self,
1047 layer_name: &str,
1048 value: HashMap<String, serde_json::Value>,
1049 id: Option<&str>,
1050 ) {
1051 if let Some(adapter) = &self.override_adapter {
1052 adapter.override_layer(layer_name, value, id);
1053 }
1054 }
1055
1056 pub fn override_parameter_store(
1057 &self,
1058 param_name: &str,
1059 value: HashMap<String, serde_json::Value>,
1060 id: Option<&str>,
1061 ) {
1062 if let Some(adapter) = &self.override_adapter {
1063 adapter.override_parameter_store(param_name, value, id);
1064 }
1065 }
1066
1067 pub fn override_experiment(
1068 &self,
1069 experiment_name: &str,
1070 value: HashMap<String, serde_json::Value>,
1071 id: Option<&str>,
1072 ) {
1073 if let Some(adapter) = &self.override_adapter {
1074 adapter.override_experiment(experiment_name, value, id);
1075 }
1076 }
1077
1078 pub fn override_experiment_by_group_name(
1079 &self,
1080 experiment_name: &str,
1081 group_name: &str,
1082 id: Option<&str>,
1083 ) {
1084 if let Some(adapter) = &self.override_adapter {
1085 adapter.override_experiment_by_group_name(experiment_name, group_name, id);
1086 }
1087 }
1088
1089 pub fn remove_gate_override(&self, gate_name: &str, id: Option<&str>) {
1090 if let Some(adapter) = &self.override_adapter {
1091 adapter.remove_gate_override(gate_name, id);
1092 }
1093 }
1094
1095 pub fn remove_dynamic_config_override(&self, config_name: &str, id: Option<&str>) {
1096 if let Some(adapter) = &self.override_adapter {
1097 adapter.remove_dynamic_config_override(config_name, id);
1098 }
1099 }
1100
1101 pub fn remove_experiment_override(&self, experiment_name: &str, id: Option<&str>) {
1102 if let Some(adapter) = &self.override_adapter {
1103 adapter.remove_experiment_override(experiment_name, id);
1104 }
1105 }
1106
1107 pub fn remove_layer_override(&self, layer_name: &str, id: Option<&str>) {
1108 if let Some(adapter) = &self.override_adapter {
1109 adapter.remove_layer_override(layer_name, id);
1110 }
1111 }
1112
1113 pub fn remove_parameter_store_override(&self, parameter_store_name: &str, id: Option<&str>) {
1114 if let Some(adapter) = &self.override_adapter {
1115 adapter.remove_parameter_store_override(parameter_store_name, id);
1116 }
1117 }
1118
1119 pub fn remove_all_overrides(&self) {
1120 if let Some(adapter) = &self.override_adapter {
1121 adapter.remove_all_overrides();
1122 }
1123 }
1124}
1125
1126impl Statsig {
1129 pub fn get_feature_gate_list(&self) -> Vec<String> {
1130 self.spec_store
1131 .unperformant_keys_entity_filter("feature_gates", "feature_gate")
1132 }
1133
1134 pub fn get_dynamic_config_list(&self) -> Vec<String> {
1135 self.spec_store
1136 .unperformant_keys_entity_filter("dynamic_configs", "dynamic_config")
1137 }
1138
1139 pub fn get_experiment_list(&self) -> Vec<String> {
1140 self.spec_store
1141 .unperformant_keys_entity_filter("dynamic_configs", "experiment")
1142 }
1143
1144 pub fn get_autotune_list(&self) -> Vec<String> {
1145 self.spec_store
1146 .unperformant_keys_entity_filter("dynamic_configs", "autotune")
1147 }
1148
1149 pub fn get_parameter_store_list(&self) -> Vec<String> {
1150 self.spec_store
1151 .unperformant_keys_entity_filter("param_stores", "*")
1152 }
1153
1154 pub fn get_layer_list(&self) -> Vec<String> {
1155 self.spec_store
1156 .unperformant_keys_entity_filter("layer_configs", "*")
1157 }
1158
1159 pub fn __get_parsed_user_agent_value(
1160 &self,
1161 user: &StatsigUser,
1162 ) -> Option<ParsedUserAgentValue> {
1163 UserAgentParser::get_parsed_user_agent_value_for_user(user, &self.options)
1164 }
1165}
1166
1167impl Statsig {
1170 pub fn check_gate(&self, user: &StatsigUser, gate_name: &str) -> bool {
1171 self.check_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1172 }
1173
1174 pub fn check_gate_with_options(
1175 &self,
1176 user: &StatsigUser,
1177 gate_name: &str,
1178 options: FeatureGateEvaluationOptions,
1179 ) -> bool {
1180 let user_internal = self.internalize_user(user);
1181 let disable_exposure_logging = options.disable_exposure_logging;
1182 let (details, evaluation) = self.get_gate_evaluation(
1183 &user_internal,
1184 gate_name,
1185 Some(options.disable_exposure_logging),
1186 );
1187
1188 let value = evaluation.as_ref().map(|e| e.value).unwrap_or_default();
1189 let rule_id = evaluation
1190 .as_ref()
1191 .map(|e| e.base.rule_id.clone())
1192 .unwrap_or_default();
1193
1194 if disable_exposure_logging {
1195 log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1196 self.event_logger.increment_non_exposure_checks(gate_name);
1197 } else {
1198 self.event_logger.enqueue(EnqueueGateExpoOp {
1199 exposure_time: Utc::now().timestamp_millis() as u64,
1200 user: &user_internal,
1201 queried_gate_name: gate_name,
1202 evaluation: evaluation.map(Cow::Owned),
1203 details: details.clone(),
1204 trigger: ExposureTrigger::Auto,
1205 });
1206 }
1207
1208 self.emit_gate_evaluated(gate_name, rule_id.as_str(), value, &details.reason);
1209
1210 value
1211 }
1212
1213 pub fn get_feature_gate(&self, user: &StatsigUser, gate_name: &str) -> FeatureGate {
1214 self.get_feature_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1215 }
1216
1217 pub fn get_feature_gate_with_options(
1218 &self,
1219 user: &StatsigUser,
1220 gate_name: &str,
1221 options: FeatureGateEvaluationOptions,
1222 ) -> FeatureGate {
1223 let user_internal = self.internalize_user(user);
1224 let disable_exposure_logging = options.disable_exposure_logging;
1225 let (details, evaluation) = self.get_gate_evaluation(
1226 &user_internal,
1227 gate_name,
1228 Some(options.disable_exposure_logging),
1229 );
1230
1231 if disable_exposure_logging {
1232 log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1233 self.event_logger.increment_non_exposure_checks(gate_name);
1234 } else {
1235 self.event_logger.enqueue(EnqueueGateExpoOp {
1236 exposure_time: Utc::now().timestamp_millis() as u64,
1237 user: &user_internal,
1238 queried_gate_name: gate_name,
1239 evaluation: evaluation.as_ref().map(Cow::Borrowed),
1240 details: details.clone(),
1241 trigger: ExposureTrigger::Auto,
1242 });
1243 }
1244
1245 let gate = make_feature_gate(gate_name, evaluation, details);
1246 self.emit_gate_evaluated(gate_name, &gate.rule_id, gate.value, &gate.details.reason);
1247 gate
1248 }
1249
1250 pub fn manually_log_gate_exposure(&self, user: &StatsigUser, gate_name: &str) {
1251 let interned_gate_name = InternedString::from_str_ref(gate_name);
1252 let user_internal = self.internalize_user(user);
1253
1254 let (details, evaluation) =
1255 self.evaluate_spec_raw(&user_internal, gate_name, &SpecType::Gate, None);
1256
1257 self.event_logger.enqueue(EnqueueExposureOp::gate_exposure(
1258 &user_internal,
1259 &interned_gate_name,
1260 ExposureTrigger::Manual,
1261 details,
1262 evaluation,
1263 ));
1264 }
1265
1266 pub fn get_fields_needed_for_gate(&self, gate_name: &str) -> Vec<String> {
1267 self.spec_store
1268 .get_fields_used_for_entity(gate_name, SpecType::Gate)
1269 }
1270}
1271
1272impl Statsig {
1275 pub fn get_dynamic_config(
1276 &self,
1277 user: &StatsigUser,
1278 dynamic_config_name: &str,
1279 ) -> DynamicConfig {
1280 self.get_dynamic_config_with_options(
1281 user,
1282 dynamic_config_name,
1283 DynamicConfigEvaluationOptions::default(),
1284 )
1285 }
1286
1287 pub fn get_dynamic_config_with_options(
1288 &self,
1289 user: &StatsigUser,
1290 dynamic_config_name: &str,
1291 options: DynamicConfigEvaluationOptions,
1292 ) -> DynamicConfig {
1293 let user_internal = self.internalize_user(user);
1294 let disable_exposure_logging = options.disable_exposure_logging;
1295 let dynamic_config = self.get_dynamic_config_impl(
1296 &user_internal,
1297 dynamic_config_name,
1298 Some(options.disable_exposure_logging),
1299 );
1300
1301 if disable_exposure_logging {
1302 log_d!(
1303 TAG,
1304 "Exposure logging is disabled for Dynamic Config {}",
1305 dynamic_config_name
1306 );
1307 self.event_logger
1308 .increment_non_exposure_checks(dynamic_config_name);
1309 } else {
1310 self.event_logger.enqueue(EnqueueConfigExpoOp {
1311 exposure_time: Utc::now().timestamp_millis() as u64,
1312 user: &user_internal,
1313 config: &dynamic_config,
1314 trigger: ExposureTrigger::Auto,
1315 });
1316 }
1317
1318 self.emit_dynamic_config_evaluated(&dynamic_config);
1319
1320 dynamic_config
1321 }
1322
1323 pub fn manually_log_dynamic_config_exposure(
1324 &self,
1325 user: &StatsigUser,
1326 dynamic_config_name: &str,
1327 ) {
1328 let interned_dynamic_config_name = InternedString::from_str_ref(dynamic_config_name);
1329 let user_internal = self.internalize_user(user);
1330
1331 let (details, evaluation) = self.evaluate_spec_raw(
1332 &user_internal,
1333 dynamic_config_name,
1334 &SpecType::DynamicConfig,
1335 None,
1336 );
1337
1338 self.event_logger
1339 .enqueue(EnqueueExposureOp::dynamic_config_exposure(
1340 &user_internal,
1341 &interned_dynamic_config_name,
1342 ExposureTrigger::Manual,
1343 details,
1344 evaluation,
1345 ));
1346 }
1347
1348 pub fn get_fields_needed_for_dynamic_config(&self, config_name: &str) -> Vec<String> {
1349 self.spec_store
1350 .get_fields_used_for_entity(config_name, SpecType::DynamicConfig)
1351 }
1352}
1353
1354impl Statsig {
1357 pub fn get_experiment(&self, user: &StatsigUser, experiment_name: &str) -> Experiment {
1358 self.get_experiment_with_options(
1359 user,
1360 experiment_name,
1361 ExperimentEvaluationOptions::default(),
1362 )
1363 }
1364
1365 pub fn get_experiment_with_options(
1366 &self,
1367 user: &StatsigUser,
1368 experiment_name: &str,
1369 options: ExperimentEvaluationOptions,
1370 ) -> Experiment {
1371 let user_internal = self.internalize_user(user);
1372 let disable_exposure_logging = options.disable_exposure_logging;
1373 let mut experiment = self.get_experiment_impl(
1374 &user_internal,
1375 experiment_name,
1376 Some(options.disable_exposure_logging),
1377 );
1378
1379 experiment = PersistentValuesManager::try_apply_sticky_value_to_experiment(
1380 &self.persistent_values_manager,
1381 &user_internal,
1382 &options,
1383 experiment,
1384 );
1385
1386 if disable_exposure_logging {
1387 log_d!(
1388 TAG,
1389 "Exposure logging is disabled for experiment {}",
1390 experiment_name
1391 );
1392 self.event_logger
1393 .increment_non_exposure_checks(experiment_name);
1394 } else {
1395 self.event_logger.enqueue(EnqueueExperimentExpoOp {
1396 exposure_time: Utc::now().timestamp_millis() as u64,
1397 user: &user_internal,
1398 experiment: &experiment,
1399 trigger: ExposureTrigger::Auto,
1400 });
1401 }
1402
1403 self.emit_experiment_evaluated(&experiment);
1404
1405 experiment
1406 }
1407
1408 pub fn manually_log_experiment_exposure(&self, user: &StatsigUser, experiment_name: &str) {
1409 let interned_experiment_name = InternedString::from_str_ref(experiment_name);
1410 let user_internal = self.internalize_user(user);
1411 let (details, evaluation) =
1412 self.evaluate_spec_raw(&user_internal, experiment_name, &SpecType::Experiment, None);
1413
1414 self.event_logger
1415 .enqueue(EnqueueExposureOp::experiment_exposure(
1416 &user_internal,
1417 &interned_experiment_name,
1418 ExposureTrigger::Manual,
1419 details,
1420 evaluation,
1421 ));
1422 }
1423
1424 pub fn get_fields_needed_for_experiment(&self, experiment_name: &str) -> Vec<String> {
1425 self.spec_store
1426 .get_fields_used_for_entity(experiment_name, SpecType::Experiment)
1427 }
1428
1429 pub fn get_experiment_by_group_name(
1430 &self,
1431 experiment_name: &str,
1432 group_name: &str,
1433 ) -> Experiment {
1434 self.get_experiment_by_group_name_impl(
1435 experiment_name,
1436 group_name,
1437 |spec_pointer, rule, details| {
1438 if let (Some(spec_pointer), Some(rule)) = (spec_pointer, rule) {
1439 let value = rule.return_value.get_json().unwrap_or_default();
1440 let rule_id = String::from(rule.id.as_str());
1441 let id_type = rule.id_type.value.unperformant_to_string();
1442 let group_name = rule.group_name.as_ref().map(|g| g.unperformant_to_string());
1443
1444 return Experiment {
1445 name: experiment_name.to_string(),
1446 value,
1447 rule_id,
1448 id_type,
1449 group_name,
1450 details,
1451 is_experiment_active: spec_pointer.as_spec_ref().is_active.unwrap_or(false),
1452 __evaluation: None,
1453 };
1454 }
1455
1456 make_experiment(experiment_name, None, details)
1457 },
1458 )
1459 }
1460
1461 pub fn get_experiment_by_group_id_advanced(
1462 &self,
1463 experiment_name: &str,
1464 group_id: &str,
1465 ) -> Experiment {
1466 self.get_experiment_by_group_id_advanced_impl(
1467 experiment_name,
1468 group_id,
1469 |spec_pointer, rule, details| {
1470 if let (Some(spec_pointer), Some(rule)) = (spec_pointer, rule) {
1471 let value = rule.return_value.get_json().unwrap_or_default();
1472 let rule_id = String::from(rule.id.as_str());
1473 let id_type = rule.id_type.value.unperformant_to_string();
1474 let group_name = rule.group_name.as_ref().map(|g| g.unperformant_to_string());
1475
1476 return Experiment {
1477 name: experiment_name.to_string(),
1478 value,
1479 rule_id,
1480 id_type,
1481 group_name,
1482 details,
1483 is_experiment_active: spec_pointer.as_spec_ref().is_active.unwrap_or(false),
1484 __evaluation: None,
1485 };
1486 }
1487
1488 make_experiment(experiment_name, None, details)
1489 },
1490 )
1491 }
1492
1493 fn get_experiment_by_group_name_impl<T>(
1494 &self,
1495 experiment_name: &str,
1496 group_name: &str,
1497 result_factory: impl FnOnce(Option<&SpecPointer>, Option<&Rule>, EvaluationDetails) -> T,
1498 ) -> T {
1499 self.get_experiment_by_rule_match_impl(
1500 experiment_name,
1501 |rule| rule.group_name.as_deref() == Some(group_name),
1502 result_factory,
1503 )
1504 }
1505
1506 fn get_experiment_by_group_id_advanced_impl<T>(
1507 &self,
1508 experiment_name: &str,
1509 rule_id: &str,
1510 result_factory: impl FnOnce(Option<&SpecPointer>, Option<&Rule>, EvaluationDetails) -> T,
1511 ) -> T {
1512 self.get_experiment_by_rule_match_impl(
1513 experiment_name,
1514 |rule| rule.id.as_str() == rule_id,
1515 result_factory,
1516 )
1517 }
1518
1519 fn get_experiment_by_rule_match_impl<T, P>(
1520 &self,
1521 experiment_name: &str,
1522 rule_predicate: P,
1523 result_factory: impl FnOnce(Option<&SpecPointer>, Option<&Rule>, EvaluationDetails) -> T,
1524 ) -> T
1525 where
1526 P: Fn(&Rule) -> bool,
1527 {
1528 let data = read_lock_or_else!(self.spec_store.data, {
1529 log_error_to_statsig_and_console!(
1530 self.ops_stats.clone(),
1531 TAG,
1532 StatsigErr::LockFailure(
1533 "Failed to acquire read lock for spec store data".to_string()
1534 )
1535 );
1536 return result_factory(
1537 None,
1538 None,
1539 EvaluationDetails::error("Failed to acquire read lock for spec store data"),
1540 );
1541 });
1542
1543 let experiment_name = InternedString::from_str_ref(experiment_name);
1544 let experiment = data.values.dynamic_configs.get(&experiment_name);
1545
1546 let Some(exp) = experiment else {
1547 return result_factory(
1548 None,
1549 None,
1550 EvaluationDetails::unrecognized(
1551 &data.source,
1552 data.values.time,
1553 data.time_received_at,
1554 ),
1555 );
1556 };
1557
1558 if let Some(rule) = exp
1559 .as_spec_ref()
1560 .rules
1561 .iter()
1562 .find(|rule| rule_predicate(rule))
1563 {
1564 return result_factory(
1565 Some(exp),
1566 Some(rule),
1567 EvaluationDetails::recognized_without_eval_result(
1568 &data.source,
1569 data.values.time,
1570 data.time_received_at,
1571 ),
1572 );
1573 }
1574
1575 result_factory(
1576 None,
1577 None,
1578 EvaluationDetails::unrecognized(&data.source, data.values.time, data.time_received_at),
1579 )
1580 }
1581}
1582
1583impl Statsig {
1586 pub fn get_layer(&self, user: &StatsigUser, layer_name: &str) -> Layer {
1587 self.get_layer_with_options(user, layer_name, LayerEvaluationOptions::default())
1588 }
1589
1590 pub fn get_layer_with_options(
1591 &self,
1592 user: &StatsigUser,
1593 layer_name: &str,
1594 options: LayerEvaluationOptions,
1595 ) -> Layer {
1596 let user_internal = self.internalize_user(user);
1597 self.get_layer_impl(user_internal, layer_name, options)
1598 }
1599
1600 pub fn manually_log_layer_parameter_exposure(
1601 &self,
1602 user: &StatsigUser,
1603 layer_name: &str,
1604 parameter_name: String,
1605 ) {
1606 let interned_layer_name = InternedString::from_str_ref(layer_name);
1607 let interned_parameter_name = InternedString::from_string(parameter_name);
1608 let user_internal = self.internalize_user(user);
1609 let (details, evaluation) =
1610 self.evaluate_spec_raw(&user_internal, layer_name, &SpecType::Layer, None);
1611
1612 self.event_logger
1613 .enqueue(EnqueueExposureOp::layer_param_exposure(
1614 &user_internal,
1615 &interned_layer_name,
1616 interned_parameter_name,
1617 ExposureTrigger::Manual,
1618 details,
1619 evaluation,
1620 ));
1621 }
1622
1623 pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> {
1624 self.spec_store
1625 .get_fields_used_for_entity(layer_name, SpecType::Layer)
1626 }
1627}
1628
1629#[cfg(feature = "ffi-support")]
1632impl Statsig {
1633 pub fn get_raw_feature_gate_with_options(
1634 &self,
1635 user: &StatsigUser,
1636 gate_name: &str,
1637 options: FeatureGateEvaluationOptions,
1638 ) -> String {
1639 use crate::evaluation::evaluator_result::result_to_gate_raw;
1640
1641 let interned_gate_name = InternedString::from_str_ref(gate_name);
1642 let user_internal = self.internalize_user(user);
1643
1644 let (details, evaluation) =
1645 self.evaluate_spec_raw(&user_internal, gate_name, &SpecType::Gate, None);
1646
1647 let raw = result_to_gate_raw(gate_name, &details, evaluation.as_ref());
1648
1649 self.emit_gate_evaluated_parts(gate_name, details.reason.as_str(), evaluation.as_ref());
1650
1651 if options.disable_exposure_logging {
1652 log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1653 self.event_logger.increment_non_exposure_checks(gate_name);
1654 } else {
1655 self.event_logger.enqueue(EnqueueExposureOp::gate_exposure(
1656 &user_internal,
1657 &interned_gate_name,
1658 ExposureTrigger::Auto,
1659 details,
1660 evaluation,
1661 ));
1662 }
1663
1664 raw
1665 }
1666
1667 pub fn get_raw_dynamic_config_with_options(
1668 &self,
1669 user: &StatsigUser,
1670 dynamic_config_name: &str,
1671 options: DynamicConfigEvaluationOptions,
1672 ) -> String {
1673 use crate::evaluation::evaluator_result::result_to_dynamic_config_raw;
1674
1675 let interned_dynamic_config_name = InternedString::from_str_ref(dynamic_config_name);
1676 let user_internal = self.internalize_user(user);
1677 let disable_exposure_logging: bool = options.disable_exposure_logging;
1678
1679 let (details, evaluation) = self.evaluate_spec_raw(
1680 &user_internal,
1681 dynamic_config_name,
1682 &SpecType::DynamicConfig,
1683 Some(disable_exposure_logging),
1684 );
1685
1686 let raw = result_to_dynamic_config_raw(dynamic_config_name, &details, evaluation.as_ref());
1687
1688 self.emit_dynamic_config_evaluated_parts(
1689 dynamic_config_name,
1690 details.reason.as_str(),
1691 evaluation.as_ref(),
1692 );
1693
1694 if disable_exposure_logging {
1695 log_d!(
1696 TAG,
1697 "Exposure logging is disabled for Dynamic Config {}",
1698 dynamic_config_name
1699 );
1700 self.event_logger
1701 .increment_non_exposure_checks(dynamic_config_name);
1702 } else {
1703 self.event_logger
1704 .enqueue(EnqueueExposureOp::dynamic_config_exposure(
1705 &user_internal,
1706 &interned_dynamic_config_name,
1707 ExposureTrigger::Auto,
1708 details,
1709 evaluation,
1710 ));
1711 }
1712
1713 raw
1714 }
1715
1716 pub fn get_raw_experiment_by_group_name(
1717 &self,
1718 experiment_name: &str,
1719 group_name: &str,
1720 ) -> String {
1721 use crate::evaluation::evaluator_result::rule_to_experiment_raw;
1722
1723 self.get_experiment_by_group_name_impl(
1724 experiment_name,
1725 group_name,
1726 |spec_pointer, rule, details| {
1727 rule_to_experiment_raw(experiment_name, spec_pointer, rule, details)
1728 },
1729 )
1730 }
1731
1732 pub fn get_raw_experiment_by_group_id_advanced(
1733 &self,
1734 experiment_name: &str,
1735 group_id: &str,
1736 ) -> String {
1737 use crate::evaluation::evaluator_result::rule_to_experiment_raw;
1738
1739 self.get_experiment_by_group_id_advanced_impl(
1740 experiment_name,
1741 group_id,
1742 |spec_pointer, rule, details| {
1743 rule_to_experiment_raw(experiment_name, spec_pointer, rule, details)
1744 },
1745 )
1746 }
1747
1748 pub fn get_raw_experiment_with_options(
1749 &self,
1750 user: &StatsigUser,
1751 experiment_name: &str,
1752 options: ExperimentEvaluationOptions,
1753 ) -> String {
1754 use crate::evaluation::evaluator_result::result_to_experiment_raw;
1755
1756 let interned_experiment_name = InternedString::from_str_ref(experiment_name);
1757 let user_internal = self.internalize_user(user);
1758 let disable_exposure_logging: bool = options.disable_exposure_logging;
1759
1760 let (details, result) = self.evaluate_spec_raw(
1761 &user_internal,
1762 experiment_name,
1763 &SpecType::Experiment,
1764 Some(disable_exposure_logging),
1765 );
1766
1767 let (result, details) = PersistentValuesManager::try_apply_sticky_value_to_raw_experiment(
1768 &self.persistent_values_manager,
1769 &user_internal,
1770 &options,
1771 details,
1772 result,
1773 );
1774
1775 let raw = result_to_experiment_raw(experiment_name, &details, result.as_ref());
1776
1777 self.emit_experiment_evaluated_parts(
1778 experiment_name,
1779 details.reason.as_str(),
1780 result.as_ref(),
1781 );
1782
1783 if disable_exposure_logging {
1784 log_d!(
1785 TAG,
1786 "Exposure logging is disabled for Experiment {}",
1787 experiment_name
1788 );
1789 self.event_logger
1790 .increment_non_exposure_checks(experiment_name);
1791 } else {
1792 self.event_logger
1793 .enqueue(EnqueueExposureOp::dynamic_config_exposure(
1794 &user_internal,
1795 &interned_experiment_name,
1796 ExposureTrigger::Auto,
1797 details,
1798 result,
1799 ));
1800 }
1801
1802 raw
1803 }
1804
1805 pub fn get_raw_layer_with_options(
1806 &self,
1807 user: &StatsigUser,
1808 layer_name: &str,
1809 options: LayerEvaluationOptions,
1810 ) -> String {
1811 use crate::evaluation::evaluator_result::result_to_layer_raw;
1812
1813 let user_internal = self.internalize_user(user);
1814 let disable_exposure_logging: bool = options.disable_exposure_logging;
1815
1816 let (details, result) = self.evaluate_spec_raw(
1817 &user_internal,
1818 layer_name,
1819 &SpecType::Layer,
1820 Some(disable_exposure_logging),
1821 );
1822
1823 let (result, details) = PersistentValuesManager::try_apply_sticky_value_to_raw_layer(
1824 &self.persistent_values_manager,
1825 &user_internal,
1826 &options,
1827 &self.spec_store,
1828 &self.ops_stats,
1829 details,
1830 result,
1831 );
1832
1833 let raw = result_to_layer_raw(
1834 &user_internal,
1835 layer_name,
1836 options,
1837 &details,
1838 result.as_ref(),
1839 );
1840
1841 self.emit_layer_evaluated_parts(layer_name, details.reason.as_str(), result.as_ref());
1842
1843 if disable_exposure_logging {
1844 log_d!(TAG, "Exposure logging is disabled for Layer {}", layer_name);
1845 self.event_logger.increment_non_exposure_checks(layer_name);
1846 }
1847
1848 raw
1849 }
1850
1851 pub fn log_layer_param_exposure_from_raw(&self, raw: String, param_name: String) {
1852 use crate::statsig_types_raw::PartialLayerRaw;
1853
1854 let partial_raw = match serde_json::from_str::<PartialLayerRaw>(&raw) {
1855 Ok(partial_raw) => partial_raw,
1856 Err(e) => {
1857 log_e!(TAG, "Failed to parse partial layer raw: {}", e);
1858 return;
1859 }
1860 };
1861
1862 if partial_raw.disable_exposure {
1863 self.event_logger
1864 .increment_non_exposure_checks(&partial_raw.name);
1865 return;
1866 }
1867
1868 let interned_parameter_name = InternedString::from_string(param_name);
1869
1870 self.event_logger
1871 .enqueue(EnqueueExposureOp::layer_param_exposure_from_partial_raw(
1872 interned_parameter_name,
1873 ExposureTrigger::Auto,
1874 partial_raw,
1875 ));
1876 }
1877}
1878
1879impl Statsig {
1882 pub(crate) fn get_from_statsig_env(&self, key: &str) -> Option<DynamicValue> {
1883 if let Some(env) = &self.statsig_environment {
1884 return env.get(key).cloned();
1885 }
1886
1887 if let Some(fallback_env) = self
1888 .fallback_environment
1889 .try_lock_for(Duration::from_secs(5))
1890 {
1891 if let Some(env) = &*fallback_env {
1892 return env.get(key).cloned();
1893 }
1894 }
1895
1896 None
1897 }
1898
1899 pub(crate) fn get_value_from_global_custom_fields(&self, key: &str) -> Option<&DynamicValue> {
1900 if let Some(env) = &self.options.global_custom_fields {
1901 return env.get(key);
1902 }
1903
1904 None
1905 }
1906
1907 pub(crate) fn use_global_custom_fields<T>(
1908 &self,
1909 f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1910 ) -> T {
1911 f(self.options.global_custom_fields.as_ref())
1912 }
1913
1914 pub(crate) fn use_statsig_env<T>(
1915 &self,
1916 f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1917 ) -> T {
1918 if let Some(env) = &self.statsig_environment {
1919 return f(Some(env));
1920 }
1921
1922 if let Some(fallback_env) = self
1923 .fallback_environment
1924 .try_lock_for(Duration::from_secs(5))
1925 {
1926 if let Some(env) = &*fallback_env {
1927 return f(Some(env));
1928 }
1929 }
1930
1931 f(None)
1932 }
1933}
1934
1935impl Statsig {
1938 async fn start_background_tasks(
1939 statsig_runtime: Arc<StatsigRuntime>,
1940 id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
1941 specs_adapter: Arc<dyn SpecsAdapter>,
1942 ops_stats: Arc<OpsStatsForInstance>,
1943 bg_tasks_started: Arc<AtomicBool>,
1944 ) -> bool {
1945 if bg_tasks_started.load(Ordering::SeqCst) {
1946 return true;
1947 }
1948
1949 let mut success = true;
1950
1951 if let Some(adapter) = &id_lists_adapter {
1952 if let Err(e) = adapter
1953 .clone()
1954 .schedule_background_sync(&statsig_runtime)
1955 .await
1956 {
1957 success = false;
1958 log_w!(TAG, "Failed to schedule idlist background job {}", e);
1959 }
1960 }
1961
1962 if let Err(e) = specs_adapter
1963 .clone()
1964 .schedule_background_sync(&statsig_runtime)
1965 .await
1966 {
1967 success = false;
1968 log_error_to_statsig_and_console!(
1969 ops_stats,
1970 TAG,
1971 StatsigErr::SpecsAdapterSkipPoll(format!(
1972 "Failed to schedule specs adapter background job: {e}"
1973 ))
1974 );
1975 }
1976
1977 bg_tasks_started.store(true, Ordering::SeqCst);
1978
1979 success
1980 }
1981
1982 async fn apply_timeout_to_init(
1983 &self,
1984 timeout_ms: u64,
1985 ) -> Result<InitializeDetails, StatsigErr> {
1986 let timeout = Duration::from_millis(timeout_ms);
1987
1988 let init_future = self.initialize_impl_with_details();
1989 let timeout_future = sleep(timeout);
1990
1991 let statsig_runtime = self.statsig_runtime.clone();
1992 let id_lists_adapter = self.id_lists_adapter.inner.clone();
1993 let specs_adapter = self.specs_adapter.inner.clone();
1994 let ops_stats = self.ops_stats.clone();
1995 let background_tasks_started = self.background_tasks_started.clone();
1996 let statsig_runtime_for_closure = statsig_runtime.clone();
1998
1999 tokio::select! {
2000 result = init_future => {
2001 result
2002 },
2003 _ = timeout_future => {
2004 statsig_runtime.spawn(
2005 "start_background_tasks",
2006 |_shutdown_notify| async move {
2007 Self::start_background_tasks(
2008 statsig_runtime_for_closure,
2009 id_lists_adapter,
2010 specs_adapter,
2011 ops_stats,
2012 background_tasks_started,
2013 ).await;
2014 }
2015 )?;
2016 Ok(InitializeDetails::from_timeout_failure(timeout_ms))
2017 },
2018 }
2019 }
2020
2021 async fn initialize_impl_with_details(&self) -> Result<InitializeDetails, StatsigErr> {
2022 let start_time = Instant::now();
2023 self.spec_store.set_source(SpecsSource::Loading);
2024 self.specs_adapter.inner.initialize(self.spec_store.clone());
2025 let use_third_party_ua_parser = self.should_user_third_party_parser();
2026
2027 let mut error_message = None;
2028 let mut id_list_ready = None;
2029
2030 let init_country_lookup = if !self.options.disable_country_lookup.unwrap_or_default() {
2031 Some(self.statsig_runtime.spawn(INIT_IP_TAG, |_| async {
2032 CountryLookup::load_country_lookup();
2033 }))
2034 } else {
2035 None
2036 };
2037
2038 let init_ua = if use_third_party_ua_parser {
2039 Some(self.statsig_runtime.spawn(INIT_UA_TAG, |_| async {
2040 UserAgentParser::load_parser();
2041 }))
2042 } else {
2043 None
2044 };
2045
2046 let init_res = match self
2047 .specs_adapter
2048 .inner
2049 .clone()
2050 .start(&self.statsig_runtime)
2051 .await
2052 {
2053 Ok(()) => Ok(()),
2054 Err(e) => {
2055 self.spec_store.set_source(SpecsSource::NoValues);
2056 error_message = Some(format!("Failed to start specs adapter: {e}"));
2057 Err(e)
2058 }
2059 };
2060
2061 if let Some(adapter) = &self.id_lists_adapter.inner {
2062 match adapter
2063 .clone()
2064 .start(&self.statsig_runtime, self.spec_store.clone())
2065 .await
2066 {
2067 Ok(()) => {
2068 id_list_ready = Some(true);
2069 }
2070 Err(e) => {
2071 id_list_ready = Some(false);
2072 error_message.get_or_insert_with(|| format!("Failed to sync ID lists: {e}"));
2073 }
2074 }
2075 }
2076
2077 if let Err(e) = self
2078 .event_logging_adapter
2079 .clone()
2080 .start(&self.statsig_runtime)
2081 .await
2082 {
2083 log_error_to_statsig_and_console!(
2084 self.ops_stats.clone(),
2085 TAG,
2086 StatsigErr::UnstartedAdapter(format!("Failed to start event logging adapter: {e}"))
2087 );
2088 }
2089
2090 let spec_info = self.spec_store.get_current_specs_info();
2091 let duration = start_time.elapsed().as_millis() as u64;
2092
2093 self.set_default_environment_from_server();
2094
2095 if self.options.wait_for_country_lookup_init.unwrap_or(false) {
2096 match init_country_lookup {
2097 Some(Ok(task_id)) => {
2098 let _ = self
2099 .statsig_runtime
2100 .await_join_handle(INIT_IP_TAG, &task_id)
2101 .await;
2102 }
2103 Some(Err(e)) => {
2104 log_error_to_statsig_and_console!(
2105 self.ops_stats.clone(),
2106 TAG,
2107 StatsigErr::UnstartedAdapter(format!(
2108 "Failed to spawn country lookup task: {e}"
2109 ))
2110 );
2111 }
2112 _ => {}
2113 }
2114 }
2115 if self.options.wait_for_user_agent_init.unwrap_or(false) {
2116 match init_ua {
2117 Some(Ok(task_id)) => {
2118 let _ = self
2119 .statsig_runtime
2120 .await_join_handle(INIT_UA_TAG, &task_id)
2121 .await;
2122 }
2123 Some(Err(e)) => {
2124 log_error_to_statsig_and_console!(
2125 self.ops_stats.clone(),
2126 TAG,
2127 StatsigErr::UnstartedAdapter(format!(
2128 "Failed to spawn user agent parser task: {e}"
2129 ))
2130 );
2131 }
2132 _ => {}
2133 }
2134 }
2135
2136 let error = init_res.clone().err();
2137
2138 let success = Self::start_background_tasks(
2139 self.statsig_runtime.clone(),
2140 self.id_lists_adapter.inner.clone(),
2141 self.specs_adapter.inner.clone(),
2142 self.ops_stats.clone(),
2143 self.background_tasks_started.clone(),
2144 )
2145 .await;
2146
2147 Ok(InitializeDetails::new(
2148 success,
2149 duration,
2150 spec_info,
2151 id_list_ready,
2152 error,
2153 ))
2154 }
2155
2156 fn log_init_details(&self, init_details: &Result<InitializeDetails, StatsigErr>) {
2157 match init_details {
2158 Ok(details) => {
2159 self.log_init_finish(
2160 details.init_success,
2161 &None,
2162 &details.duration_ms,
2163 &self.spec_store.get_current_specs_info(),
2164 );
2165 if let Some(failure) = &details.failure_details {
2166 log_error_to_statsig_and_console!(
2167 self.ops_stats,
2168 TAG,
2169 StatsigErr::InitializationError(failure.reason.clone())
2170 );
2171 }
2172 }
2173 Err(err) => {
2174 log_w!(TAG, "Initialization error: {:?}", err);
2176 }
2177 }
2178 }
2179
2180 fn create_standard_eval_context<'a>(
2181 &'a self,
2182 user_internal: &'a StatsigUserInternal,
2183 data: &'a SpecStoreData,
2184 app_id: Option<&'a DynamicValue>,
2185 override_adapter: Option<&'a Arc<dyn OverrideAdapter>>,
2186 disable_exposure_logging: bool,
2187 ) -> EvaluatorContext<'a> {
2188 EvaluatorContext::new(
2189 user_internal,
2190 &data.values,
2191 IdListResolution::MapLookup(&data.id_lists),
2192 &self.hashing,
2193 app_id,
2194 override_adapter,
2195 self.should_user_third_party_parser(),
2196 Some(self),
2197 disable_exposure_logging,
2198 )
2199 }
2200
2201 fn create_gcir_eval_context<'a>(
2202 &'a self,
2203 user_internal: &'a StatsigUserInternal,
2204 data: &'a SpecStoreData,
2205 options: &'a ClientInitResponseOptions,
2206 ) -> EvaluatorContext<'a> {
2207 let app_id = select_app_id_for_gcir(options, &data.values, &self.hashing);
2208 let override_adapter = match options.include_local_overrides {
2209 Some(true) => self.override_adapter.as_ref(),
2210 _ => None,
2211 };
2212
2213 EvaluatorContext::new(
2214 user_internal,
2215 &data.values,
2216 IdListResolution::MapLookup(&data.id_lists),
2217 &self.hashing,
2218 app_id,
2219 override_adapter,
2220 self.should_user_third_party_parser(),
2221 None,
2222 true,
2223 )
2224 }
2225
2226 fn evaluate_spec_raw(
2227 &self,
2228 user_internal: &StatsigUserInternal,
2229 spec_name: &str,
2230 spec_type: &SpecType,
2231 disable_exposure_logging: Option<bool>,
2232 ) -> (EvaluationDetails, Option<EvaluatorResult>) {
2233 let data = read_lock_or_else!(self.spec_store.data, {
2234 log_error_to_statsig_and_console!(
2235 &self.ops_stats,
2236 TAG,
2237 StatsigErr::LockFailure(
2238 "Failed to acquire read lock for spec store data".to_string()
2239 )
2240 );
2241 return (EvaluationDetails::unrecognized_no_data(), None);
2242 });
2243
2244 let mut context = self.create_standard_eval_context(
2245 user_internal,
2246 &data,
2247 data.values.app_id.as_ref(),
2248 self.override_adapter.as_ref(),
2249 disable_exposure_logging.unwrap_or(false),
2250 );
2251
2252 match Self::evaluate_with_details(&mut context, &data, spec_name, spec_type) {
2253 Ok(eval_details) => (eval_details, Some(context.result)),
2254 Err(e) => {
2255 log_error_to_statsig_and_console!(
2256 &self.ops_stats,
2257 TAG,
2258 StatsigErr::EvaluationError(e.to_string())
2259 );
2260 (EvaluationDetails::error(&e.to_string()), None)
2261 }
2262 }
2263 }
2264
2265 #[allow(clippy::too_many_arguments)]
2266 fn evaluate_spec<T>(
2267 &self,
2268 user_internal: &StatsigUserInternal,
2269 spec_name: &str,
2270 make_empty_result: impl FnOnce(EvaluationDetails) -> T,
2271 make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
2272 spec_type: &SpecType,
2273 disable_exposure_logging: Option<bool>,
2274 ) -> T {
2275 let data = read_lock_or_else!(self.spec_store.data, {
2276 log_error_to_statsig_and_console!(
2277 &self.ops_stats,
2278 TAG,
2279 StatsigErr::LockFailure(
2280 "Failed to acquire read lock for spec store data".to_string()
2281 )
2282 );
2283 return make_empty_result(EvaluationDetails::unrecognized_no_data());
2284 });
2285
2286 let mut context = self.create_standard_eval_context(
2287 user_internal,
2288 &data,
2289 data.values.app_id.as_ref(),
2290 self.override_adapter.as_ref(),
2291 disable_exposure_logging.unwrap_or(false),
2292 );
2293
2294 match Self::evaluate_with_details(&mut context, &data, spec_name, spec_type) {
2295 Ok(eval_details) => make_result(context.result, eval_details),
2296 Err(e) => {
2297 log_error_to_statsig_and_console!(
2298 &self.ops_stats,
2299 TAG,
2300 StatsigErr::EvaluationError(e.to_string())
2301 );
2302 make_empty_result(EvaluationDetails::error(&e.to_string()))
2303 }
2304 }
2305 }
2306
2307 fn evaluate_with_details(
2308 ctx: &mut EvaluatorContext,
2309 spec_store_data: &SpecStoreData,
2310 spec_name: &str,
2311 spec_type: &SpecType,
2312 ) -> Result<EvaluationDetails, StatsigErr> {
2313 let recognition = Evaluator::evaluate(ctx, spec_name, spec_type)?;
2314
2315 if recognition == Recognition::Unrecognized {
2316 return Ok(EvaluationDetails::unrecognized(
2317 &spec_store_data.source,
2318 spec_store_data.values.time,
2319 spec_store_data.time_received_at,
2320 ));
2321 }
2322
2323 if let Some(reason) = ctx.result.override_reason {
2324 return Ok(EvaluationDetails::recognized_but_overridden(
2325 spec_store_data.values.time,
2326 spec_store_data.time_received_at,
2327 reason,
2328 ctx.result.version,
2329 ));
2330 }
2331
2332 Ok(EvaluationDetails::recognized(
2333 &spec_store_data.source,
2334 spec_store_data.values.time,
2335 spec_store_data.time_received_at,
2336 &ctx.result,
2337 ))
2338 }
2339
2340 fn stringify_gcir_response<T: Serialize>(
2341 &self,
2342 input: Result<T, StatsigErr>,
2343 fallback: impl FnOnce() -> T,
2344 ) -> String {
2345 match input {
2346 Ok(value) => serde_json::to_string(&value).unwrap_or_default(),
2347 Err(e) => {
2348 log_error_to_statsig_and_console!(
2349 &self.ops_stats,
2350 TAG,
2351 StatsigErr::GCIRError(e.to_string())
2352 );
2353 serde_json::to_string(&fallback()).unwrap_or_default()
2354 }
2355 }
2356 }
2357
2358 fn get_gate_evaluation(
2359 &self,
2360 user_internal: &StatsigUserInternal,
2361 gate_name: &str,
2362 disable_exposure_logging: Option<bool>,
2363 ) -> (EvaluationDetails, Option<GateEvaluation>) {
2364 self.evaluate_spec(
2365 user_internal,
2366 gate_name,
2367 |eval_details| (eval_details, None),
2368 |mut result, eval_details| {
2369 let evaluation = result_to_gate_eval(gate_name, &mut result);
2370 (eval_details, Some(evaluation))
2371 },
2372 &SpecType::Gate,
2373 disable_exposure_logging,
2374 )
2375 }
2376
2377 fn get_dynamic_config_impl(
2378 &self,
2379 user_internal: &StatsigUserInternal,
2380 config_name: &str,
2381 disable_exposure_logging: Option<bool>,
2382 ) -> DynamicConfig {
2383 self.evaluate_spec(
2384 user_internal,
2385 config_name,
2386 |eval_details| make_dynamic_config(config_name, None, eval_details),
2387 |mut result, eval_details| {
2388 let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
2389 make_dynamic_config(config_name, Some(evaluation), eval_details)
2390 },
2391 &SpecType::DynamicConfig,
2392 disable_exposure_logging,
2393 )
2394 }
2395
2396 fn get_experiment_impl(
2397 &self,
2398 user_internal: &StatsigUserInternal,
2399 experiment_name: &str,
2400 disable_exposure_logging: Option<bool>,
2401 ) -> Experiment {
2402 self.evaluate_spec(
2403 user_internal,
2404 experiment_name,
2405 |eval_details| make_experiment(experiment_name, None, eval_details),
2406 |mut result, eval_details| {
2407 let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
2408 make_experiment(experiment_name, Some(evaluation), eval_details)
2409 },
2410 &SpecType::Experiment,
2411 disable_exposure_logging,
2412 )
2413 }
2414
2415 fn get_layer_impl(
2416 &self,
2417 user_internal: StatsigUserInternal,
2418 layer_name: &str,
2419 evaluation_options: LayerEvaluationOptions,
2420 ) -> Layer {
2421 let disable_exposure_logging = evaluation_options.disable_exposure_logging;
2422
2423 if disable_exposure_logging {
2424 self.event_logger.increment_non_exposure_checks(layer_name);
2425 }
2426
2427 let mut layer = self.evaluate_spec(
2428 &user_internal,
2429 layer_name,
2430 |eval_details| {
2431 make_layer(
2432 user_internal.to_loggable(),
2433 layer_name,
2434 None,
2435 eval_details,
2436 None,
2437 disable_exposure_logging,
2438 )
2439 },
2440 |mut result, eval_details| {
2441 let evaluation = result_to_layer_eval(layer_name, &mut result);
2442 let event_logger_ptr = Arc::downgrade(&self.event_logger);
2443
2444 make_layer(
2445 user_internal.to_loggable(),
2446 layer_name,
2447 Some(evaluation),
2448 eval_details,
2449 Some(event_logger_ptr),
2450 disable_exposure_logging,
2451 )
2452 },
2453 &SpecType::Layer,
2454 Some(evaluation_options.disable_exposure_logging),
2455 );
2456
2457 layer = PersistentValuesManager::try_apply_sticky_value_to_layer(
2458 &self.persistent_values_manager,
2459 &user_internal,
2460 &evaluation_options,
2461 &self.spec_store,
2462 &self.ops_stats,
2463 layer,
2464 );
2465
2466 self.emit_layer_evaluated(&layer);
2467
2468 layer
2469 }
2470
2471 fn internalize_user<'s, 'u>(&'s self, user: &'u StatsigUser) -> StatsigUserInternal<'s, 'u> {
2472 StatsigUserInternal::new(user, Some(self))
2473 }
2474
2475 fn set_default_environment_from_server(&self) {
2476 let data = read_lock_or_else!(self.spec_store.data, {
2477 return;
2478 });
2479
2480 if let Some(default_env) = data.values.default_environment.as_ref() {
2481 let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
2482
2483 match self
2484 .fallback_environment
2485 .try_lock_for(Duration::from_secs(5))
2486 {
2487 Some(mut fallback_env) => {
2488 *fallback_env = Some(env_map);
2489 }
2490 None => {
2491 log_e!(TAG, "Failed to lock fallback_environment");
2492 }
2493 }
2494 }
2495 }
2496
2497 fn log_init_finish(
2498 &self,
2499 success: bool,
2500 error_message: &Option<String>,
2501 duration_ms: &u64,
2502 specs_info: &SpecsInfo,
2503 ) {
2504 let is_store_populated = specs_info.source != SpecsSource::NoValues;
2505 let source_str = specs_info.source.to_string();
2506
2507 let event = ObservabilityEvent::new_event(
2508 MetricType::Dist,
2509 "initialization".to_string(),
2510 *duration_ms as f64,
2511 Some(HashMap::from([
2512 ("init_success".to_owned(), success.to_string()),
2513 ("source".to_owned(), source_str.clone()),
2514 ("store_populated".to_owned(), is_store_populated.to_string()),
2515 (
2516 "init_source_api".to_owned(),
2517 specs_info.source_api.clone().unwrap_or_default(),
2518 ),
2519 ])),
2520 );
2521
2522 self.ops_stats.log(event);
2523 self.ops_stats.add_marker(
2524 {
2525 let marker = Marker::new(KeyType::Overall, ActionType::End, None)
2526 .with_is_success(success)
2527 .with_config_spec_ready(specs_info.source != SpecsSource::NoValues)
2528 .with_source(source_str);
2529
2530 if let Some(msg) = &error_message {
2531 marker.with_message(msg.to_string())
2532 } else {
2533 marker
2534 }
2535 },
2536 Some(ContextType::Initialize),
2537 );
2538 self.ops_stats
2539 .enqueue_diagnostics_event(None, Some(ContextType::Initialize));
2540 }
2541
2542 fn should_user_third_party_parser(&self) -> bool {
2543 self.options.use_third_party_ua_parser.unwrap_or(false)
2544 }
2545}
2546
2547fn initialize_event_logging_adapter(
2548 sdk_key: &str,
2549 options: &StatsigOptions,
2550) -> Arc<dyn EventLoggingAdapter> {
2551 options
2552 .event_logging_adapter
2553 .clone()
2554 .unwrap_or_else(|| Arc::new(StatsigHttpEventLoggingAdapter::new(sdk_key, Some(options))))
2555}
2556
2557fn initialize_specs_adapter(
2558 sdk_key: &str,
2559 data_store_key: &str,
2560 options: &StatsigOptions,
2561) -> SpecsAdapterHousing {
2562 if let Some(adapter) = options.specs_adapter.clone() {
2563 log_d!(TAG, "Using provided SpecsAdapter: {}", sdk_key);
2564 return SpecsAdapterHousing {
2565 inner: adapter,
2566 as_default_adapter: None,
2567 };
2568 }
2569
2570 if let Some(adapter_config) = options.spec_adapters_config.clone() {
2571 let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
2572 sdk_key,
2573 data_store_key,
2574 adapter_config,
2575 options,
2576 ));
2577
2578 return SpecsAdapterHousing {
2579 inner: adapter,
2580 as_default_adapter: None,
2581 };
2582 }
2583
2584 if let Some(data_store) = options.data_store.clone() {
2585 let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
2586 sdk_key,
2587 data_store_key,
2588 data_store,
2589 options,
2590 ));
2591
2592 return SpecsAdapterHousing {
2593 inner: adapter,
2594 as_default_adapter: None,
2595 };
2596 }
2597
2598 let adapter = Arc::new(StatsigHttpSpecsAdapter::new(sdk_key, Some(options), None));
2599
2600 SpecsAdapterHousing {
2601 inner: adapter.clone(),
2602 as_default_adapter: Some(adapter),
2603 }
2604}
2605
2606fn initialize_id_lists_adapter(sdk_key: &str, options: &StatsigOptions) -> IdListsAdapterHousing {
2607 if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
2608 return IdListsAdapterHousing {
2609 inner: Some(id_lists_adapter),
2610 as_default_adapter: None,
2611 };
2612 }
2613
2614 if options.enable_id_lists.unwrap_or(false) {
2615 let adapter = Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options));
2616
2617 return IdListsAdapterHousing {
2618 inner: Some(adapter.clone()),
2619 as_default_adapter: Some(adapter),
2620 };
2621 }
2622
2623 IdListsAdapterHousing {
2624 inner: None,
2625 as_default_adapter: None,
2626 }
2627}
2628
2629struct IdListsAdapterHousing {
2630 inner: Option<Arc<dyn IdListsAdapter>>,
2631 as_default_adapter: Option<Arc<StatsigHttpIdListsAdapter>>,
2632}
2633
2634struct SpecsAdapterHousing {
2635 inner: Arc<dyn SpecsAdapter>,
2636 as_default_adapter: Option<Arc<StatsigHttpSpecsAdapter>>,
2637}
2638
2639fn setup_ops_stats(
2640 sdk_key: &str,
2641 statsig_runtime: Arc<StatsigRuntime>,
2642 error_observer: &Arc<dyn OpsStatsEventObserver>,
2643 diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
2644 console_capture_observer: &Arc<dyn OpsStatsEventObserver>,
2645 external_observer: &Option<Weak<dyn ObservabilityClient>>,
2646) -> Arc<OpsStatsForInstance> {
2647 let ops_stat = OPS_STATS.get_for_instance(sdk_key);
2648 ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
2649 ops_stat.subscribe(
2650 statsig_runtime.clone(),
2651 Arc::downgrade(diagnostics_observer),
2652 );
2653 ops_stat.subscribe(
2654 statsig_runtime.clone(),
2655 Arc::downgrade(console_capture_observer),
2656 );
2657 if let Some(ob_client) = external_observer {
2658 if let Some(client) = ob_client.upgrade() {
2659 client.init();
2660 let as_observer = client.to_ops_stats_event_observer();
2661 ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
2662 }
2663 }
2664
2665 ops_stat
2666}