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