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