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_with_options(
1733 &self,
1734 user: &StatsigUser,
1735 experiment_name: &str,
1736 options: ExperimentEvaluationOptions,
1737 ) -> String {
1738 use crate::evaluation::evaluator_result::result_to_experiment_raw;
1739
1740 let interned_experiment_name = InternedString::from_str_ref(experiment_name);
1741 let user_internal = self.internalize_user(user);
1742 let disable_exposure_logging: bool = options.disable_exposure_logging;
1743
1744 let (details, result) = self.evaluate_spec_raw(
1745 &user_internal,
1746 experiment_name,
1747 &SpecType::Experiment,
1748 Some(disable_exposure_logging),
1749 );
1750
1751 let (result, details) = PersistentValuesManager::try_apply_sticky_value_to_raw_experiment(
1752 &self.persistent_values_manager,
1753 &user_internal,
1754 &options,
1755 details,
1756 result,
1757 );
1758
1759 let raw = result_to_experiment_raw(experiment_name, &details, result.as_ref());
1760
1761 self.emit_experiment_evaluated_parts(
1762 experiment_name,
1763 details.reason.as_str(),
1764 result.as_ref(),
1765 );
1766
1767 if disable_exposure_logging {
1768 log_d!(
1769 TAG,
1770 "Exposure logging is disabled for Experiment {}",
1771 experiment_name
1772 );
1773 self.event_logger
1774 .increment_non_exposure_checks(experiment_name);
1775 } else {
1776 self.event_logger
1777 .enqueue(EnqueueExposureOp::dynamic_config_exposure(
1778 &user_internal,
1779 &interned_experiment_name,
1780 ExposureTrigger::Auto,
1781 details,
1782 result,
1783 ));
1784 }
1785
1786 raw
1787 }
1788
1789 pub fn get_raw_layer_with_options(
1790 &self,
1791 user: &StatsigUser,
1792 layer_name: &str,
1793 options: LayerEvaluationOptions,
1794 ) -> String {
1795 use crate::evaluation::evaluator_result::result_to_layer_raw;
1796
1797 let user_internal = self.internalize_user(user);
1798 let disable_exposure_logging: bool = options.disable_exposure_logging;
1799
1800 let (details, result) = self.evaluate_spec_raw(
1801 &user_internal,
1802 layer_name,
1803 &SpecType::Layer,
1804 Some(disable_exposure_logging),
1805 );
1806
1807 let (result, details) = PersistentValuesManager::try_apply_sticky_value_to_raw_layer(
1808 &self.persistent_values_manager,
1809 &user_internal,
1810 &options,
1811 &self.spec_store,
1812 &self.ops_stats,
1813 details,
1814 result,
1815 );
1816
1817 let raw = result_to_layer_raw(
1818 &user_internal,
1819 layer_name,
1820 options,
1821 &details,
1822 result.as_ref(),
1823 );
1824
1825 self.emit_layer_evaluated_parts(layer_name, details.reason.as_str(), result.as_ref());
1826
1827 if disable_exposure_logging {
1828 log_d!(TAG, "Exposure logging is disabled for Layer {}", layer_name);
1829 self.event_logger.increment_non_exposure_checks(layer_name);
1830 }
1831
1832 raw
1833 }
1834
1835 pub fn log_layer_param_exposure_from_raw(&self, raw: String, param_name: String) {
1836 use crate::statsig_types_raw::PartialLayerRaw;
1837
1838 let partial_raw = match serde_json::from_str::<PartialLayerRaw>(&raw) {
1839 Ok(partial_raw) => partial_raw,
1840 Err(e) => {
1841 log_e!(TAG, "Failed to parse partial layer raw: {}", e);
1842 return;
1843 }
1844 };
1845
1846 if partial_raw.disable_exposure {
1847 self.event_logger
1848 .increment_non_exposure_checks(&partial_raw.name);
1849 return;
1850 }
1851
1852 let interned_parameter_name = InternedString::from_string(param_name);
1853
1854 self.event_logger
1855 .enqueue(EnqueueExposureOp::layer_param_exposure_from_partial_raw(
1856 interned_parameter_name,
1857 ExposureTrigger::Auto,
1858 partial_raw,
1859 ));
1860 }
1861}
1862
1863impl Statsig {
1866 pub(crate) fn get_from_statsig_env(&self, key: &str) -> Option<DynamicValue> {
1867 if let Some(env) = &self.statsig_environment {
1868 return env.get(key).cloned();
1869 }
1870
1871 if let Some(fallback_env) = self
1872 .fallback_environment
1873 .try_lock_for(Duration::from_secs(5))
1874 {
1875 if let Some(env) = &*fallback_env {
1876 return env.get(key).cloned();
1877 }
1878 }
1879
1880 None
1881 }
1882
1883 pub(crate) fn get_value_from_global_custom_fields(&self, key: &str) -> Option<&DynamicValue> {
1884 if let Some(env) = &self.options.global_custom_fields {
1885 return env.get(key);
1886 }
1887
1888 None
1889 }
1890
1891 pub(crate) fn use_global_custom_fields<T>(
1892 &self,
1893 f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1894 ) -> T {
1895 f(self.options.global_custom_fields.as_ref())
1896 }
1897
1898 pub(crate) fn use_statsig_env<T>(
1899 &self,
1900 f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1901 ) -> T {
1902 if let Some(env) = &self.statsig_environment {
1903 return f(Some(env));
1904 }
1905
1906 if let Some(fallback_env) = self
1907 .fallback_environment
1908 .try_lock_for(Duration::from_secs(5))
1909 {
1910 if let Some(env) = &*fallback_env {
1911 return f(Some(env));
1912 }
1913 }
1914
1915 f(None)
1916 }
1917}
1918
1919impl Statsig {
1922 async fn start_background_tasks(
1923 statsig_runtime: Arc<StatsigRuntime>,
1924 id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
1925 specs_adapter: Arc<dyn SpecsAdapter>,
1926 ops_stats: Arc<OpsStatsForInstance>,
1927 bg_tasks_started: Arc<AtomicBool>,
1928 ) -> bool {
1929 if bg_tasks_started.load(Ordering::SeqCst) {
1930 return true;
1931 }
1932
1933 let mut success = true;
1934
1935 if let Some(adapter) = &id_lists_adapter {
1936 if let Err(e) = adapter
1937 .clone()
1938 .schedule_background_sync(&statsig_runtime)
1939 .await
1940 {
1941 success = false;
1942 log_w!(TAG, "Failed to schedule idlist background job {}", e);
1943 }
1944 }
1945
1946 if let Err(e) = specs_adapter
1947 .clone()
1948 .schedule_background_sync(&statsig_runtime)
1949 .await
1950 {
1951 success = false;
1952 log_error_to_statsig_and_console!(
1953 ops_stats,
1954 TAG,
1955 StatsigErr::SpecsAdapterSkipPoll(format!(
1956 "Failed to schedule specs adapter background job: {e}"
1957 ))
1958 );
1959 }
1960
1961 bg_tasks_started.store(true, Ordering::SeqCst);
1962
1963 success
1964 }
1965
1966 async fn apply_timeout_to_init(
1967 &self,
1968 timeout_ms: u64,
1969 ) -> Result<InitializeDetails, StatsigErr> {
1970 let timeout = Duration::from_millis(timeout_ms);
1971
1972 let init_future = self.initialize_impl_with_details();
1973 let timeout_future = sleep(timeout);
1974
1975 let statsig_runtime = self.statsig_runtime.clone();
1976 let id_lists_adapter = self.id_lists_adapter.inner.clone();
1977 let specs_adapter = self.specs_adapter.inner.clone();
1978 let ops_stats = self.ops_stats.clone();
1979 let background_tasks_started = self.background_tasks_started.clone();
1980 let statsig_runtime_for_closure = statsig_runtime.clone();
1982
1983 tokio::select! {
1984 result = init_future => {
1985 result
1986 },
1987 _ = timeout_future => {
1988 statsig_runtime.spawn(
1989 "start_background_tasks",
1990 |_shutdown_notify| async move {
1991 Self::start_background_tasks(
1992 statsig_runtime_for_closure,
1993 id_lists_adapter,
1994 specs_adapter,
1995 ops_stats,
1996 background_tasks_started,
1997 ).await;
1998 }
1999 )?;
2000 Ok(InitializeDetails::from_timeout_failure(timeout_ms))
2001 },
2002 }
2003 }
2004
2005 async fn initialize_impl_with_details(&self) -> Result<InitializeDetails, StatsigErr> {
2006 let start_time = Instant::now();
2007 self.spec_store.set_source(SpecsSource::Loading);
2008 self.specs_adapter.inner.initialize(self.spec_store.clone());
2009 let use_third_party_ua_parser = self.should_user_third_party_parser();
2010
2011 let mut error_message = None;
2012 let mut id_list_ready = None;
2013
2014 let init_country_lookup = if !self.options.disable_country_lookup.unwrap_or_default() {
2015 Some(self.statsig_runtime.spawn(INIT_IP_TAG, |_| async {
2016 CountryLookup::load_country_lookup();
2017 }))
2018 } else {
2019 None
2020 };
2021
2022 let init_ua = if use_third_party_ua_parser {
2023 Some(self.statsig_runtime.spawn(INIT_UA_TAG, |_| async {
2024 UserAgentParser::load_parser();
2025 }))
2026 } else {
2027 None
2028 };
2029
2030 let init_res = match self
2031 .specs_adapter
2032 .inner
2033 .clone()
2034 .start(&self.statsig_runtime)
2035 .await
2036 {
2037 Ok(()) => Ok(()),
2038 Err(e) => {
2039 self.spec_store.set_source(SpecsSource::NoValues);
2040 error_message = Some(format!("Failed to start specs adapter: {e}"));
2041 Err(e)
2042 }
2043 };
2044
2045 if let Some(adapter) = &self.id_lists_adapter.inner {
2046 match adapter
2047 .clone()
2048 .start(&self.statsig_runtime, self.spec_store.clone())
2049 .await
2050 {
2051 Ok(()) => {
2052 id_list_ready = Some(true);
2053 }
2054 Err(e) => {
2055 id_list_ready = Some(false);
2056 error_message.get_or_insert_with(|| format!("Failed to sync ID lists: {e}"));
2057 }
2058 }
2059 }
2060
2061 if let Err(e) = self
2062 .event_logging_adapter
2063 .clone()
2064 .start(&self.statsig_runtime)
2065 .await
2066 {
2067 log_error_to_statsig_and_console!(
2068 self.ops_stats.clone(),
2069 TAG,
2070 StatsigErr::UnstartedAdapter(format!("Failed to start event logging adapter: {e}"))
2071 );
2072 }
2073
2074 let spec_info = self.spec_store.get_current_specs_info();
2075 let duration = start_time.elapsed().as_millis() as u64;
2076
2077 self.set_default_environment_from_server();
2078
2079 if self.options.wait_for_country_lookup_init.unwrap_or(false) {
2080 match init_country_lookup {
2081 Some(Ok(task_id)) => {
2082 let _ = self
2083 .statsig_runtime
2084 .await_join_handle(INIT_IP_TAG, &task_id)
2085 .await;
2086 }
2087 Some(Err(e)) => {
2088 log_error_to_statsig_and_console!(
2089 self.ops_stats.clone(),
2090 TAG,
2091 StatsigErr::UnstartedAdapter(format!(
2092 "Failed to spawn country lookup task: {e}"
2093 ))
2094 );
2095 }
2096 _ => {}
2097 }
2098 }
2099 if self.options.wait_for_user_agent_init.unwrap_or(false) {
2100 match init_ua {
2101 Some(Ok(task_id)) => {
2102 let _ = self
2103 .statsig_runtime
2104 .await_join_handle(INIT_UA_TAG, &task_id)
2105 .await;
2106 }
2107 Some(Err(e)) => {
2108 log_error_to_statsig_and_console!(
2109 self.ops_stats.clone(),
2110 TAG,
2111 StatsigErr::UnstartedAdapter(format!(
2112 "Failed to spawn user agent parser task: {e}"
2113 ))
2114 );
2115 }
2116 _ => {}
2117 }
2118 }
2119
2120 let error = init_res.clone().err();
2121
2122 let success = Self::start_background_tasks(
2123 self.statsig_runtime.clone(),
2124 self.id_lists_adapter.inner.clone(),
2125 self.specs_adapter.inner.clone(),
2126 self.ops_stats.clone(),
2127 self.background_tasks_started.clone(),
2128 )
2129 .await;
2130
2131 Ok(InitializeDetails::new(
2132 success,
2133 duration,
2134 spec_info,
2135 id_list_ready,
2136 error,
2137 ))
2138 }
2139
2140 fn log_init_details(&self, init_details: &Result<InitializeDetails, StatsigErr>) {
2141 match init_details {
2142 Ok(details) => {
2143 self.log_init_finish(
2144 details.init_success,
2145 &None,
2146 &details.duration_ms,
2147 &self.spec_store.get_current_specs_info(),
2148 );
2149 if let Some(failure) = &details.failure_details {
2150 log_error_to_statsig_and_console!(
2151 self.ops_stats,
2152 TAG,
2153 StatsigErr::InitializationError(failure.reason.clone())
2154 );
2155 }
2156 }
2157 Err(err) => {
2158 log_w!(TAG, "Initialization error: {:?}", err);
2160 }
2161 }
2162 }
2163
2164 fn create_standard_eval_context<'a>(
2165 &'a self,
2166 user_internal: &'a StatsigUserInternal,
2167 data: &'a SpecStoreData,
2168 app_id: Option<&'a DynamicValue>,
2169 override_adapter: Option<&'a Arc<dyn OverrideAdapter>>,
2170 disable_exposure_logging: bool,
2171 ) -> EvaluatorContext<'a> {
2172 EvaluatorContext::new(
2173 user_internal,
2174 &data.values,
2175 IdListResolution::MapLookup(&data.id_lists),
2176 &self.hashing,
2177 app_id,
2178 override_adapter,
2179 self.should_user_third_party_parser(),
2180 Some(self),
2181 disable_exposure_logging,
2182 )
2183 }
2184
2185 fn create_gcir_eval_context<'a>(
2186 &'a self,
2187 user_internal: &'a StatsigUserInternal,
2188 data: &'a SpecStoreData,
2189 options: &'a ClientInitResponseOptions,
2190 ) -> EvaluatorContext<'a> {
2191 let app_id = select_app_id_for_gcir(options, &data.values, &self.hashing);
2192 let override_adapter = match options.include_local_overrides {
2193 Some(true) => self.override_adapter.as_ref(),
2194 _ => None,
2195 };
2196
2197 EvaluatorContext::new(
2198 user_internal,
2199 &data.values,
2200 IdListResolution::MapLookup(&data.id_lists),
2201 &self.hashing,
2202 app_id,
2203 override_adapter,
2204 self.should_user_third_party_parser(),
2205 None,
2206 true,
2207 )
2208 }
2209
2210 fn evaluate_spec_raw(
2211 &self,
2212 user_internal: &StatsigUserInternal,
2213 spec_name: &str,
2214 spec_type: &SpecType,
2215 disable_exposure_logging: Option<bool>,
2216 ) -> (EvaluationDetails, Option<EvaluatorResult>) {
2217 let data = read_lock_or_else!(self.spec_store.data, {
2218 log_error_to_statsig_and_console!(
2219 &self.ops_stats,
2220 TAG,
2221 StatsigErr::LockFailure(
2222 "Failed to acquire read lock for spec store data".to_string()
2223 )
2224 );
2225 return (EvaluationDetails::unrecognized_no_data(), None);
2226 });
2227
2228 let mut context = self.create_standard_eval_context(
2229 user_internal,
2230 &data,
2231 data.values.app_id.as_ref(),
2232 self.override_adapter.as_ref(),
2233 disable_exposure_logging.unwrap_or(false),
2234 );
2235
2236 match Self::evaluate_with_details(&mut context, &data, spec_name, spec_type) {
2237 Ok(eval_details) => (eval_details, Some(context.result)),
2238 Err(e) => {
2239 log_error_to_statsig_and_console!(
2240 &self.ops_stats,
2241 TAG,
2242 StatsigErr::EvaluationError(e.to_string())
2243 );
2244 (EvaluationDetails::error(&e.to_string()), None)
2245 }
2246 }
2247 }
2248
2249 #[allow(clippy::too_many_arguments)]
2250 fn evaluate_spec<T>(
2251 &self,
2252 user_internal: &StatsigUserInternal,
2253 spec_name: &str,
2254 make_empty_result: impl FnOnce(EvaluationDetails) -> T,
2255 make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
2256 spec_type: &SpecType,
2257 disable_exposure_logging: Option<bool>,
2258 ) -> T {
2259 let data = read_lock_or_else!(self.spec_store.data, {
2260 log_error_to_statsig_and_console!(
2261 &self.ops_stats,
2262 TAG,
2263 StatsigErr::LockFailure(
2264 "Failed to acquire read lock for spec store data".to_string()
2265 )
2266 );
2267 return make_empty_result(EvaluationDetails::unrecognized_no_data());
2268 });
2269
2270 let mut context = self.create_standard_eval_context(
2271 user_internal,
2272 &data,
2273 data.values.app_id.as_ref(),
2274 self.override_adapter.as_ref(),
2275 disable_exposure_logging.unwrap_or(false),
2276 );
2277
2278 match Self::evaluate_with_details(&mut context, &data, spec_name, spec_type) {
2279 Ok(eval_details) => make_result(context.result, eval_details),
2280 Err(e) => {
2281 log_error_to_statsig_and_console!(
2282 &self.ops_stats,
2283 TAG,
2284 StatsigErr::EvaluationError(e.to_string())
2285 );
2286 make_empty_result(EvaluationDetails::error(&e.to_string()))
2287 }
2288 }
2289 }
2290
2291 fn evaluate_with_details(
2292 ctx: &mut EvaluatorContext,
2293 spec_store_data: &SpecStoreData,
2294 spec_name: &str,
2295 spec_type: &SpecType,
2296 ) -> Result<EvaluationDetails, StatsigErr> {
2297 let recognition = Evaluator::evaluate(ctx, spec_name, spec_type)?;
2298
2299 if recognition == Recognition::Unrecognized {
2300 return Ok(EvaluationDetails::unrecognized(
2301 &spec_store_data.source,
2302 spec_store_data.values.time,
2303 spec_store_data.time_received_at,
2304 ));
2305 }
2306
2307 if let Some(reason) = ctx.result.override_reason {
2308 return Ok(EvaluationDetails::recognized_but_overridden(
2309 spec_store_data.values.time,
2310 spec_store_data.time_received_at,
2311 reason,
2312 ctx.result.version,
2313 ));
2314 }
2315
2316 Ok(EvaluationDetails::recognized(
2317 &spec_store_data.source,
2318 spec_store_data.values.time,
2319 spec_store_data.time_received_at,
2320 &ctx.result,
2321 ))
2322 }
2323
2324 fn stringify_gcir_response<T: Serialize>(
2325 &self,
2326 input: Result<T, StatsigErr>,
2327 fallback: impl FnOnce() -> T,
2328 ) -> String {
2329 match input {
2330 Ok(value) => serde_json::to_string(&value).unwrap_or_default(),
2331 Err(e) => {
2332 log_error_to_statsig_and_console!(
2333 &self.ops_stats,
2334 TAG,
2335 StatsigErr::GCIRError(e.to_string())
2336 );
2337 serde_json::to_string(&fallback()).unwrap_or_default()
2338 }
2339 }
2340 }
2341
2342 fn get_gate_evaluation(
2343 &self,
2344 user_internal: &StatsigUserInternal,
2345 gate_name: &str,
2346 disable_exposure_logging: Option<bool>,
2347 ) -> (EvaluationDetails, Option<GateEvaluation>) {
2348 self.evaluate_spec(
2349 user_internal,
2350 gate_name,
2351 |eval_details| (eval_details, None),
2352 |mut result, eval_details| {
2353 let evaluation = result_to_gate_eval(gate_name, &mut result);
2354 (eval_details, Some(evaluation))
2355 },
2356 &SpecType::Gate,
2357 disable_exposure_logging,
2358 )
2359 }
2360
2361 fn get_dynamic_config_impl(
2362 &self,
2363 user_internal: &StatsigUserInternal,
2364 config_name: &str,
2365 disable_exposure_logging: Option<bool>,
2366 ) -> DynamicConfig {
2367 self.evaluate_spec(
2368 user_internal,
2369 config_name,
2370 |eval_details| make_dynamic_config(config_name, None, eval_details),
2371 |mut result, eval_details| {
2372 let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
2373 make_dynamic_config(config_name, Some(evaluation), eval_details)
2374 },
2375 &SpecType::DynamicConfig,
2376 disable_exposure_logging,
2377 )
2378 }
2379
2380 fn get_experiment_impl(
2381 &self,
2382 user_internal: &StatsigUserInternal,
2383 experiment_name: &str,
2384 disable_exposure_logging: Option<bool>,
2385 ) -> Experiment {
2386 self.evaluate_spec(
2387 user_internal,
2388 experiment_name,
2389 |eval_details| make_experiment(experiment_name, None, eval_details),
2390 |mut result, eval_details| {
2391 let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
2392 make_experiment(experiment_name, Some(evaluation), eval_details)
2393 },
2394 &SpecType::Experiment,
2395 disable_exposure_logging,
2396 )
2397 }
2398
2399 fn get_layer_impl(
2400 &self,
2401 user_internal: StatsigUserInternal,
2402 layer_name: &str,
2403 evaluation_options: LayerEvaluationOptions,
2404 ) -> Layer {
2405 let disable_exposure_logging = evaluation_options.disable_exposure_logging;
2406
2407 if disable_exposure_logging {
2408 self.event_logger.increment_non_exposure_checks(layer_name);
2409 }
2410
2411 let mut layer = self.evaluate_spec(
2412 &user_internal,
2413 layer_name,
2414 |eval_details| {
2415 make_layer(
2416 user_internal.to_loggable(),
2417 layer_name,
2418 None,
2419 eval_details,
2420 None,
2421 disable_exposure_logging,
2422 )
2423 },
2424 |mut result, eval_details| {
2425 let evaluation = result_to_layer_eval(layer_name, &mut result);
2426 let event_logger_ptr = Arc::downgrade(&self.event_logger);
2427
2428 make_layer(
2429 user_internal.to_loggable(),
2430 layer_name,
2431 Some(evaluation),
2432 eval_details,
2433 Some(event_logger_ptr),
2434 disable_exposure_logging,
2435 )
2436 },
2437 &SpecType::Layer,
2438 Some(evaluation_options.disable_exposure_logging),
2439 );
2440
2441 layer = PersistentValuesManager::try_apply_sticky_value_to_layer(
2442 &self.persistent_values_manager,
2443 &user_internal,
2444 &evaluation_options,
2445 &self.spec_store,
2446 &self.ops_stats,
2447 layer,
2448 );
2449
2450 self.emit_layer_evaluated(&layer);
2451
2452 layer
2453 }
2454
2455 fn internalize_user<'s, 'u>(&'s self, user: &'u StatsigUser) -> StatsigUserInternal<'s, 'u> {
2456 StatsigUserInternal::new(user, Some(self))
2457 }
2458
2459 fn set_default_environment_from_server(&self) {
2460 let data = read_lock_or_else!(self.spec_store.data, {
2461 return;
2462 });
2463
2464 if let Some(default_env) = data.values.default_environment.as_ref() {
2465 let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
2466
2467 match self
2468 .fallback_environment
2469 .try_lock_for(Duration::from_secs(5))
2470 {
2471 Some(mut fallback_env) => {
2472 *fallback_env = Some(env_map);
2473 }
2474 None => {
2475 log_e!(TAG, "Failed to lock fallback_environment");
2476 }
2477 }
2478 }
2479 }
2480
2481 fn log_init_finish(
2482 &self,
2483 success: bool,
2484 error_message: &Option<String>,
2485 duration_ms: &u64,
2486 specs_info: &SpecsInfo,
2487 ) {
2488 let is_store_populated = specs_info.source != SpecsSource::NoValues;
2489 let source_str = specs_info.source.to_string();
2490
2491 let event = ObservabilityEvent::new_event(
2492 MetricType::Dist,
2493 "initialization".to_string(),
2494 *duration_ms as f64,
2495 Some(HashMap::from([
2496 ("success".to_owned(), success.to_string()),
2497 ("source".to_owned(), source_str.clone()),
2498 ("store_populated".to_owned(), is_store_populated.to_string()),
2499 (
2500 "spec_source_api".to_owned(),
2501 specs_info.source_api.clone().unwrap_or_default(),
2502 ),
2503 ])),
2504 );
2505
2506 self.ops_stats.log(event);
2507 self.ops_stats.add_marker(
2508 {
2509 let marker = Marker::new(KeyType::Overall, ActionType::End, None)
2510 .with_is_success(success)
2511 .with_config_spec_ready(specs_info.source != SpecsSource::NoValues)
2512 .with_source(source_str);
2513
2514 if let Some(msg) = &error_message {
2515 marker.with_message(msg.to_string())
2516 } else {
2517 marker
2518 }
2519 },
2520 Some(ContextType::Initialize),
2521 );
2522 self.ops_stats
2523 .enqueue_diagnostics_event(None, Some(ContextType::Initialize));
2524 }
2525
2526 fn should_user_third_party_parser(&self) -> bool {
2527 self.options.use_third_party_ua_parser.unwrap_or(false)
2528 }
2529}
2530
2531fn initialize_event_logging_adapter(
2532 sdk_key: &str,
2533 options: &StatsigOptions,
2534) -> Arc<dyn EventLoggingAdapter> {
2535 options
2536 .event_logging_adapter
2537 .clone()
2538 .unwrap_or_else(|| Arc::new(StatsigHttpEventLoggingAdapter::new(sdk_key, Some(options))))
2539}
2540
2541fn initialize_specs_adapter(
2542 sdk_key: &str,
2543 data_store_key: &str,
2544 options: &StatsigOptions,
2545) -> SpecsAdapterHousing {
2546 if let Some(adapter) = options.specs_adapter.clone() {
2547 log_d!(TAG, "Using provided SpecsAdapter: {}", sdk_key);
2548 return SpecsAdapterHousing {
2549 inner: adapter,
2550 as_default_adapter: None,
2551 };
2552 }
2553
2554 if let Some(adapter_config) = options.spec_adapters_config.clone() {
2555 let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
2556 sdk_key,
2557 data_store_key,
2558 adapter_config,
2559 options,
2560 ));
2561
2562 return SpecsAdapterHousing {
2563 inner: adapter,
2564 as_default_adapter: None,
2565 };
2566 }
2567
2568 if let Some(data_store) = options.data_store.clone() {
2569 let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
2570 sdk_key,
2571 data_store_key,
2572 data_store,
2573 options,
2574 ));
2575
2576 return SpecsAdapterHousing {
2577 inner: adapter,
2578 as_default_adapter: None,
2579 };
2580 }
2581
2582 let adapter = Arc::new(StatsigHttpSpecsAdapter::new(sdk_key, Some(options), None));
2583
2584 SpecsAdapterHousing {
2585 inner: adapter.clone(),
2586 as_default_adapter: Some(adapter),
2587 }
2588}
2589
2590fn initialize_id_lists_adapter(sdk_key: &str, options: &StatsigOptions) -> IdListsAdapterHousing {
2591 if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
2592 return IdListsAdapterHousing {
2593 inner: Some(id_lists_adapter),
2594 as_default_adapter: None,
2595 };
2596 }
2597
2598 if options.enable_id_lists.unwrap_or(false) {
2599 let adapter = Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options));
2600
2601 return IdListsAdapterHousing {
2602 inner: Some(adapter.clone()),
2603 as_default_adapter: Some(adapter),
2604 };
2605 }
2606
2607 IdListsAdapterHousing {
2608 inner: None,
2609 as_default_adapter: None,
2610 }
2611}
2612
2613struct IdListsAdapterHousing {
2614 inner: Option<Arc<dyn IdListsAdapter>>,
2615 as_default_adapter: Option<Arc<StatsigHttpIdListsAdapter>>,
2616}
2617
2618struct SpecsAdapterHousing {
2619 inner: Arc<dyn SpecsAdapter>,
2620 as_default_adapter: Option<Arc<StatsigHttpSpecsAdapter>>,
2621}
2622
2623fn setup_ops_stats(
2624 sdk_key: &str,
2625 statsig_runtime: Arc<StatsigRuntime>,
2626 error_observer: &Arc<dyn OpsStatsEventObserver>,
2627 diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
2628 console_capture_observer: &Arc<dyn OpsStatsEventObserver>,
2629 external_observer: &Option<Weak<dyn ObservabilityClient>>,
2630) -> Arc<OpsStatsForInstance> {
2631 let ops_stat = OPS_STATS.get_for_instance(sdk_key);
2632 ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
2633 ops_stat.subscribe(
2634 statsig_runtime.clone(),
2635 Arc::downgrade(diagnostics_observer),
2636 );
2637 ops_stat.subscribe(
2638 statsig_runtime.clone(),
2639 Arc::downgrade(console_capture_observer),
2640 );
2641 if let Some(ob_client) = external_observer {
2642 if let Some(client) = ob_client.upgrade() {
2643 client.init();
2644 let as_observer = client.to_ops_stats_event_observer();
2645 ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
2646 }
2647 }
2648
2649 ops_stat
2650}