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