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