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_data::{RuleRef, SpecView};
11use crate::evaluation::evaluation_details::EvaluationDetails;
12use crate::evaluation::evaluation_types::GateEvaluation;
13use crate::evaluation::evaluator::{Evaluator, Recognition, SpecType};
14use crate::evaluation::evaluator_context::{EvaluatorContext, IdListResolution};
15use crate::evaluation::evaluator_result::{
16 result_to_dynamic_config_eval, result_to_experiment_eval, result_to_gate_eval,
17 result_to_layer_eval, EvaluatorResult,
18};
19use crate::evaluation::user_agent_parsing::{ParsedUserAgentValue, UserAgentParser};
20#[cfg(feature = "ffi-support")]
21use crate::event_logging::delayed_exposure_store::DelayedExposureStore;
22use crate::event_logging::event_logger::{EventLogger, ExposureTrigger};
23use crate::event_logging::event_queue::queued_config_expo::EnqueueConfigExpoOp;
24use crate::event_logging::event_queue::queued_experiment_expo::EnqueueExperimentExpoOp;
25use crate::event_logging::event_queue::queued_expo::EnqueueExposureOp;
26use crate::event_logging::event_queue::queued_gate_expo::EnqueueGateExpoOp;
27use crate::event_logging::event_queue::queued_layer_param_expo::EnqueueLayerParamExpoOp;
28use crate::event_logging::event_queue::queued_passthrough::EnqueuePassthroughOp;
29use crate::event_logging::statsig_event_internal::StatsigEventInternal;
30use crate::event_logging_adapter::EventLoggingAdapter;
31use crate::event_logging_adapter::StatsigHttpEventLoggingAdapter;
32use crate::gcir::gcir_formatter::GCIRFormatter;
33use crate::gcir::target_app_id_utils::select_app_id_for_gcir;
34use crate::hashing::HashUtil;
35use crate::initialize_evaluations_response::InitializeEvaluationsResponse;
36use crate::initialize_response::InitializeResponse;
37use crate::initialize_v2_response::InitializeV2Response;
38use crate::interned_string::InternedString;
39use crate::observability::console_capture_observer::ConsoleCaptureObserver;
40use crate::observability::diagnostics_observer::DiagnosticsObserver;
41use crate::observability::observability_client_adapter::{MetricType, ObservabilityEvent};
42use crate::observability::ops_stats::{OpsStatsForInstance, OPS_STATS};
43use crate::observability::sdk_errors_observer::{ErrorBoundaryEvent, SDKErrorsObserver};
44use crate::output_logger::{initialize_output_logger, shutdown_output_logger};
45use crate::persistent_storage::persistent_values_manager::PersistentValuesManager;
46use crate::sdk_diagnostics::diagnostics::{ContextType, Diagnostics};
47use crate::sdk_diagnostics::marker::{ActionType, KeyType, Marker};
48use crate::sdk_event_emitter::SdkEventEmitter;
49use crate::spec_store::{SpecStore, SpecStoreData};
50use crate::specs_adapter::{StatsigCustomizedSpecsAdapter, StatsigHttpSpecsAdapter};
51use crate::specs_response::param_store_types::Parameter;
52use crate::statsig_err::StatsigErr;
53use crate::statsig_metadata::StatsigMetadata;
54use crate::statsig_options::StatsigOptions;
55use crate::statsig_runtime::StatsigRuntime;
56use crate::statsig_type_factories::{
57 make_dynamic_config, make_experiment, make_feature_gate, make_layer,
58};
59#[cfg(feature = "ffi-support")]
60use crate::statsig_types::{
61 BulkDynamicConfigEvaluation, BulkEvaluationCommonFields, BulkEvaluationOptions,
62 BulkEvaluationResponse, BulkExperimentEvaluation, BulkFeatureGateEvaluation,
63 BulkLayerEvaluation, ResolvedBulkEvaluationOptions,
64};
65use crate::statsig_types::{DynamicConfig, Experiment, FeatureGate, Layer, ParameterStore};
66#[cfg(feature = "ffi-support")]
67use crate::statsig_types_raw::{DynamicConfigRaw, ExperimentRaw, FeatureGateRaw, LayerRaw};
68use crate::user::StatsigUserInternal;
69use crate::utils::get_loggable_sdk_key;
70use crate::{
71 dyn_value, log_d, log_e, log_w, ClientInitResponseOptions, GCIRResponseFormat, IdListsAdapter,
72 InitializeDetails, ObservabilityClient, OpsStatsEventObserver, OverrideAdapter, SpecsAdapter,
73 SpecsInfo, SpecsSource, SpecsUpdateListener, StatsigHttpIdListsAdapter,
74 StatsigLocalOverrideAdapter, StatsigUser,
75};
76use crate::{
77 log_error_to_statsig_and_console,
78 statsig_core_api_options::{
79 DynamicConfigEvaluationOptions, ExperimentEvaluationOptions, FeatureGateEvaluationOptions,
80 LayerEvaluationOptions, ParameterStoreEvaluationOptions,
81 },
82};
83use chrono::Utc;
84use parking_lot::Mutex;
85use serde::de::DeserializeOwned;
86use serde::Serialize;
87use serde_json::json;
88use serde_json::Value;
89use std::borrow::Cow;
90use std::collections::HashMap;
91use std::sync::atomic::{AtomicBool, Ordering};
92use std::sync::{Arc, Weak};
93use std::time::{Duration, Instant};
94use tokio::time::sleep;
95use tokio::try_join;
96
97const TAG: &str = stringify!(Statsig);
98
99#[cfg(feature = "ffi-support")]
100enum RawExposureLogging {
101 Immediate,
102 Delayed,
103}
104
105#[cfg(feature = "ffi-support")]
106struct RawExperimentExposureOptions {
107 include_local_override: bool,
108 exposure_logging: RawExposureLogging,
109 exposure_metadata: Option<HashMap<String, Value>>,
110}
111const ERROR_SDK_KEY: &str = "__STATSIG_ERROR_SDK_KEY__";
112const INIT_IP_TAG: &str = "INIT_COUNTRY_LOOKUP";
113const INIT_UA_TAG: &str = "INIT_UA";
114
115lazy_static::lazy_static! {
116 static ref SHARED_INSTANCE: Mutex<Option<Arc<Statsig>>> = Mutex::new(None);
117}
118
119pub struct Statsig {
120 pub statsig_runtime: Arc<StatsigRuntime>,
121 pub options: Arc<StatsigOptions>,
122 pub event_emitter: Arc<SdkEventEmitter>,
123
124 sdk_key: String,
125 event_logger: Arc<EventLogger>,
126 #[cfg(feature = "ffi-support")]
127 delayed_exposure_store: Arc<DelayedExposureStore>,
128 specs_adapter: SpecsAdapterHousing,
129 event_logging_adapter: Arc<dyn EventLoggingAdapter>,
130 id_lists_adapter: IdListsAdapterHousing,
131 override_adapter: Option<Arc<dyn OverrideAdapter>>,
132 spec_store: Arc<SpecStore>,
133 hashing: Arc<HashUtil>,
134 statsig_environment: Option<HashMap<String, DynamicValue>>,
135 fallback_environment: Mutex<Option<HashMap<String, DynamicValue>>>,
136 ops_stats: Arc<OpsStatsForInstance>,
137 console_capture: Arc<ConsoleCaptureInstance>,
138 error_observer: Arc<dyn OpsStatsEventObserver>,
139 diagnostics_observer: Arc<dyn OpsStatsEventObserver>,
140 console_capture_observer: Arc<dyn OpsStatsEventObserver>,
141 background_tasks_started: Arc<AtomicBool>,
142 persistent_values_manager: Option<Arc<PersistentValuesManager>>,
143 initialize_details: Mutex<InitializeDetails>,
144}
145
146pub struct StatsigContext {
147 pub sdk_key: String,
148 pub options: Arc<StatsigOptions>,
149 pub local_override_adapter: Option<Arc<dyn OverrideAdapter>>,
150 pub error_observer: Arc<dyn OpsStatsEventObserver>,
151 pub diagnostics_observer: Arc<dyn OpsStatsEventObserver>,
152 pub console_capture_observer: Arc<dyn OpsStatsEventObserver>,
153 pub spec_store: Arc<SpecStore>,
154 pub console_capture: Arc<ConsoleCaptureInstance>,
155}
156
157impl Drop for Statsig {
158 fn drop(&mut self) {
159 #[cfg(feature = "ffi-support")]
160 self.delayed_exposure_store.clear();
161
162 self.event_logger.force_shutdown();
163
164 if let Some(adapter) = &self.id_lists_adapter.as_default_adapter {
165 adapter.force_shutdown();
166 }
167
168 if let Some(adapter) = &self.specs_adapter.as_default_adapter {
169 adapter.force_shutdown();
170 }
171
172 shutdown_output_logger();
173
174 log_d!(TAG, "Statsig instance dropped");
175 }
176}
177
178impl Statsig {
179 pub fn new(sdk_key: &str, options: Option<Arc<StatsigOptions>>) -> Self {
180 let options = options.map(|o| o.validate_and_fix()).unwrap_or_default();
181 let statsig_runtime = StatsigRuntime::get_runtime_with_options(
182 options.sdk_runtime_thread_count,
183 options.runtime_thread_start_callback.clone(),
184 );
185
186 initialize_output_logger(
187 &options.output_log_level,
188 options.output_logger_provider.clone(),
189 );
190
191 let hashing = Arc::new(HashUtil::new());
192 let sdk_instance_id = options.get_sdk_instance_id(sdk_key).to_string();
193
194 let data_store_key =
195 get_data_store_key(RequestPath::RulesetsV2, sdk_key, &hashing, &options);
196
197 let specs_adapter = initialize_specs_adapter(sdk_key, &data_store_key, &options);
198 let id_lists_adapter = initialize_id_lists_adapter(sdk_key, &options);
199 let event_logging_adapter = initialize_event_logging_adapter(sdk_key, &options);
200 let override_adapter = match options.override_adapter.as_ref() {
201 Some(adapter) => Some(Arc::clone(adapter)),
202 None => Some(Arc::new(StatsigLocalOverrideAdapter::new()) as Arc<dyn OverrideAdapter>),
203 };
204
205 let event_logger =
206 EventLogger::new(sdk_key, &options, &event_logging_adapter, &statsig_runtime);
207
208 let diagnostics = Arc::new(Diagnostics::new(
209 event_logger.clone(),
210 sdk_instance_id.as_str(),
211 ));
212 let diagnostics_observer: Arc<dyn OpsStatsEventObserver> =
213 Arc::new(DiagnosticsObserver::new(diagnostics));
214 let error_observer: Arc<dyn OpsStatsEventObserver> =
215 Arc::new(SDKErrorsObserver::new(sdk_key, &options));
216 let console_capture = Arc::new(ConsoleCaptureHandler::new(event_logger.clone()));
217 let console_capture_observer: Arc<dyn OpsStatsEventObserver> =
218 Arc::new(ConsoleCaptureObserver::new(console_capture));
219
220 let ops_stats = setup_ops_stats(
221 sdk_instance_id.as_str(),
222 statsig_runtime.clone(),
223 &error_observer,
224 &diagnostics_observer,
225 &console_capture_observer,
226 &options.observability_client,
227 );
228
229 let event_emitter = Arc::new(SdkEventEmitter::default());
230
231 let spec_store = Arc::new(SpecStore::new(
232 sdk_key,
233 data_store_key,
234 statsig_runtime.clone(),
235 event_emitter.clone(),
236 Some(&options),
237 ));
238
239 let environment = options
240 .environment
241 .as_ref()
242 .map(|env| HashMap::from([("tier".into(), dyn_value!(env.as_str()))]));
243
244 let persistent_values_manager = options.persistent_storage.clone().map(|storage| {
245 Arc::new(PersistentValuesManager {
246 persistent_storage: storage,
247 })
248 });
249
250 StatsigMetadata::update_service_name(options.service_name.clone());
251
252 let console_capture =
253 CONSOLE_CAPTURE_REGISTRY.get_for_instance(sdk_key, &options, &environment);
254
255 Statsig {
256 sdk_key: sdk_key.to_string(),
257 options,
258 hashing,
259 statsig_environment: environment,
260 fallback_environment: Mutex::new(None),
261 override_adapter,
262 spec_store,
263 specs_adapter,
264 event_logging_adapter,
265 event_logger,
266 #[cfg(feature = "ffi-support")]
267 delayed_exposure_store: Arc::new(DelayedExposureStore::new(ops_stats.clone())),
268 id_lists_adapter,
269 statsig_runtime,
270 ops_stats,
271 console_capture,
272 error_observer,
273 diagnostics_observer,
274 console_capture_observer,
275 background_tasks_started: Arc::new(AtomicBool::new(false)),
276 persistent_values_manager,
277 initialize_details: Mutex::new(InitializeDetails::default()),
278 event_emitter,
279 }
280 }
281
282 pub async fn initialize(&self) -> Result<(), StatsigErr> {
295 let details = self.initialize_with_details().await?;
296
297 if let Some(failure_details) = details.failure_details {
298 Err(failure_details
299 .error
300 .unwrap_or(StatsigErr::InitializationError(failure_details.reason)))
301 } else {
302 Ok(())
303 }
304 }
305
306 pub async fn initialize_with_details(&self) -> Result<InitializeDetails, StatsigErr> {
318 self.ops_stats.add_marker(
319 Marker::new(KeyType::Overall, ActionType::Start, None),
320 Some(ContextType::Initialize),
321 );
322
323 let init_details = if let Some(timeout_ms) = self.options.init_timeout_ms {
324 self.apply_timeout_to_init(timeout_ms).await
325 } else {
326 self.initialize_impl_with_details().await
327 };
328 self.log_init_details(&init_details);
329 if let Ok(details) = &init_details {
330 match self.initialize_details.try_lock_for(Duration::from_secs(5)) {
331 Some(mut curr_init_details) => {
332 *curr_init_details = details.clone();
333 }
334 None => {
335 log_e!(TAG, "Failed to lock initialize_details");
336 }
337 }
338 }
339 init_details
340 }
341
342 pub fn get_initialize_details(&self) -> InitializeDetails {
343 match self.initialize_details.try_lock_for(Duration::from_secs(5)) {
344 Some(details) => details.clone(),
345 None => InitializeDetails::from_error(
346 "Failed to lock initialize_details",
347 Some(StatsigErr::LockFailure(
348 "Failed to lock initialize_details".to_string(),
349 )),
350 ),
351 }
352 }
353
354 pub fn is_initialized(&self) -> bool {
355 match self.initialize_details.try_lock_for(Duration::from_secs(5)) {
356 Some(details) => details.init_success,
357 None => false,
358 }
359 }
360
361 pub fn is_config_spec_ready(&self) -> bool {
362 let specs_info = self.spec_store.get_current_specs_info();
363 matches!(specs_info.lcut, Some(lcut) if lcut != 0)
364 }
365
366 pub async fn shutdown(&self) -> Result<(), StatsigErr> {
367 self.shutdown_with_timeout(Duration::from_secs(3)).await
368 }
369
370 pub async fn shutdown_with_timeout(&self, timeout: Duration) -> Result<(), StatsigErr> {
371 log_d!(
372 TAG,
373 "Shutting down Statsig with timeout {}ms",
374 timeout.as_millis()
375 );
376
377 let start = Instant::now();
378 let shutdown_result = tokio::select! {
379 () = tokio::time::sleep(timeout) => {
380 log_w!(TAG, "Statsig shutdown timed out. {}", start.elapsed().as_millis());
381 Err(StatsigErr::ShutdownFailure(
382 "Shutdown timed out".to_string()
383 ))
384 }
385 sub_result = async {
386 let id_list_shutdown: Pin<Box<_>> = if let Some(adapter) = &self.id_lists_adapter.inner {
387 adapter.shutdown(timeout)
388 } else {
389 Box::pin(async { Ok(()) })
390 };
391
392 shutdown_output_logger();
393
394 try_join!(
395 id_list_shutdown,
396 self.event_logger.shutdown(),
397 self.specs_adapter.inner.shutdown(timeout, &self.statsig_runtime),
398 )
399 } => {
400 match sub_result {
401 Ok(_) => {
402 log_d!(TAG, "All Statsig tasks shutdown successfully");
403 Ok(())
404 }
405 Err(e) => {
406 log_w!(TAG, "Error during shutdown: {:?}", e);
407 Err(e)
408 }
409 }
410 }
411 };
412
413 #[cfg(feature = "ffi-support")]
414 self.delayed_exposure_store.clear();
415
416 self.statsig_runtime.shutdown();
417 shutdown_result
418 }
419
420 pub fn get_context(&self) -> StatsigContext {
421 StatsigContext {
422 sdk_key: self.sdk_key.clone(),
423 options: self.options.clone(),
424 local_override_adapter: self.override_adapter.clone(),
425 error_observer: self.error_observer.clone(),
426 diagnostics_observer: self.diagnostics_observer.clone(),
427 console_capture_observer: self.console_capture_observer.clone(),
428 spec_store: self.spec_store.clone(),
429 console_capture: self.console_capture.clone(),
430 }
431 }
432}
433
434impl Statsig {
437 pub fn shared() -> Arc<Statsig> {
438 let lock = match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
439 Some(lock) => lock,
440 None => {
441 log_e!(
442 TAG,
443 "Statsig::shared() mutex error: Failed to lock SHARED_INSTANCE"
444 );
445 return Arc::new(Statsig::new(ERROR_SDK_KEY, None));
446 }
447 };
448
449 match lock.as_ref() {
450 Some(statsig) => statsig.clone(),
451 None => {
452 log_e!(
453 TAG,
454 "Statsig::shared() called, but no instance has been set with Statsig::new_shared(...)"
455 );
456 Arc::new(Statsig::new(ERROR_SDK_KEY, None))
457 }
458 }
459 }
460
461 pub fn new_shared(
462 sdk_key: &str,
463 options: Option<Arc<StatsigOptions>>,
464 ) -> Result<Arc<Statsig>, StatsigErr> {
465 match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
466 Some(mut lock) => {
467 if lock.is_some() {
468 let message = "Statsig shared instance already exists. Call Statsig::remove_shared() before creating a new instance.";
469 log_e!(TAG, "{}", message);
470 return Err(StatsigErr::SharedInstanceFailure(message.to_string()));
471 }
472
473 let statsig = Arc::new(Statsig::new(sdk_key, options));
474 *lock = Some(statsig.clone());
475 Ok(statsig)
476 }
477 None => {
478 let message = "Statsig::new_shared() mutex error: Failed to lock SHARED_INSTANCE";
479 log_e!(TAG, "{}", message);
480 Err(StatsigErr::SharedInstanceFailure(message.to_string()))
481 }
482 }
483 }
484
485 pub fn remove_shared() {
486 match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
487 Some(mut lock) => {
488 *lock = None;
489 }
490 None => {
491 log_e!(
492 TAG,
493 "Statsig::remove_shared() mutex error: Failed to lock SHARED_INSTANCE"
494 );
495 }
496 }
497 }
498
499 pub fn has_shared_instance() -> bool {
500 match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
501 Some(lock) => lock.is_some(),
502 None => false,
503 }
504 }
505}
506
507impl Statsig {
510 pub fn get_client_init_response(&self, user: &StatsigUser) -> InitializeResponse {
511 self.get_client_init_response_with_options(user, &ClientInitResponseOptions::default())
512 }
513
514 pub fn get_client_init_response_with_options(
515 &self,
516 user: &StatsigUser,
517 options: &ClientInitResponseOptions,
518 ) -> InitializeResponse {
519 let user_internal = self.internalize_user(user);
520 self.get_client_init_response_with_options_for_internal_user(&user_internal, options)
521 }
522
523 #[doc(hidden)]
524 pub fn get_client_init_response_with_options_for_internal_user(
525 &self,
526 user_internal: &StatsigUserInternal<'_, '_>,
527 options: &ClientInitResponseOptions,
528 ) -> InitializeResponse {
529 let data = self.spec_store.load_data();
530 let plan = data.gcir_evaluation_plan(&self.hashing);
531
532 let mut context = self.create_gcir_eval_context(user_internal, &data, options);
533
534 match GCIRFormatter::generate_v1_format_with_plan(&mut context, options, plan) {
535 Ok(mut response) => {
536 if response.has_updates {
537 response.time = data.lcut();
538 }
539 response
540 }
541 Err(e) => {
542 log_error_to_statsig_and_console!(
543 &self.ops_stats,
544 TAG,
545 StatsigErr::GCIRError(e.to_string())
546 );
547 InitializeResponse::blank(user_internal.clone())
548 }
549 }
550 }
551
552 pub fn get_client_init_response_as_string(&self, user: &StatsigUser) -> String {
553 serde_json::to_string(&self.get_client_init_response(user)).unwrap_or_default()
554 }
555
556 pub fn get_client_init_response_with_options_as_string(
557 &self,
558 user: &StatsigUser,
559 options: &ClientInitResponseOptions,
560 ) -> String {
561 let user_internal = self.internalize_user(user);
562 self.get_client_init_response_with_options_as_string_for_internal_user(
563 &user_internal,
564 options,
565 )
566 }
567
568 #[doc(hidden)]
569 pub fn get_client_init_response_with_options_as_string_for_internal_user(
570 &self,
571 user_internal: &StatsigUserInternal<'_, '_>,
572 options: &ClientInitResponseOptions,
573 ) -> String {
574 let data = self.spec_store.load_data();
575
576 let mut context = self.create_gcir_eval_context(user_internal, &data, options);
577 let response_lcut = data.lcut();
578
579 match options.response_format {
580 Some(GCIRResponseFormat::InitializeWithSecondaryExposureMapping) => self
581 .stringify_gcir_response(
582 GCIRFormatter::generate_v2_format(&mut context, options).map(|mut response| {
583 if response.has_updates {
584 response.time = response_lcut;
585 }
586 response
587 }),
588 || InitializeEvaluationsResponse::blank(user_internal.clone()),
589 ),
590 Some(GCIRResponseFormat::InitializeV2) => self.stringify_gcir_response(
591 GCIRFormatter::generate_init_v2_format(&mut context, options).map(
592 |mut response| {
593 if response.has_updates {
594 response.time = response_lcut;
595 }
596 response
597 },
598 ),
599 || InitializeV2Response::blank(user_internal.clone()),
600 ),
601 _ => {
602 let plan = data.gcir_evaluation_plan(&self.hashing);
603
604 self.stringify_gcir_response(
605 GCIRFormatter::generate_v1_format_with_plan(&mut context, options, plan).map(
606 |mut response| {
607 if response.has_updates {
608 response.time = response_lcut;
609 }
610 response
611 },
612 ),
613 || InitializeResponse::blank(user_internal.clone()),
614 )
615 }
616 }
617 }
618}
619
620impl Statsig {
623 pub fn log_event(
624 &self,
625 user: &StatsigUser,
626 event_name: &str,
627 value: Option<String>,
628 metadata: Option<HashMap<String, String>>,
629 ) {
630 let user_internal = self.internalize_user(user);
631
632 self.event_logger.enqueue(EnqueuePassthroughOp {
633 event: StatsigEventInternal::new_custom_event(
634 user_internal.to_loggable(),
635 event_name.to_string(),
636 value.map(|v| json!(v)),
637 metadata,
638 ),
639 });
640 }
641
642 pub fn log_event_with_number(
643 &self,
644 user: &StatsigUser,
645 event_name: &str,
646 value: Option<f64>,
647 metadata: Option<HashMap<String, String>>,
648 ) {
649 let user_internal = self.internalize_user(user);
650 self.event_logger.enqueue(EnqueuePassthroughOp {
651 event: StatsigEventInternal::new_custom_event(
652 user_internal.to_loggable(),
653 event_name.to_string(),
654 value.map(|v| json!(v)),
655 metadata,
656 ),
657 });
658 }
659
660 pub fn log_event_with_typed_metadata(
661 &self,
662 user: &StatsigUser,
663 event_name: &str,
664 value: Option<String>,
665 metadata: Option<HashMap<String, Value>>,
666 ) {
667 let user_internal = self.internalize_user(user);
668 self.log_event_with_typed_metadata_for_internal_user(
669 &user_internal,
670 event_name,
671 value,
672 metadata,
673 );
674 }
675
676 #[doc(hidden)]
677 pub fn log_event_with_typed_metadata_for_internal_user(
678 &self,
679 user_internal: &StatsigUserInternal<'_, '_>,
680 event_name: &str,
681 value: Option<String>,
682 metadata: Option<HashMap<String, Value>>,
683 ) {
684 self.event_logger.enqueue(EnqueuePassthroughOp {
685 event: StatsigEventInternal::new_custom_event_with_typed_metadata(
686 user_internal.to_loggable(),
687 event_name.to_string(),
688 value.map(|v| json!(v)),
689 metadata,
690 ),
691 });
692 }
693
694 pub fn log_event_with_number_and_typed_metadata(
695 &self,
696 user: &StatsigUser,
697 event_name: &str,
698 value: Option<f64>,
699 metadata: Option<HashMap<String, Value>>,
700 ) {
701 let user_internal = self.internalize_user(user);
702 self.log_event_with_number_and_typed_metadata_for_internal_user(
703 &user_internal,
704 event_name,
705 value,
706 metadata,
707 );
708 }
709
710 #[doc(hidden)]
711 pub fn log_event_with_number_and_typed_metadata_for_internal_user(
712 &self,
713 user_internal: &StatsigUserInternal<'_, '_>,
714 event_name: &str,
715 value: Option<f64>,
716 metadata: Option<HashMap<String, Value>>,
717 ) {
718 self.event_logger.enqueue(EnqueuePassthroughOp {
719 event: StatsigEventInternal::new_custom_event_with_typed_metadata(
720 user_internal.to_loggable(),
721 event_name.to_string(),
722 value.map(|v| json!(v)),
723 metadata,
724 ),
725 });
726 }
727
728 pub fn forward_log_line_event(
729 &self,
730 user: &StatsigUser,
731 log_level: StatsigLogLineLevel,
732 value: Option<String>,
733 metadata: Option<HashMap<String, String>>,
734 ) {
735 let user_internal = self.internalize_user(user);
736 self.event_logger.enqueue(EnqueuePassthroughOp {
737 event: StatsigEventInternal::new_statsig_log_line_event(
738 user_internal.to_loggable(),
739 log_level,
740 value,
741 metadata,
742 None,
743 ),
744 });
745 }
746
747 pub fn log_layer_param_exposure_with_layer_json(
748 &self,
749 layer_json: String,
750 parameter_name: String,
751 ) {
752 let layer = match serde_json::from_str::<Layer>(&layer_json) {
753 Ok(layer) => layer,
754 Err(e) => {
755 log_error_to_statsig_and_console!(
756 self.ops_stats.clone(),
757 TAG,
758 StatsigErr::ShutdownFailure(e.to_string())
759 );
760 return;
761 }
762 };
763
764 self.log_layer_param_exposure_with_layer(layer, parameter_name);
765 }
766
767 pub fn log_layer_param_exposure_with_layer(&self, layer: Layer, parameter_name: String) {
768 if layer.__disable_exposure {
769 self.event_logger.increment_non_exposure_checks(&layer.name);
770 return;
771 }
772
773 self.event_logger
774 .enqueue(EnqueueLayerParamExpoOp::LayerOwned(
775 Utc::now().timestamp_millis() as u64,
776 Box::new(layer),
777 parameter_name,
778 ExposureTrigger::Auto,
779 ));
780 }
781
782 pub async fn flush_events(&self) {
783 let _ = self.event_logger.flush_all_pending_events().await;
784 }
785}
786
787impl Statsig {
790 pub fn get_string_parameter_from_store(
791 &self,
792 user: &StatsigUser,
793 parameter_store_name: &str,
794 parameter_name: &str,
795 fallback: Option<String>,
796 options: Option<ParameterStoreEvaluationOptions>,
797 ) -> Option<String> {
798 self.get_parameter_from_store(
799 user,
800 parameter_store_name,
801 parameter_name,
802 fallback,
803 options,
804 )
805 }
806
807 pub fn get_boolean_parameter_from_store(
808 &self,
809 user: &StatsigUser,
810 parameter_store_name: &str,
811 parameter_name: &str,
812 fallback: Option<bool>,
813 options: Option<ParameterStoreEvaluationOptions>,
814 ) -> Option<bool> {
815 self.get_parameter_from_store(
816 user,
817 parameter_store_name,
818 parameter_name,
819 fallback,
820 options,
821 )
822 }
823
824 pub fn get_float_parameter_from_store(
825 &self,
826 user: &StatsigUser,
827 parameter_store_name: &str,
828 parameter_name: &str,
829 fallback: Option<f64>,
830 options: Option<ParameterStoreEvaluationOptions>,
831 ) -> Option<f64> {
832 self.get_parameter_from_store(
833 user,
834 parameter_store_name,
835 parameter_name,
836 fallback,
837 options,
838 )
839 }
840
841 pub fn get_integer_parameter_from_store(
842 &self,
843 user: &StatsigUser,
844 parameter_store_name: &str,
845 parameter_name: &str,
846 fallback: Option<i64>,
847 options: Option<ParameterStoreEvaluationOptions>,
848 ) -> Option<i64> {
849 self.get_parameter_from_store(
850 user,
851 parameter_store_name,
852 parameter_name,
853 fallback,
854 options,
855 )
856 }
857
858 pub fn get_array_parameter_from_store(
859 &self,
860 user: &StatsigUser,
861 parameter_store_name: &str,
862 parameter_name: &str,
863 fallback: Option<Vec<Value>>,
864 options: Option<ParameterStoreEvaluationOptions>,
865 ) -> Option<Vec<Value>> {
866 self.get_parameter_from_store(
867 user,
868 parameter_store_name,
869 parameter_name,
870 fallback,
871 options,
872 )
873 }
874
875 pub fn get_object_parameter_from_store(
876 &self,
877 user: &StatsigUser,
878 parameter_store_name: &str,
879 parameter_name: &str,
880 fallback: Option<HashMap<String, Value>>,
881 options: Option<ParameterStoreEvaluationOptions>,
882 ) -> Option<HashMap<String, Value>> {
883 self.get_parameter_from_store(
884 user,
885 parameter_store_name,
886 parameter_name,
887 fallback,
888 options,
889 )
890 }
891
892 pub fn get_parameter_from_store<T: DeserializeOwned>(
893 &self,
894 user: &StatsigUser,
895 parameter_store_name: &str,
896 parameter_name: &str,
897 fallback: Option<T>,
898 options: Option<ParameterStoreEvaluationOptions>,
899 ) -> Option<T> {
900 let store = self.get_parameter_store_with_user_and_options(
901 Some(user),
902 parameter_store_name,
903 options.unwrap_or_default(),
904 );
905 match fallback {
906 Some(fallback) => Some(store.get(user, parameter_name, fallback)),
907 None => store.get_opt(user, parameter_name),
908 }
909 }
910
911 pub fn get_parameter_store(&self, parameter_store_name: &str) -> ParameterStore<'_> {
912 self.get_parameter_store_with_options(
913 parameter_store_name,
914 ParameterStoreEvaluationOptions::default(),
915 )
916 }
917
918 pub fn get_parameter_store_for_user(
919 &self,
920 user: &StatsigUser,
921 parameter_store_name: &str,
922 ) -> ParameterStore<'_> {
923 self.get_parameter_store_with_user_and_options(
924 Some(user),
925 parameter_store_name,
926 ParameterStoreEvaluationOptions::default(),
927 )
928 }
929
930 pub fn get_parameter_store_with_options(
931 &self,
932 parameter_store_name: &str,
933 options: ParameterStoreEvaluationOptions,
934 ) -> ParameterStore<'_> {
935 self.get_parameter_store_with_user_and_options(None, parameter_store_name, options)
936 }
937
938 fn get_parameter_store_with_user_and_options(
939 &self,
940 user: Option<&StatsigUser>,
941 parameter_store_name: &str,
942 options: ParameterStoreEvaluationOptions,
943 ) -> ParameterStore<'_> {
944 let store_name_intern = InternedString::from_str_ref(parameter_store_name);
945
946 self.event_logger
947 .increment_non_exposure_checks(parameter_store_name);
948
949 let data = self.spec_store.load_data();
950
951 if let Some(user) = user {
952 if let Some((override_result, parameters)) =
953 self.get_parameter_store_override(user, parameter_store_name)
954 {
955 let details = EvaluationDetails::recognized_but_overridden(
956 data.lcut(),
957 data.time_received_at,
958 override_result.override_reason.unwrap_or("Override"),
959 override_result.version,
960 );
961
962 return ParameterStore {
963 name: parameter_store_name.to_string(),
964 parameters,
965 details,
966 options,
967 _statsig_ref: self,
968 };
969 }
970 }
971
972 let stores = &data.snapshot.param_stores;
973 let store = match stores {
974 Some(stores) => stores.get(&store_name_intern),
975 None => {
976 return ParameterStore {
977 name: parameter_store_name.to_string(),
978 parameters: HashMap::new(),
979 details: EvaluationDetails::unrecognized(
980 &data.source,
981 data.lcut(),
982 data.time_received_at,
983 ),
984 options,
985 _statsig_ref: self,
986 };
987 }
988 };
989 match store {
990 Some(store) => ParameterStore {
991 name: parameter_store_name.to_string(),
992 parameters: store.parameters.clone(),
993 details: EvaluationDetails::recognized(
994 &data.source,
995 data.lcut(),
996 data.time_received_at,
997 &EvaluatorResult::default(),
998 ),
999 options,
1000 _statsig_ref: self,
1001 },
1002 None => ParameterStore {
1003 name: parameter_store_name.to_string(),
1004 parameters: HashMap::new(),
1005 details: EvaluationDetails::unrecognized(
1006 &data.source,
1007 data.lcut(),
1008 data.time_received_at,
1009 ),
1010 options,
1011 _statsig_ref: self,
1012 },
1013 }
1014 }
1015
1016 pub(crate) fn get_parameter_store_override(
1017 &self,
1018 user: &StatsigUser,
1019 parameter_store_name: &str,
1020 ) -> Option<(EvaluatorResult, HashMap<String, Parameter>)> {
1021 let adapter = self.override_adapter.as_ref()?;
1022
1023 let mut result = EvaluatorResult::default();
1024 if !adapter.get_parameter_store_override(user, parameter_store_name, &mut result) {
1025 return None;
1026 }
1027
1028 let mut parameters = HashMap::new();
1029 if let Some(json_value) = &result.json_value {
1030 if let Some(map) = json_value.get_json() {
1031 for (param_name, param_value) in map {
1032 if let Ok(parameter) = serde_json::from_value::<Parameter>(param_value) {
1033 parameters.insert(param_name, parameter);
1034 }
1035 }
1036 }
1037 }
1038
1039 Some((result, parameters))
1040 }
1041}
1042
1043impl Statsig {
1046 pub fn identify(&self, user: &StatsigUser) {
1047 let user_internal = self.internalize_user(user);
1048 self.identify_internal_user(&user_internal);
1049 }
1050
1051 #[doc(hidden)]
1052 pub fn identify_internal_user(&self, user_internal: &StatsigUserInternal<'_, '_>) {
1053 self.event_logger.enqueue(EnqueuePassthroughOp {
1054 event: StatsigEventInternal::new_custom_event(
1055 user_internal.to_loggable(),
1056 "statsig::identify".to_string(),
1057 None,
1058 None,
1059 ),
1060 });
1061 }
1062}
1063
1064impl Statsig {
1067 pub fn get_cmab_ranked_groups(
1068 &self,
1069 user: &StatsigUser,
1070 cmab_name: &str,
1071 ) -> Vec<CMABRankedGroup> {
1072 self.event_logger.increment_non_exposure_checks(cmab_name);
1073
1074 let data = self.spec_store.load_data();
1075 let user_internal = self.internalize_user(user);
1076 let mut context = self.create_standard_eval_context(
1077 &user_internal,
1078 &data,
1079 data.snapshot.app_id.as_ref(),
1080 self.override_adapter.as_ref(),
1081 true,
1082 );
1083 get_cmab_ranked_list(&mut context, cmab_name)
1084 }
1085
1086 pub fn log_cmab_exposure_for_group(
1087 &self,
1088 user: &StatsigUser,
1089 cmab_name: &str,
1090 group_id: String,
1091 ) {
1092 let user_internal = self.internalize_user(user);
1093
1094 let mut experiment = self.get_experiment_impl(&user_internal, cmab_name, None);
1095 experiment.rule_id = group_id;
1096
1097 self.event_logger.enqueue(EnqueueExperimentExpoOp {
1098 exposure_time: Utc::now().timestamp_millis() as u64,
1099 user: &user_internal,
1100 experiment: &experiment,
1101 trigger: ExposureTrigger::Manual,
1102 });
1103 }
1104}
1105
1106impl Statsig {
1109 pub fn override_gate(&self, gate_name: &str, value: bool, id: Option<&str>) {
1110 if let Some(adapter) = &self.override_adapter {
1111 adapter.override_gate(gate_name, value, id);
1112 }
1113 }
1114
1115 pub fn override_dynamic_config(
1116 &self,
1117 config_name: &str,
1118 value: HashMap<String, serde_json::Value>,
1119 id: Option<&str>,
1120 ) {
1121 if let Some(adapter) = &self.override_adapter {
1122 adapter.override_dynamic_config(config_name, value, id);
1123 }
1124 }
1125
1126 pub fn override_layer(
1127 &self,
1128 layer_name: &str,
1129 value: HashMap<String, serde_json::Value>,
1130 id: Option<&str>,
1131 ) {
1132 if let Some(adapter) = &self.override_adapter {
1133 adapter.override_layer(layer_name, value, id);
1134 }
1135 }
1136
1137 pub fn override_parameter_store(
1138 &self,
1139 param_name: &str,
1140 value: HashMap<String, serde_json::Value>,
1141 id: Option<&str>,
1142 ) {
1143 if let Some(adapter) = &self.override_adapter {
1144 adapter.override_parameter_store(param_name, value, id);
1145 }
1146 }
1147
1148 pub fn override_experiment(
1149 &self,
1150 experiment_name: &str,
1151 value: HashMap<String, serde_json::Value>,
1152 id: Option<&str>,
1153 ) {
1154 if let Some(adapter) = &self.override_adapter {
1155 adapter.override_experiment(experiment_name, value, id);
1156 }
1157 }
1158
1159 pub fn override_experiment_by_group_name(
1160 &self,
1161 experiment_name: &str,
1162 group_name: &str,
1163 id: Option<&str>,
1164 ) {
1165 if let Some(adapter) = &self.override_adapter {
1166 adapter.override_experiment_by_group_name(experiment_name, group_name, id);
1167 }
1168 }
1169
1170 pub fn remove_gate_override(&self, gate_name: &str, id: Option<&str>) {
1171 if let Some(adapter) = &self.override_adapter {
1172 adapter.remove_gate_override(gate_name, id);
1173 }
1174 }
1175
1176 pub fn remove_dynamic_config_override(&self, config_name: &str, id: Option<&str>) {
1177 if let Some(adapter) = &self.override_adapter {
1178 adapter.remove_dynamic_config_override(config_name, id);
1179 }
1180 }
1181
1182 pub fn remove_experiment_override(&self, experiment_name: &str, id: Option<&str>) {
1183 if let Some(adapter) = &self.override_adapter {
1184 adapter.remove_experiment_override(experiment_name, id);
1185 }
1186 }
1187
1188 pub fn remove_layer_override(&self, layer_name: &str, id: Option<&str>) {
1189 if let Some(adapter) = &self.override_adapter {
1190 adapter.remove_layer_override(layer_name, id);
1191 }
1192 }
1193
1194 pub fn remove_parameter_store_override(&self, parameter_store_name: &str, id: Option<&str>) {
1195 if let Some(adapter) = &self.override_adapter {
1196 adapter.remove_parameter_store_override(parameter_store_name, id);
1197 }
1198 }
1199
1200 pub fn remove_all_overrides(&self) {
1201 if let Some(adapter) = &self.override_adapter {
1202 adapter.remove_all_overrides();
1203 }
1204 }
1205}
1206
1207impl Statsig {
1210 pub fn get_feature_gate_list(&self) -> Vec<String> {
1211 self.spec_store
1212 .unperformant_keys_entity_filter("feature_gates", "feature_gate")
1213 }
1214
1215 pub fn get_dynamic_config_list(&self) -> Vec<String> {
1216 self.spec_store
1217 .unperformant_keys_entity_filter("dynamic_configs", "dynamic_config")
1218 }
1219
1220 pub fn get_experiment_list(&self) -> Vec<String> {
1221 self.spec_store
1222 .unperformant_keys_entity_filter("dynamic_configs", "experiment")
1223 }
1224
1225 pub fn get_autotune_list(&self) -> Vec<String> {
1226 self.spec_store
1227 .unperformant_keys_entity_filter("dynamic_configs", "autotune")
1228 }
1229
1230 pub fn get_parameter_store_list(&self) -> Vec<String> {
1231 self.spec_store
1232 .unperformant_keys_entity_filter("param_stores", "*")
1233 }
1234
1235 pub fn get_layer_list(&self) -> Vec<String> {
1236 self.spec_store
1237 .unperformant_keys_entity_filter("layer_configs", "*")
1238 }
1239
1240 pub fn __get_parsed_user_agent_value(
1241 &self,
1242 user: &StatsigUser,
1243 ) -> Option<ParsedUserAgentValue> {
1244 UserAgentParser::get_parsed_user_agent_value_for_user(user, &self.options)
1245 }
1246}
1247
1248impl Statsig {
1251 pub fn check_gate(&self, user: &StatsigUser, gate_name: &str) -> bool {
1252 self.check_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1253 }
1254
1255 pub fn check_gate_with_options(
1256 &self,
1257 user: &StatsigUser,
1258 gate_name: &str,
1259 options: FeatureGateEvaluationOptions,
1260 ) -> bool {
1261 let user_internal = self.internalize_user(user);
1262 self.check_gate_with_options_for_internal_user(&user_internal, gate_name, options)
1263 }
1264
1265 #[doc(hidden)]
1266 pub fn check_gate_with_options_for_internal_user(
1267 &self,
1268 user_internal: &StatsigUserInternal<'_, '_>,
1269 gate_name: &str,
1270 options: FeatureGateEvaluationOptions,
1271 ) -> bool {
1272 let disable_exposure_logging = options.disable_exposure_logging;
1273 let (details, evaluation) = self.get_gate_evaluation(
1274 user_internal,
1275 gate_name,
1276 Some(options.disable_exposure_logging),
1277 );
1278
1279 let value = evaluation.as_ref().map(|e| e.value).unwrap_or_default();
1280 let rule_id = evaluation
1281 .as_ref()
1282 .map(|e| e.base.rule_id.clone())
1283 .unwrap_or_default();
1284
1285 if disable_exposure_logging {
1286 log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1287 self.event_logger.increment_non_exposure_checks(gate_name);
1288 } else {
1289 self.event_logger.enqueue(EnqueueGateExpoOp {
1290 exposure_time: Utc::now().timestamp_millis() as u64,
1291 user: user_internal,
1292 queried_gate_name: gate_name,
1293 evaluation: evaluation.map(Cow::Owned),
1294 details: details.clone(),
1295 trigger: ExposureTrigger::Auto,
1296 });
1297 }
1298
1299 self.emit_gate_evaluated(gate_name, rule_id.as_str(), value, &details.reason);
1300
1301 value
1302 }
1303
1304 pub fn get_feature_gate(&self, user: &StatsigUser, gate_name: &str) -> FeatureGate {
1305 self.get_feature_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1306 }
1307
1308 pub fn get_feature_gate_with_options(
1309 &self,
1310 user: &StatsigUser,
1311 gate_name: &str,
1312 options: FeatureGateEvaluationOptions,
1313 ) -> FeatureGate {
1314 let user_internal = self.internalize_user(user);
1315 let disable_exposure_logging = options.disable_exposure_logging;
1316 let (details, evaluation) = self.get_gate_evaluation(
1317 &user_internal,
1318 gate_name,
1319 Some(options.disable_exposure_logging),
1320 );
1321
1322 if disable_exposure_logging {
1323 log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1324 self.event_logger.increment_non_exposure_checks(gate_name);
1325 } else {
1326 self.event_logger.enqueue(EnqueueGateExpoOp {
1327 exposure_time: Utc::now().timestamp_millis() as u64,
1328 user: &user_internal,
1329 queried_gate_name: gate_name,
1330 evaluation: evaluation.as_ref().map(Cow::Borrowed),
1331 details: details.clone(),
1332 trigger: ExposureTrigger::Auto,
1333 });
1334 }
1335
1336 let gate = make_feature_gate(gate_name, evaluation, details);
1337 self.emit_gate_evaluated(gate_name, &gate.rule_id, gate.value, &gate.details.reason);
1338 gate
1339 }
1340
1341 pub fn manually_log_gate_exposure(&self, user: &StatsigUser, gate_name: &str) {
1342 let interned_gate_name = InternedString::from_str_ref(gate_name);
1343 let user_internal = self.internalize_user(user);
1344 self.manually_log_gate_exposure_for_internal_user(&user_internal, &interned_gate_name);
1345 }
1346
1347 #[doc(hidden)]
1348 pub fn manually_log_gate_exposure_for_internal_user(
1349 &self,
1350 user_internal: &StatsigUserInternal<'_, '_>,
1351 gate_name: &InternedString,
1352 ) {
1353 let (details, evaluation) =
1354 self.evaluate_spec_raw(user_internal, gate_name.as_str(), &SpecType::Gate, None);
1355
1356 self.event_logger.enqueue(EnqueueExposureOp::gate_exposure(
1357 user_internal,
1358 gate_name,
1359 ExposureTrigger::Manual,
1360 details,
1361 evaluation,
1362 ));
1363 }
1364
1365 pub fn get_fields_needed_for_gate(&self, gate_name: &str) -> Vec<String> {
1366 self.spec_store
1367 .get_fields_used_for_entity(gate_name, SpecType::Gate)
1368 }
1369}
1370
1371impl Statsig {
1374 pub fn get_dynamic_config(
1375 &self,
1376 user: &StatsigUser,
1377 dynamic_config_name: &str,
1378 ) -> DynamicConfig {
1379 self.get_dynamic_config_with_options(
1380 user,
1381 dynamic_config_name,
1382 DynamicConfigEvaluationOptions::default(),
1383 )
1384 }
1385
1386 pub fn get_dynamic_config_with_options(
1387 &self,
1388 user: &StatsigUser,
1389 dynamic_config_name: &str,
1390 options: DynamicConfigEvaluationOptions,
1391 ) -> DynamicConfig {
1392 let user_internal = self.internalize_user(user);
1393 let disable_exposure_logging = options.disable_exposure_logging;
1394 let dynamic_config = self.get_dynamic_config_impl(
1395 &user_internal,
1396 dynamic_config_name,
1397 Some(options.disable_exposure_logging),
1398 );
1399
1400 if disable_exposure_logging {
1401 log_d!(
1402 TAG,
1403 "Exposure logging is disabled for Dynamic Config {}",
1404 dynamic_config_name
1405 );
1406 self.event_logger
1407 .increment_non_exposure_checks(dynamic_config_name);
1408 } else {
1409 self.event_logger.enqueue(EnqueueConfigExpoOp {
1410 exposure_time: Utc::now().timestamp_millis() as u64,
1411 user: &user_internal,
1412 config: &dynamic_config,
1413 trigger: ExposureTrigger::Auto,
1414 });
1415 }
1416
1417 self.emit_dynamic_config_evaluated(&dynamic_config);
1418
1419 dynamic_config
1420 }
1421
1422 pub fn manually_log_dynamic_config_exposure(
1423 &self,
1424 user: &StatsigUser,
1425 dynamic_config_name: &str,
1426 ) {
1427 let interned_dynamic_config_name = InternedString::from_str_ref(dynamic_config_name);
1428 let user_internal = self.internalize_user(user);
1429 self.manually_log_dynamic_config_exposure_for_internal_user(
1430 &user_internal,
1431 &interned_dynamic_config_name,
1432 );
1433 }
1434
1435 #[doc(hidden)]
1436 pub fn manually_log_dynamic_config_exposure_for_internal_user(
1437 &self,
1438 user_internal: &StatsigUserInternal<'_, '_>,
1439 dynamic_config_name: &InternedString,
1440 ) {
1441 let (details, evaluation) = self.evaluate_spec_raw(
1442 user_internal,
1443 dynamic_config_name.as_str(),
1444 &SpecType::DynamicConfig,
1445 None,
1446 );
1447
1448 self.event_logger
1449 .enqueue(EnqueueExposureOp::dynamic_config_exposure(
1450 user_internal,
1451 dynamic_config_name,
1452 ExposureTrigger::Manual,
1453 details,
1454 evaluation,
1455 ));
1456 }
1457
1458 pub fn get_fields_needed_for_dynamic_config(&self, config_name: &str) -> Vec<String> {
1459 self.spec_store
1460 .get_fields_used_for_entity(config_name, SpecType::DynamicConfig)
1461 }
1462}
1463
1464impl Statsig {
1467 pub fn get_experiment(&self, user: &StatsigUser, experiment_name: &str) -> Experiment {
1468 self.get_experiment_with_options(
1469 user,
1470 experiment_name,
1471 ExperimentEvaluationOptions::default(),
1472 )
1473 }
1474
1475 pub fn get_experiment_with_options(
1476 &self,
1477 user: &StatsigUser,
1478 experiment_name: &str,
1479 options: ExperimentEvaluationOptions,
1480 ) -> Experiment {
1481 let user_internal = self.internalize_user(user);
1482 let disable_exposure_logging = options.disable_exposure_logging;
1483 let mut experiment = self.get_experiment_impl(
1484 &user_internal,
1485 experiment_name,
1486 Some(options.disable_exposure_logging),
1487 );
1488
1489 experiment = PersistentValuesManager::try_apply_sticky_value_to_experiment(
1490 &self.persistent_values_manager,
1491 &user_internal,
1492 &options,
1493 experiment,
1494 );
1495
1496 if disable_exposure_logging {
1497 log_d!(
1498 TAG,
1499 "Exposure logging is disabled for experiment {}",
1500 experiment_name
1501 );
1502 self.event_logger
1503 .increment_non_exposure_checks(experiment_name);
1504 } else {
1505 self.event_logger.enqueue(EnqueueExperimentExpoOp {
1506 exposure_time: Utc::now().timestamp_millis() as u64,
1507 user: &user_internal,
1508 experiment: &experiment,
1509 trigger: ExposureTrigger::Auto,
1510 });
1511 }
1512
1513 self.emit_experiment_evaluated(&experiment);
1514
1515 experiment
1516 }
1517
1518 pub fn manually_log_experiment_exposure(&self, user: &StatsigUser, experiment_name: &str) {
1519 let interned_experiment_name = InternedString::from_str_ref(experiment_name);
1520 let user_internal = self.internalize_user(user);
1521 self.manually_log_experiment_exposure_for_internal_user(
1522 &user_internal,
1523 &interned_experiment_name,
1524 );
1525 }
1526
1527 #[doc(hidden)]
1528 pub fn manually_log_experiment_exposure_for_internal_user(
1529 &self,
1530 user_internal: &StatsigUserInternal<'_, '_>,
1531 experiment_name: &InternedString,
1532 ) {
1533 self.manually_log_experiment_exposure_for_internal_user_with_metadata(
1534 user_internal,
1535 experiment_name,
1536 None,
1537 );
1538 }
1539
1540 #[doc(hidden)]
1541 pub fn manually_log_experiment_exposure_for_internal_user_with_metadata(
1542 &self,
1543 user_internal: &StatsigUserInternal<'_, '_>,
1544 experiment_name: &InternedString,
1545 exposure_metadata: Option<HashMap<String, Value>>,
1546 ) {
1547 let (details, evaluation) = self.evaluate_spec_raw(
1548 user_internal,
1549 experiment_name.as_str(),
1550 &SpecType::Experiment,
1551 None,
1552 );
1553
1554 self.event_logger.enqueue(
1555 EnqueueExposureOp::experiment_exposure(
1556 user_internal,
1557 experiment_name,
1558 ExposureTrigger::Manual,
1559 details,
1560 evaluation,
1561 )
1562 .with_extra_metadata(exposure_metadata),
1563 );
1564 }
1565
1566 pub fn get_fields_needed_for_experiment(&self, experiment_name: &str) -> Vec<String> {
1567 self.spec_store
1568 .get_fields_used_for_entity(experiment_name, SpecType::Experiment)
1569 }
1570
1571 pub fn get_experiment_by_group_name(
1572 &self,
1573 experiment_name: &str,
1574 group_name: &str,
1575 ) -> Experiment {
1576 self.get_experiment_by_group_name_impl(
1577 experiment_name,
1578 group_name,
1579 |spec, rule, details| {
1580 if let (Some(spec), Some(rule)) = (spec, rule) {
1581 let value = rule.return_value().json_value().unwrap_or_default();
1582 let rule_id = rule.id().as_str().to_string();
1583 let id_type = rule.id_type().value().to_string();
1584 let group_name = rule.group_name().map(|name| name.as_str().to_string());
1585
1586 return Experiment {
1587 name: experiment_name.to_string(),
1588 value,
1589 rule_id,
1590 id_type,
1591 group_name,
1592 details,
1593 is_experiment_active: spec.is_active().unwrap_or(false),
1594 __evaluation: None,
1595 };
1596 }
1597
1598 make_experiment(experiment_name, None, details)
1599 },
1600 )
1601 }
1602
1603 pub fn get_experiment_by_group_id_advanced(
1604 &self,
1605 experiment_name: &str,
1606 group_id: &str,
1607 ) -> Experiment {
1608 self.get_experiment_by_group_id_advanced_impl(
1609 experiment_name,
1610 group_id,
1611 |spec, rule, details| {
1612 if let (Some(spec), Some(rule)) = (spec, rule) {
1613 let value = rule.return_value().json_value().unwrap_or_default();
1614 let rule_id = rule.id().as_str().to_string();
1615 let id_type = rule.id_type().value().to_string();
1616 let group_name = rule.group_name().map(|name| name.as_str().to_string());
1617
1618 return Experiment {
1619 name: experiment_name.to_string(),
1620 value,
1621 rule_id,
1622 id_type,
1623 group_name,
1624 details,
1625 is_experiment_active: spec.is_active().unwrap_or(false),
1626 __evaluation: None,
1627 };
1628 }
1629
1630 make_experiment(experiment_name, None, details)
1631 },
1632 )
1633 }
1634
1635 fn get_experiment_by_group_name_impl<T>(
1636 &self,
1637 experiment_name: &str,
1638 group_name: &str,
1639 result_factory: impl FnOnce(Option<SpecView<'_>>, Option<RuleRef<'_>>, EvaluationDetails) -> T,
1640 ) -> T {
1641 self.get_experiment_by_rule_match_impl(
1642 experiment_name,
1643 |rule| {
1644 rule.group_name()
1645 .is_some_and(|name| name.as_str() == group_name)
1646 },
1647 result_factory,
1648 )
1649 }
1650
1651 fn get_experiment_by_group_id_advanced_impl<T>(
1652 &self,
1653 experiment_name: &str,
1654 rule_id: &str,
1655 result_factory: impl FnOnce(Option<SpecView<'_>>, Option<RuleRef<'_>>, EvaluationDetails) -> T,
1656 ) -> T {
1657 self.get_experiment_by_rule_match_impl(
1658 experiment_name,
1659 |rule| rule.id().as_str() == rule_id,
1660 result_factory,
1661 )
1662 }
1663
1664 fn get_experiment_by_rule_match_impl<T, P>(
1665 &self,
1666 experiment_name: &str,
1667 rule_predicate: P,
1668 result_factory: impl FnOnce(Option<SpecView<'_>>, Option<RuleRef<'_>>, EvaluationDetails) -> T,
1669 ) -> T
1670 where
1671 P: Fn(RuleRef<'_>) -> bool,
1672 {
1673 let data = self.spec_store.load_data();
1674
1675 let experiment_name = InternedString::from_str_ref(experiment_name);
1676 let experiment = data.snapshot.dynamic_configs.get(&experiment_name);
1677
1678 let Some(exp) = experiment else {
1679 return result_factory(
1680 None,
1681 None,
1682 EvaluationDetails::unrecognized(&data.source, data.lcut(), data.time_received_at),
1683 );
1684 };
1685
1686 let spec = exp.view();
1687 for index in 0..spec.rules_len() {
1688 let rule = spec.rule(index);
1689 if rule_predicate(rule) {
1690 return result_factory(
1691 Some(spec),
1692 Some(rule),
1693 EvaluationDetails::recognized_without_eval_result(
1694 &data.source,
1695 data.lcut(),
1696 data.time_received_at,
1697 ),
1698 );
1699 }
1700 }
1701
1702 result_factory(
1703 None,
1704 None,
1705 EvaluationDetails::unrecognized(&data.source, data.lcut(), data.time_received_at),
1706 )
1707 }
1708}
1709
1710#[cfg(feature = "ffi-support")]
1711fn experiment_group_to_raw_json(
1712 experiment_name: &str,
1713 spec: Option<SpecView<'_>>,
1714 rule: Option<RuleRef<'_>>,
1715 details: EvaluationDetails,
1716) -> String {
1717 use crate::statsig_types_raw::{ExperimentRaw, SuffixedRuleId};
1718
1719 let (Some(spec), Some(rule)) = (spec, rule) else {
1720 return ExperimentRaw::empty(experiment_name, &details).unperformant_to_json_string();
1721 };
1722
1723 let value = rule.return_value().to_owned();
1724 let rule_id = rule.id().to_interned();
1725 let id_type = InternedString::from_str_ref(rule.id_type().value());
1726 let group_name = rule.group_name().map(|name| name.to_interned());
1727 ExperimentRaw {
1728 name: experiment_name,
1729 value: Some(&value),
1730 rule_id: SuffixedRuleId {
1731 rule_id: &rule_id,
1732 rule_id_suffix: None,
1733 },
1734 id_type: Some(&id_type),
1735 group_name: group_name.as_ref(),
1736 is_experiment_active: Some(spec.is_active().unwrap_or(false)),
1737 details: &details,
1738 secondary_exposures: None,
1739 }
1740 .unperformant_to_json_string()
1741}
1742
1743impl Statsig {
1746 pub fn get_layer(&self, user: &StatsigUser, layer_name: &str) -> Layer {
1747 self.get_layer_with_options(user, layer_name, LayerEvaluationOptions::default())
1748 }
1749
1750 pub fn get_layer_with_options(
1751 &self,
1752 user: &StatsigUser,
1753 layer_name: &str,
1754 options: LayerEvaluationOptions,
1755 ) -> Layer {
1756 let user_internal = self.internalize_user(user);
1757 self.get_layer_impl(user_internal, layer_name, options)
1758 }
1759
1760 pub fn manually_log_layer_parameter_exposure(
1761 &self,
1762 user: &StatsigUser,
1763 layer_name: &str,
1764 parameter_name: String,
1765 ) {
1766 let interned_layer_name = InternedString::from_str_ref(layer_name);
1767 let interned_parameter_name = InternedString::from_string(parameter_name);
1768 let user_internal = self.internalize_user(user);
1769 self.manually_log_layer_parameter_exposure_for_internal_user(
1770 &user_internal,
1771 &interned_layer_name,
1772 interned_parameter_name,
1773 );
1774 }
1775
1776 #[doc(hidden)]
1777 pub fn manually_log_layer_parameter_exposure_for_internal_user(
1778 &self,
1779 user_internal: &StatsigUserInternal<'_, '_>,
1780 layer_name: &InternedString,
1781 parameter_name: InternedString,
1782 ) {
1783 self.manually_log_layer_parameter_exposure_for_internal_user_with_metadata(
1784 user_internal,
1785 layer_name,
1786 parameter_name,
1787 None,
1788 );
1789 }
1790
1791 #[doc(hidden)]
1792 pub fn manually_log_layer_parameter_exposure_for_internal_user_with_metadata(
1793 &self,
1794 user_internal: &StatsigUserInternal<'_, '_>,
1795 layer_name: &InternedString,
1796 parameter_name: InternedString,
1797 exposure_metadata: Option<HashMap<String, Value>>,
1798 ) {
1799 let (details, evaluation) =
1800 self.evaluate_spec_raw(user_internal, layer_name.as_str(), &SpecType::Layer, None);
1801
1802 self.event_logger.enqueue(
1803 EnqueueExposureOp::layer_param_exposure(
1804 user_internal,
1805 layer_name,
1806 parameter_name,
1807 ExposureTrigger::Manual,
1808 details,
1809 evaluation,
1810 )
1811 .with_extra_metadata(exposure_metadata),
1812 );
1813 }
1814
1815 pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> {
1816 self.spec_store
1817 .get_fields_used_for_entity(layer_name, SpecType::Layer)
1818 }
1819}
1820
1821#[cfg(feature = "ffi-support")]
1824impl Statsig {
1825 pub fn use_raw_feature_gate_with_delayed_exposure<T>(
1826 &self,
1827 user: &StatsigUser,
1828 gate_name: &str,
1829 callback: impl FnOnce(&FeatureGateRaw<'_>) -> T,
1830 ) -> (T, Option<String>) {
1831 self.use_raw_feature_gate_with_delayed_exposure_with_options(
1832 user, gate_name, true, callback,
1833 )
1834 }
1835
1836 pub fn use_raw_feature_gate_with_delayed_exposure_with_options<T>(
1837 &self,
1838 user: &StatsigUser,
1839 gate_name: &str,
1840 include_local_override: bool,
1841 callback: impl FnOnce(&FeatureGateRaw<'_>) -> T,
1842 ) -> (T, Option<String>) {
1843 let user_internal = self.internalize_user(user);
1844 self.use_raw_feature_gate_impl(
1845 &user_internal,
1846 gate_name,
1847 FeatureGateEvaluationOptions::default(),
1848 include_local_override,
1849 RawExposureLogging::Delayed,
1850 callback,
1851 )
1852 }
1853
1854 #[doc(hidden)]
1855 pub fn use_raw_feature_gate_with_delayed_exposure_with_options_for_internal_user<T>(
1856 &self,
1857 user_internal: &StatsigUserInternal<'_, '_>,
1858 gate_name: &str,
1859 include_local_override: bool,
1860 callback: impl FnOnce(&FeatureGateRaw<'_>) -> T,
1861 ) -> (T, Option<String>) {
1862 self.use_raw_feature_gate_impl(
1863 user_internal,
1864 gate_name,
1865 FeatureGateEvaluationOptions::default(),
1866 include_local_override,
1867 RawExposureLogging::Delayed,
1868 callback,
1869 )
1870 }
1871
1872 pub fn use_raw_feature_gate_with_options<T>(
1873 &self,
1874 user: &StatsigUser,
1875 gate_name: &str,
1876 options: FeatureGateEvaluationOptions,
1877 callback: impl FnOnce(&FeatureGateRaw<'_>) -> T,
1878 ) -> T {
1879 let user_internal = self.internalize_user(user);
1880 self.use_raw_feature_gate_with_options_for_internal_user(
1881 &user_internal,
1882 gate_name,
1883 options,
1884 callback,
1885 )
1886 }
1887
1888 #[doc(hidden)]
1889 pub fn use_raw_feature_gate_with_options_for_internal_user<T>(
1890 &self,
1891 user_internal: &StatsigUserInternal<'_, '_>,
1892 gate_name: &str,
1893 options: FeatureGateEvaluationOptions,
1894 callback: impl FnOnce(&FeatureGateRaw<'_>) -> T,
1895 ) -> T {
1896 self.use_raw_feature_gate_impl(
1897 user_internal,
1898 gate_name,
1899 options,
1900 true,
1901 RawExposureLogging::Immediate,
1902 callback,
1903 )
1904 .0
1905 }
1906
1907 fn use_raw_feature_gate_impl<T>(
1908 &self,
1909 user_internal: &StatsigUserInternal<'_, '_>,
1910 gate_name: &str,
1911 options: FeatureGateEvaluationOptions,
1912 include_local_override: bool,
1913 exposure_logging: RawExposureLogging,
1914 callback: impl FnOnce(&FeatureGateRaw<'_>) -> T,
1915 ) -> (T, Option<String>) {
1916 use crate::evaluation::evaluator_result::result_to_gate_raw;
1917
1918 let interned_gate_name = InternedString::from_str_ref(gate_name);
1919 let (details, evaluation) = self.evaluate_spec_raw_with_include_local_override(
1920 user_internal,
1921 gate_name,
1922 &SpecType::Gate,
1923 None,
1924 include_local_override,
1925 );
1926
1927 let raw = result_to_gate_raw(gate_name, &details, evaluation.as_ref());
1928 let result = callback(&raw);
1929
1930 self.emit_gate_evaluated_parts(gate_name, details.reason.as_str(), evaluation.as_ref());
1931
1932 let token = match exposure_logging {
1933 RawExposureLogging::Immediate => {
1934 if options.disable_exposure_logging {
1935 log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1936 self.event_logger.increment_non_exposure_checks(gate_name);
1937 None
1938 } else {
1939 self.event_logger.enqueue(EnqueueExposureOp::gate_exposure(
1940 user_internal,
1941 &interned_gate_name,
1942 ExposureTrigger::Auto,
1943 details,
1944 evaluation,
1945 ));
1946 None
1947 }
1948 }
1949 RawExposureLogging::Delayed => self
1950 .event_logger
1951 .prepare_event(EnqueueExposureOp::gate_exposure(
1952 user_internal,
1953 &interned_gate_name,
1954 ExposureTrigger::Auto,
1955 details,
1956 evaluation,
1957 ))
1958 .map(|prepared| self.delayed_exposure_store.insert_prepared(prepared)),
1959 };
1960
1961 (result, token)
1962 }
1963
1964 pub fn use_raw_dynamic_config_with_delayed_exposure<T>(
1965 &self,
1966 user: &StatsigUser,
1967 dynamic_config_name: &str,
1968 callback: impl FnOnce(&DynamicConfigRaw<'_>) -> T,
1969 ) -> (T, Option<String>) {
1970 self.use_raw_dynamic_config_with_delayed_exposure_with_options(
1971 user,
1972 dynamic_config_name,
1973 true,
1974 callback,
1975 )
1976 }
1977
1978 pub fn use_raw_dynamic_config_with_delayed_exposure_with_options<T>(
1979 &self,
1980 user: &StatsigUser,
1981 dynamic_config_name: &str,
1982 include_local_override: bool,
1983 callback: impl FnOnce(&DynamicConfigRaw<'_>) -> T,
1984 ) -> (T, Option<String>) {
1985 let user_internal = self.internalize_user(user);
1986 self.use_raw_dynamic_config_impl(
1987 &user_internal,
1988 dynamic_config_name,
1989 DynamicConfigEvaluationOptions::default(),
1990 include_local_override,
1991 RawExposureLogging::Delayed,
1992 callback,
1993 )
1994 }
1995
1996 #[doc(hidden)]
1997 pub fn use_raw_dynamic_config_with_delayed_exposure_with_options_for_internal_user<T>(
1998 &self,
1999 user_internal: &StatsigUserInternal<'_, '_>,
2000 dynamic_config_name: &str,
2001 include_local_override: bool,
2002 callback: impl FnOnce(&DynamicConfigRaw<'_>) -> T,
2003 ) -> (T, Option<String>) {
2004 self.use_raw_dynamic_config_impl(
2005 user_internal,
2006 dynamic_config_name,
2007 DynamicConfigEvaluationOptions::default(),
2008 include_local_override,
2009 RawExposureLogging::Delayed,
2010 callback,
2011 )
2012 }
2013
2014 pub fn use_raw_dynamic_config_with_options<T>(
2015 &self,
2016 user: &StatsigUser,
2017 dynamic_config_name: &str,
2018 options: DynamicConfigEvaluationOptions,
2019 callback: impl FnOnce(&DynamicConfigRaw<'_>) -> T,
2020 ) -> T {
2021 let user_internal = self.internalize_user(user);
2022 self.use_raw_dynamic_config_with_options_for_internal_user(
2023 &user_internal,
2024 dynamic_config_name,
2025 options,
2026 callback,
2027 )
2028 }
2029
2030 #[doc(hidden)]
2031 pub fn use_raw_dynamic_config_with_options_for_internal_user<T>(
2032 &self,
2033 user_internal: &StatsigUserInternal<'_, '_>,
2034 dynamic_config_name: &str,
2035 options: DynamicConfigEvaluationOptions,
2036 callback: impl FnOnce(&DynamicConfigRaw<'_>) -> T,
2037 ) -> T {
2038 self.use_raw_dynamic_config_impl(
2039 user_internal,
2040 dynamic_config_name,
2041 options,
2042 true,
2043 RawExposureLogging::Immediate,
2044 callback,
2045 )
2046 .0
2047 }
2048
2049 fn use_raw_dynamic_config_impl<T>(
2050 &self,
2051 user_internal: &StatsigUserInternal<'_, '_>,
2052 dynamic_config_name: &str,
2053 options: DynamicConfigEvaluationOptions,
2054 include_local_override: bool,
2055 exposure_logging: RawExposureLogging,
2056 callback: impl FnOnce(&DynamicConfigRaw<'_>) -> T,
2057 ) -> (T, Option<String>) {
2058 use crate::evaluation::evaluator_result::result_to_dynamic_config_raw;
2059
2060 let interned_dynamic_config_name = InternedString::from_str_ref(dynamic_config_name);
2061 let disable_exposure_logging: bool = options.disable_exposure_logging;
2062
2063 let (details, evaluation) = self.evaluate_spec_raw_with_include_local_override(
2064 user_internal,
2065 dynamic_config_name,
2066 &SpecType::DynamicConfig,
2067 Some(disable_exposure_logging),
2068 include_local_override,
2069 );
2070
2071 let raw = result_to_dynamic_config_raw(dynamic_config_name, &details, evaluation.as_ref());
2072 let result = callback(&raw);
2073
2074 self.emit_dynamic_config_evaluated_parts(
2075 dynamic_config_name,
2076 details.reason.as_str(),
2077 evaluation.as_ref(),
2078 );
2079
2080 let token = match exposure_logging {
2081 RawExposureLogging::Immediate => {
2082 if disable_exposure_logging {
2083 log_d!(
2084 TAG,
2085 "Exposure logging is disabled for Dynamic Config {}",
2086 dynamic_config_name
2087 );
2088 self.event_logger
2089 .increment_non_exposure_checks(dynamic_config_name);
2090 None
2091 } else {
2092 self.event_logger
2093 .enqueue(EnqueueExposureOp::dynamic_config_exposure(
2094 user_internal,
2095 &interned_dynamic_config_name,
2096 ExposureTrigger::Auto,
2097 details,
2098 evaluation,
2099 ));
2100 None
2101 }
2102 }
2103 RawExposureLogging::Delayed => self
2104 .event_logger
2105 .prepare_event(EnqueueExposureOp::dynamic_config_exposure(
2106 user_internal,
2107 &interned_dynamic_config_name,
2108 ExposureTrigger::Auto,
2109 details,
2110 evaluation,
2111 ))
2112 .map(|prepared| self.delayed_exposure_store.insert_prepared(prepared)),
2113 };
2114
2115 (result, token)
2116 }
2117
2118 pub fn get_raw_experiment_by_group_name(
2119 &self,
2120 experiment_name: &str,
2121 group_name: &str,
2122 ) -> String {
2123 self.get_experiment_by_group_name_impl(
2124 experiment_name,
2125 group_name,
2126 |spec, rule, details| {
2127 experiment_group_to_raw_json(experiment_name, spec, rule, details)
2128 },
2129 )
2130 }
2131
2132 pub fn get_raw_experiment_by_group_id_advanced(
2133 &self,
2134 experiment_name: &str,
2135 group_id: &str,
2136 ) -> String {
2137 self.get_experiment_by_group_id_advanced_impl(
2138 experiment_name,
2139 group_id,
2140 |spec, rule, details| {
2141 experiment_group_to_raw_json(experiment_name, spec, rule, details)
2142 },
2143 )
2144 }
2145
2146 pub fn use_raw_experiment_with_delayed_exposure<T>(
2147 &self,
2148 user: &StatsigUser,
2149 experiment_name: &str,
2150 options: ExperimentEvaluationOptions,
2151 callback: impl FnOnce(&ExperimentRaw<'_>) -> T,
2152 ) -> (T, Option<String>) {
2153 self.use_raw_experiment_with_delayed_exposure_with_options(
2154 user,
2155 experiment_name,
2156 options,
2157 true,
2158 callback,
2159 )
2160 }
2161
2162 pub fn use_raw_experiment_with_delayed_exposure_with_options<T>(
2163 &self,
2164 user: &StatsigUser,
2165 experiment_name: &str,
2166 options: ExperimentEvaluationOptions,
2167 include_local_override: bool,
2168 callback: impl FnOnce(&ExperimentRaw<'_>) -> T,
2169 ) -> (T, Option<String>) {
2170 let user_internal = self.internalize_user(user);
2171 self.use_raw_experiment_impl(
2172 &user_internal,
2173 experiment_name,
2174 options,
2175 RawExperimentExposureOptions {
2176 include_local_override,
2177 exposure_logging: RawExposureLogging::Delayed,
2178 exposure_metadata: None,
2179 },
2180 callback,
2181 )
2182 }
2183
2184 #[doc(hidden)]
2185 pub fn use_raw_experiment_with_delayed_exposure_with_options_for_internal_user<T>(
2186 &self,
2187 user_internal: &StatsigUserInternal<'_, '_>,
2188 experiment_name: &str,
2189 options: ExperimentEvaluationOptions,
2190 include_local_override: bool,
2191 callback: impl FnOnce(&ExperimentRaw<'_>) -> T,
2192 ) -> (T, Option<String>) {
2193 self.use_raw_experiment_impl(
2194 user_internal,
2195 experiment_name,
2196 options,
2197 RawExperimentExposureOptions {
2198 include_local_override,
2199 exposure_logging: RawExposureLogging::Delayed,
2200 exposure_metadata: None,
2201 },
2202 callback,
2203 )
2204 }
2205
2206 pub fn use_raw_experiment_with_options<T>(
2207 &self,
2208 user: &StatsigUser,
2209 experiment_name: &str,
2210 options: ExperimentEvaluationOptions,
2211 callback: impl FnOnce(&ExperimentRaw<'_>) -> T,
2212 ) -> T {
2213 let user_internal = self.internalize_user(user);
2214 self.use_raw_experiment_with_options_for_internal_user(
2215 &user_internal,
2216 experiment_name,
2217 options,
2218 callback,
2219 )
2220 }
2221
2222 #[doc(hidden)]
2223 pub fn use_raw_experiment_with_options_for_internal_user<T>(
2224 &self,
2225 user_internal: &StatsigUserInternal<'_, '_>,
2226 experiment_name: &str,
2227 options: ExperimentEvaluationOptions,
2228 callback: impl FnOnce(&ExperimentRaw<'_>) -> T,
2229 ) -> T {
2230 self.use_raw_experiment_with_options_for_internal_user_with_metadata(
2231 user_internal,
2232 experiment_name,
2233 options,
2234 None,
2235 callback,
2236 )
2237 }
2238
2239 #[doc(hidden)]
2240 pub fn use_raw_experiment_with_options_for_internal_user_with_metadata<T>(
2241 &self,
2242 user_internal: &StatsigUserInternal<'_, '_>,
2243 experiment_name: &str,
2244 options: ExperimentEvaluationOptions,
2245 exposure_metadata: Option<HashMap<String, Value>>,
2246 callback: impl FnOnce(&ExperimentRaw<'_>) -> T,
2247 ) -> T {
2248 self.use_raw_experiment_impl(
2249 user_internal,
2250 experiment_name,
2251 options,
2252 RawExperimentExposureOptions {
2253 include_local_override: true,
2254 exposure_logging: RawExposureLogging::Immediate,
2255 exposure_metadata,
2256 },
2257 callback,
2258 )
2259 .0
2260 }
2261
2262 fn use_raw_experiment_impl<T>(
2263 &self,
2264 user_internal: &StatsigUserInternal<'_, '_>,
2265 experiment_name: &str,
2266 options: ExperimentEvaluationOptions,
2267 exposure_options: RawExperimentExposureOptions,
2268 callback: impl FnOnce(&ExperimentRaw<'_>) -> T,
2269 ) -> (T, Option<String>) {
2270 use crate::evaluation::evaluator_result::result_to_experiment_raw;
2271
2272 let RawExperimentExposureOptions {
2273 include_local_override,
2274 exposure_logging,
2275 exposure_metadata,
2276 } = exposure_options;
2277 let interned_experiment_name = InternedString::from_str_ref(experiment_name);
2278 let disable_exposure_logging: bool = options.disable_exposure_logging;
2279
2280 let (details, evaluation) = self.evaluate_spec_raw_with_include_local_override(
2281 user_internal,
2282 experiment_name,
2283 &SpecType::Experiment,
2284 Some(disable_exposure_logging),
2285 include_local_override,
2286 );
2287
2288 let (evaluation, details) =
2289 PersistentValuesManager::try_apply_sticky_value_to_raw_experiment(
2290 &self.persistent_values_manager,
2291 user_internal,
2292 &options,
2293 details,
2294 evaluation,
2295 );
2296
2297 let raw = result_to_experiment_raw(experiment_name, &details, evaluation.as_ref());
2298 let result = callback(&raw);
2299
2300 self.emit_experiment_evaluated_parts(
2301 experiment_name,
2302 details.reason.as_str(),
2303 evaluation.as_ref(),
2304 );
2305
2306 let token = match exposure_logging {
2307 RawExposureLogging::Immediate => {
2308 if disable_exposure_logging {
2309 log_d!(
2310 TAG,
2311 "Exposure logging is disabled for Experiment {}",
2312 experiment_name
2313 );
2314 self.event_logger
2315 .increment_non_exposure_checks(experiment_name);
2316 None
2317 } else {
2318 self.event_logger.enqueue(
2319 EnqueueExposureOp::experiment_exposure(
2320 user_internal,
2321 &interned_experiment_name,
2322 ExposureTrigger::Auto,
2323 details,
2324 evaluation,
2325 )
2326 .with_extra_metadata(exposure_metadata),
2327 );
2328 None
2329 }
2330 }
2331 RawExposureLogging::Delayed => self
2332 .event_logger
2333 .prepare_event(
2334 EnqueueExposureOp::experiment_exposure(
2335 user_internal,
2336 &interned_experiment_name,
2337 ExposureTrigger::Auto,
2338 details,
2339 evaluation,
2340 )
2341 .with_extra_metadata(exposure_metadata),
2342 )
2343 .map(|prepared| self.delayed_exposure_store.insert_prepared(prepared)),
2344 };
2345
2346 (result, token)
2347 }
2348
2349 pub fn use_raw_layer_with_delayed_exposure<T>(
2350 &self,
2351 user: &StatsigUser,
2352 layer_name: &str,
2353 options: LayerEvaluationOptions,
2354 callback: impl FnOnce(&LayerRaw<'_>) -> T,
2355 ) -> (T, Option<String>) {
2356 self.use_raw_layer_with_delayed_exposure_with_options(
2357 user, layer_name, options, true, callback,
2358 )
2359 }
2360
2361 pub fn use_raw_layer_with_delayed_exposure_with_options<T>(
2362 &self,
2363 user: &StatsigUser,
2364 layer_name: &str,
2365 options: LayerEvaluationOptions,
2366 include_local_override: bool,
2367 callback: impl FnOnce(&LayerRaw<'_>) -> T,
2368 ) -> (T, Option<String>) {
2369 let user_internal = self.internalize_user(user);
2370 self.use_raw_layer_impl(
2371 &user_internal,
2372 layer_name,
2373 options,
2374 include_local_override,
2375 RawExposureLogging::Delayed,
2376 callback,
2377 )
2378 }
2379
2380 #[doc(hidden)]
2381 pub fn use_raw_layer_with_delayed_exposure_with_options_for_internal_user<T>(
2382 &self,
2383 user_internal: &StatsigUserInternal<'_, '_>,
2384 layer_name: &str,
2385 options: LayerEvaluationOptions,
2386 include_local_override: bool,
2387 callback: impl FnOnce(&LayerRaw<'_>) -> T,
2388 ) -> (T, Option<String>) {
2389 self.use_raw_layer_impl(
2390 user_internal,
2391 layer_name,
2392 options,
2393 include_local_override,
2394 RawExposureLogging::Delayed,
2395 callback,
2396 )
2397 }
2398
2399 pub fn use_raw_layer_with_options<T>(
2400 &self,
2401 user: &StatsigUser,
2402 layer_name: &str,
2403 options: LayerEvaluationOptions,
2404 callback: impl FnOnce(&LayerRaw<'_>) -> T,
2405 ) -> T {
2406 let user_internal = self.internalize_user(user);
2407 self.use_raw_layer_with_options_for_internal_user(
2408 &user_internal,
2409 layer_name,
2410 options,
2411 callback,
2412 )
2413 }
2414
2415 #[doc(hidden)]
2416 pub fn use_raw_layer_with_options_for_internal_user<T>(
2417 &self,
2418 user_internal: &StatsigUserInternal<'_, '_>,
2419 layer_name: &str,
2420 options: LayerEvaluationOptions,
2421 callback: impl FnOnce(&LayerRaw<'_>) -> T,
2422 ) -> T {
2423 self.use_raw_layer_impl(
2424 user_internal,
2425 layer_name,
2426 options,
2427 true,
2428 RawExposureLogging::Immediate,
2429 callback,
2430 )
2431 .0
2432 }
2433
2434 fn use_raw_layer_impl<T>(
2435 &self,
2436 user_internal: &StatsigUserInternal<'_, '_>,
2437 layer_name: &str,
2438 options: LayerEvaluationOptions,
2439 include_local_override: bool,
2440 exposure_logging: RawExposureLogging,
2441 callback: impl FnOnce(&LayerRaw<'_>) -> T,
2442 ) -> (T, Option<String>) {
2443 use crate::evaluation::evaluator_result::result_to_layer_raw;
2444
2445 let disable_exposure_logging: bool = options.disable_exposure_logging;
2446
2447 let (details, evaluation) = self.evaluate_spec_raw_with_include_local_override(
2448 user_internal,
2449 layer_name,
2450 &SpecType::Layer,
2451 Some(disable_exposure_logging),
2452 include_local_override,
2453 );
2454
2455 let (evaluation, details) = PersistentValuesManager::try_apply_sticky_value_to_raw_layer(
2456 &self.persistent_values_manager,
2457 user_internal,
2458 &options,
2459 &self.spec_store,
2460 &self.ops_stats,
2461 details,
2462 evaluation,
2463 );
2464
2465 let raw = result_to_layer_raw(
2466 user_internal,
2467 layer_name,
2468 options,
2469 &details,
2470 evaluation.as_ref(),
2471 );
2472
2473 let token = match exposure_logging {
2474 RawExposureLogging::Immediate => {
2475 if disable_exposure_logging {
2476 log_d!(TAG, "Exposure logging is disabled for Layer {}", layer_name);
2477 self.event_logger.increment_non_exposure_checks(layer_name);
2478 }
2479 None
2480 }
2481 RawExposureLogging::Delayed => {
2482 if raw.disable_exposure || self.options.disable_all_logging == Some(true) {
2483 None
2484 } else {
2485 Some(
2486 self.delayed_exposure_store
2487 .insert_layer(crate::statsig_types_raw::PartialLayerRaw::from(&raw)),
2488 )
2489 }
2490 }
2491 };
2492
2493 let result = callback(&raw);
2494
2495 self.emit_layer_evaluated_parts(layer_name, details.reason.as_str(), evaluation.as_ref());
2496
2497 (result, token)
2498 }
2499
2500 pub fn bulk_evaluate_with_delayed_exposures(
2501 &self,
2502 user: &StatsigUser,
2503 options: BulkEvaluationOptions,
2504 ) -> BulkEvaluationResponse {
2505 let mut response = BulkEvaluationResponse::default();
2506 let resolved = self.resolve_bulk_evaluation_options(options);
2507 let include_local_override = resolved.include_local_override;
2508
2509 macro_rules! insert_bulk_evaluations {
2510 ($destination:expr, $names:expr, |$name:ident| $evaluate:expr) => {
2511 for $name in $names {
2512 let (mut evaluation, exposure_token) = $evaluate;
2513 evaluation.common.exposure_token = exposure_token;
2514 $destination.insert($name, evaluation);
2515 }
2516 };
2517 }
2518
2519 insert_bulk_evaluations!(response.feature_gates, resolved.feature_gates, |name| self
2520 .use_raw_feature_gate_with_delayed_exposure_with_options(
2521 user,
2522 &name,
2523 include_local_override,
2524 |raw| BulkFeatureGateEvaluation {
2525 value: raw.value,
2526 common: bulk_common_fields_from_raw_parts(
2527 interned_rule_id_from_raw(&raw.rule_id),
2528 raw.id_type.cloned(),
2529 raw.details.clone(),
2530 ),
2531 },
2532 ));
2533
2534 insert_bulk_evaluations!(response.dynamic_configs, resolved.dynamic_configs, |name| {
2535 self.use_raw_dynamic_config_with_delayed_exposure_with_options(
2536 user,
2537 &name,
2538 include_local_override,
2539 |raw| BulkDynamicConfigEvaluation {
2540 value: raw
2541 .value
2542 .and_then(|value| value.get_json())
2543 .unwrap_or_default(),
2544 common: bulk_common_fields_from_raw_parts(
2545 interned_rule_id_from_raw(&raw.rule_id),
2546 raw.id_type.cloned(),
2547 raw.details.clone(),
2548 ),
2549 },
2550 )
2551 });
2552
2553 insert_bulk_evaluations!(response.experiments, resolved.experiments, |name| self
2554 .use_raw_experiment_with_delayed_exposure_with_options(
2555 user,
2556 &name,
2557 ExperimentEvaluationOptions::default(),
2558 include_local_override,
2559 |raw| BulkExperimentEvaluation {
2560 value: raw
2561 .value
2562 .and_then(|value| value.get_json())
2563 .unwrap_or_default(),
2564 group_name: raw.group_name.map(|name| name.unperformant_to_string()),
2565 common: bulk_common_fields_from_raw_parts(
2566 interned_rule_id_from_raw(&raw.rule_id),
2567 raw.id_type.cloned(),
2568 raw.details.clone(),
2569 ),
2570 },
2571 ));
2572
2573 insert_bulk_evaluations!(response.layer_configs, resolved.layers, |name| self
2574 .use_raw_layer_with_delayed_exposure_with_options(
2575 user,
2576 &name,
2577 LayerEvaluationOptions::default(),
2578 include_local_override,
2579 |raw| BulkLayerEvaluation {
2580 value: raw
2581 .value
2582 .and_then(|value| value.get_json())
2583 .unwrap_or_default(),
2584 group_name: raw.group_name.map(|name| name.unperformant_to_string()),
2585 allocated_experiment_name: raw
2586 .allocated_experiment_name
2587 .map(|name| name.unperformant_to_string()),
2588 common: bulk_common_fields_from_raw_parts(
2589 interned_rule_id_from_raw(&raw.rule_id),
2590 raw.id_type.cloned(),
2591 raw.details.clone(),
2592 ),
2593 },
2594 ));
2595
2596 response
2597 }
2598
2599 pub fn resolve_bulk_evaluation_options(
2600 &self,
2601 options: BulkEvaluationOptions,
2602 ) -> ResolvedBulkEvaluationOptions {
2603 let feature_gates = options.feature_gate_filter.unwrap_or_else(|| {
2604 self.spec_store
2605 .unperformant_keys_entity_filter("feature_gates", "feature_gate")
2606 });
2607 let dynamic_configs = options.dynamic_config_filter.unwrap_or_else(|| {
2608 let mut dynamic_config_names = self
2609 .spec_store
2610 .unperformant_keys_entity_filter("dynamic_configs", "dynamic_config");
2611 dynamic_config_names.extend(
2612 self.spec_store
2613 .unperformant_keys_entity_filter("dynamic_configs", "autotune"),
2614 );
2615 dynamic_config_names
2616 });
2617 let experiments = options.experiment_filter.unwrap_or_else(|| {
2618 self.spec_store
2619 .unperformant_keys_entity_filter("dynamic_configs", "experiment")
2620 });
2621 let layers = options.layer_filter.unwrap_or_else(|| {
2622 self.spec_store
2623 .unperformant_keys_entity_filter("layer_configs", "*")
2624 });
2625
2626 ResolvedBulkEvaluationOptions {
2627 feature_gates,
2628 dynamic_configs,
2629 experiments,
2630 layers,
2631 include_local_override: options.include_local_override,
2632 }
2633 }
2634
2635 pub fn log_delayed_exposure(&self, token: &str) -> bool {
2636 self.delayed_exposure_store
2637 .log_delayed_exposure(token, &self.event_logger)
2638 }
2639
2640 pub fn log_delayed_layer_parameter_exposure(&self, token: &str, parameter_name: &str) -> bool {
2641 self.delayed_exposure_store
2642 .log_delayed_layer_parameter_exposure(token, parameter_name, &self.event_logger)
2643 }
2644
2645 pub fn release_delayed_exposure(&self, token: &str) -> bool {
2646 self.delayed_exposure_store.release(token)
2647 }
2648
2649 pub fn release_delayed_exposures(&self, tokens: &[String]) -> usize {
2650 self.delayed_exposure_store.release_many(tokens)
2651 }
2652
2653 pub fn log_layer_param_exposure_from_raw(&self, raw: String, param_name: String) {
2654 use crate::statsig_types_raw::PartialLayerRaw;
2655
2656 let partial_raw = match serde_json::from_str::<PartialLayerRaw>(&raw) {
2657 Ok(partial_raw) => partial_raw,
2658 Err(e) => {
2659 log_e!(TAG, "Failed to parse partial layer raw: {}", e);
2660 return;
2661 }
2662 };
2663
2664 self.log_layer_param_exposure_from_partial_raw(partial_raw, param_name);
2665 }
2666
2667 pub fn log_layer_param_exposure_from_partial_raw(
2668 &self,
2669 partial_raw: crate::statsig_types_raw::PartialLayerRaw,
2670 param_name: String,
2671 ) {
2672 self.log_layer_param_exposure_from_partial_raw_with_metadata(partial_raw, param_name, None);
2673 }
2674
2675 pub fn log_layer_param_exposure_from_partial_raw_with_metadata(
2676 &self,
2677 partial_raw: crate::statsig_types_raw::PartialLayerRaw,
2678 param_name: String,
2679 exposure_metadata: Option<HashMap<String, Value>>,
2680 ) {
2681 if partial_raw.disable_exposure {
2682 self.event_logger
2683 .increment_non_exposure_checks(&partial_raw.name);
2684 return;
2685 }
2686
2687 let interned_parameter_name = InternedString::from_string(param_name);
2688
2689 self.event_logger.enqueue(
2690 EnqueueExposureOp::layer_param_exposure_from_partial_raw(
2691 interned_parameter_name,
2692 ExposureTrigger::Auto,
2693 partial_raw,
2694 )
2695 .with_extra_metadata(exposure_metadata),
2696 );
2697 }
2698}
2699
2700#[cfg(feature = "ffi-support")]
2701fn bulk_common_fields_from_raw_parts(
2702 rule_id: InternedString,
2703 id_type: Option<InternedString>,
2704 details: EvaluationDetails,
2705) -> BulkEvaluationCommonFields {
2706 BulkEvaluationCommonFields {
2707 rule_id,
2708 id_type,
2709 details,
2710 exposure_token: None,
2711 }
2712}
2713
2714#[cfg(feature = "ffi-support")]
2715fn interned_rule_id_from_raw(
2716 rule_id: &crate::statsig_types_raw::SuffixedRuleId<'_>,
2717) -> InternedString {
2718 match rule_id.try_as_unprefixed_str() {
2719 Some(_) => rule_id.rule_id.clone(),
2720 None => InternedString::from_string(rule_id.unperformant_to_string()),
2721 }
2722}
2723
2724impl Statsig {
2727 pub(crate) fn get_from_statsig_env(&self, key: &str) -> Option<DynamicValue> {
2728 if let Some(env) = &self.statsig_environment {
2729 return env.get(key).cloned();
2730 }
2731
2732 if let Some(fallback_env) = self
2733 .fallback_environment
2734 .try_lock_for(Duration::from_secs(5))
2735 {
2736 if let Some(env) = &*fallback_env {
2737 return env.get(key).cloned();
2738 }
2739 }
2740
2741 None
2742 }
2743
2744 pub(crate) fn get_value_from_global_custom_fields(&self, key: &str) -> Option<&DynamicValue> {
2745 if let Some(env) = &self.options.global_custom_fields {
2746 return env.get(key);
2747 }
2748
2749 None
2750 }
2751
2752 pub(crate) fn use_global_custom_fields<T>(
2753 &self,
2754 f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
2755 ) -> T {
2756 f(self.options.global_custom_fields.as_ref())
2757 }
2758
2759 pub(crate) fn use_statsig_env<T>(
2760 &self,
2761 f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
2762 ) -> T {
2763 if let Some(env) = &self.statsig_environment {
2764 return f(Some(env));
2765 }
2766
2767 if let Some(fallback_env) = self
2768 .fallback_environment
2769 .try_lock_for(Duration::from_secs(5))
2770 {
2771 if let Some(env) = &*fallback_env {
2772 return f(Some(env));
2773 }
2774 }
2775
2776 f(None)
2777 }
2778}
2779
2780impl Statsig {
2783 async fn start_background_tasks(
2784 statsig_runtime: Arc<StatsigRuntime>,
2785 id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
2786 specs_adapter: Arc<dyn SpecsAdapter>,
2787 ops_stats: Arc<OpsStatsForInstance>,
2788 bg_tasks_started: Arc<AtomicBool>,
2789 ) -> bool {
2790 if bg_tasks_started.load(Ordering::SeqCst) {
2791 return true;
2792 }
2793
2794 let mut success = true;
2795
2796 if let Some(adapter) = &id_lists_adapter {
2797 if let Err(e) = adapter
2798 .clone()
2799 .schedule_background_sync(&statsig_runtime)
2800 .await
2801 {
2802 success = false;
2803 log_w!(TAG, "Failed to schedule idlist background job {}", e);
2804 }
2805 }
2806
2807 if let Err(e) = specs_adapter
2808 .clone()
2809 .schedule_background_sync(&statsig_runtime)
2810 .await
2811 {
2812 success = false;
2813 log_error_to_statsig_and_console!(
2814 ops_stats,
2815 TAG,
2816 StatsigErr::SpecsAdapterSkipPoll(format!(
2817 "Failed to schedule specs adapter background job: {e}"
2818 ))
2819 );
2820 }
2821
2822 bg_tasks_started.store(true, Ordering::SeqCst);
2823
2824 success
2825 }
2826
2827 async fn apply_timeout_to_init(
2828 &self,
2829 timeout_ms: u64,
2830 ) -> Result<InitializeDetails, StatsigErr> {
2831 let timeout = Duration::from_millis(timeout_ms);
2832
2833 let init_future = self.initialize_impl_with_details();
2834 let timeout_future = sleep(timeout);
2835
2836 let statsig_runtime = self.statsig_runtime.clone();
2837 let id_lists_adapter = self.id_lists_adapter.inner.clone();
2838 let specs_adapter = self.specs_adapter.inner.clone();
2839 let ops_stats = self.ops_stats.clone();
2840 let background_tasks_started = self.background_tasks_started.clone();
2841 let statsig_runtime_for_closure = statsig_runtime.clone();
2843
2844 tokio::select! {
2845 result = init_future => {
2846 result
2847 },
2848 _ = timeout_future => {
2849 statsig_runtime.spawn(
2850 "start_background_tasks",
2851 |_shutdown_notify| async move {
2852 Self::start_background_tasks(
2853 statsig_runtime_for_closure,
2854 id_lists_adapter,
2855 specs_adapter,
2856 ops_stats,
2857 background_tasks_started,
2858 ).await;
2859 }
2860 )?;
2861 Ok(InitializeDetails::from_timeout_failure(timeout_ms))
2862 },
2863 }
2864 }
2865
2866 async fn initialize_impl_with_details(&self) -> Result<InitializeDetails, StatsigErr> {
2867 let start_time = Instant::now();
2868 self.spec_store.set_source(SpecsSource::Loading);
2869 self.specs_adapter.inner.initialize(self.spec_store.clone());
2870 let use_third_party_ua_parser = self.should_user_third_party_parser();
2871
2872 let mut error_message = None;
2873 let mut id_list_ready = None;
2874
2875 let init_country_lookup = if !self.options.disable_country_lookup.unwrap_or_default() {
2876 Some(self.statsig_runtime.spawn(INIT_IP_TAG, |_| async {
2877 CountryLookup::load_country_lookup();
2878 }))
2879 } else {
2880 None
2881 };
2882
2883 let init_ua = if use_third_party_ua_parser {
2884 Some(self.statsig_runtime.spawn(INIT_UA_TAG, |_| async {
2885 UserAgentParser::load_parser();
2886 }))
2887 } else {
2888 None
2889 };
2890
2891 let init_res = match self
2892 .specs_adapter
2893 .inner
2894 .clone()
2895 .start(&self.statsig_runtime)
2896 .await
2897 {
2898 Ok(()) => Ok(()),
2899 Err(e) => {
2900 self.spec_store.set_source(SpecsSource::NoValues);
2901 error_message = Some(format!("Failed to start specs adapter: {e}"));
2902 Err(e)
2903 }
2904 };
2905
2906 if let Some(adapter) = &self.id_lists_adapter.inner {
2907 match adapter
2908 .clone()
2909 .start(&self.statsig_runtime, self.spec_store.clone())
2910 .await
2911 {
2912 Ok(()) => {
2913 id_list_ready = Some(true);
2914 }
2915 Err(e) => {
2916 id_list_ready = Some(false);
2917 error_message.get_or_insert_with(|| format!("Failed to sync ID lists: {e}"));
2918 }
2919 }
2920 }
2921
2922 if let Err(e) = self
2923 .event_logging_adapter
2924 .clone()
2925 .start(&self.statsig_runtime)
2926 .await
2927 {
2928 log_error_to_statsig_and_console!(
2929 self.ops_stats.clone(),
2930 TAG,
2931 StatsigErr::UnstartedAdapter(format!("Failed to start event logging adapter: {e}"))
2932 );
2933 }
2934
2935 let spec_info = self.spec_store.get_current_specs_info();
2936 let duration = start_time.elapsed().as_millis() as u64;
2937
2938 self.set_default_environment_from_server();
2939
2940 if self.options.wait_for_country_lookup_init.unwrap_or(false) {
2941 match init_country_lookup {
2942 Some(Ok(task_id)) => {
2943 let _ = self
2944 .statsig_runtime
2945 .await_join_handle(INIT_IP_TAG, &task_id)
2946 .await;
2947 }
2948 Some(Err(e)) => {
2949 log_error_to_statsig_and_console!(
2950 self.ops_stats.clone(),
2951 TAG,
2952 StatsigErr::UnstartedAdapter(format!(
2953 "Failed to spawn country lookup task: {e}"
2954 ))
2955 );
2956 }
2957 _ => {}
2958 }
2959 }
2960 if self.options.wait_for_user_agent_init.unwrap_or(false) {
2961 match init_ua {
2962 Some(Ok(task_id)) => {
2963 let _ = self
2964 .statsig_runtime
2965 .await_join_handle(INIT_UA_TAG, &task_id)
2966 .await;
2967 }
2968 Some(Err(e)) => {
2969 log_error_to_statsig_and_console!(
2970 self.ops_stats.clone(),
2971 TAG,
2972 StatsigErr::UnstartedAdapter(format!(
2973 "Failed to spawn user agent parser task: {e}"
2974 ))
2975 );
2976 }
2977 _ => {}
2978 }
2979 }
2980
2981 let error = init_res.clone().err();
2982
2983 let success = Self::start_background_tasks(
2984 self.statsig_runtime.clone(),
2985 self.id_lists_adapter.inner.clone(),
2986 self.specs_adapter.inner.clone(),
2987 self.ops_stats.clone(),
2988 self.background_tasks_started.clone(),
2989 )
2990 .await;
2991
2992 Ok(InitializeDetails::new(
2993 success,
2994 duration,
2995 spec_info,
2996 id_list_ready,
2997 error,
2998 ))
2999 }
3000
3001 fn log_init_details(&self, init_details: &Result<InitializeDetails, StatsigErr>) {
3002 match init_details {
3003 Ok(details) => {
3004 self.log_init_finish(
3005 details.init_success,
3006 &None,
3007 &details.duration_ms,
3008 &self.spec_store.get_current_specs_info(),
3009 );
3010 if let Some(failure) = &details.failure_details {
3011 log_error_to_statsig_and_console!(
3012 self.ops_stats,
3013 TAG,
3014 StatsigErr::InitializationError(failure.reason.clone())
3015 );
3016 }
3017 }
3018 Err(err) => {
3019 log_w!(TAG, "Initialization error: {:?}", err);
3021 }
3022 }
3023 }
3024
3025 fn create_standard_eval_context<'a>(
3026 &'a self,
3027 user_internal: &'a StatsigUserInternal,
3028 data: &'a SpecStoreData,
3029 app_id: Option<&'a DynamicValue>,
3030 override_adapter: Option<&'a Arc<dyn OverrideAdapter>>,
3031 disable_exposure_logging: bool,
3032 ) -> EvaluatorContext<'a> {
3033 EvaluatorContext::new(
3034 user_internal,
3035 &data.snapshot,
3036 IdListResolution::MapLookup(data.id_lists.as_ref()),
3037 &self.hashing,
3038 app_id,
3039 override_adapter,
3040 self.should_user_third_party_parser(),
3041 Some(self),
3042 disable_exposure_logging,
3043 )
3044 }
3045
3046 fn create_gcir_eval_context<'a>(
3047 &'a self,
3048 user_internal: &'a StatsigUserInternal,
3049 data: &'a SpecStoreData,
3050 options: &'a ClientInitResponseOptions,
3051 ) -> EvaluatorContext<'a> {
3052 let app_id = select_app_id_for_gcir(options, &data.snapshot, &self.hashing);
3053 let override_adapter = match options.include_local_overrides {
3054 Some(true) => self.override_adapter.as_ref(),
3055 _ => None,
3056 };
3057
3058 EvaluatorContext::new(
3059 user_internal,
3060 &data.snapshot,
3061 IdListResolution::MapLookup(data.id_lists.as_ref()),
3062 &self.hashing,
3063 app_id,
3064 override_adapter,
3065 self.should_user_third_party_parser(),
3066 None,
3067 true,
3068 )
3069 }
3070
3071 fn evaluate_spec_raw(
3072 &self,
3073 user_internal: &StatsigUserInternal,
3074 spec_name: &str,
3075 spec_type: &SpecType,
3076 disable_exposure_logging: Option<bool>,
3077 ) -> (EvaluationDetails, Option<EvaluatorResult>) {
3078 self.evaluate_spec_raw_with_include_local_override(
3079 user_internal,
3080 spec_name,
3081 spec_type,
3082 disable_exposure_logging,
3083 true,
3084 )
3085 }
3086
3087 fn evaluate_spec_raw_with_include_local_override(
3088 &self,
3089 user_internal: &StatsigUserInternal,
3090 spec_name: &str,
3091 spec_type: &SpecType,
3092 disable_exposure_logging: Option<bool>,
3093 include_local_override: bool,
3094 ) -> (EvaluationDetails, Option<EvaluatorResult>) {
3095 let data = self.spec_store.load_data();
3096
3097 let override_adapter = if include_local_override {
3098 self.override_adapter.as_ref()
3099 } else {
3100 None
3101 };
3102
3103 let mut context = self.create_standard_eval_context(
3104 user_internal,
3105 &data,
3106 data.snapshot.app_id.as_ref(),
3107 override_adapter,
3108 disable_exposure_logging.unwrap_or(false),
3109 );
3110
3111 match Self::evaluate_with_details(&mut context, &data, spec_name, spec_type) {
3112 Ok(eval_details) => (eval_details, Some(context.result)),
3113 Err(e) => {
3114 log_error_to_statsig_and_console!(
3115 &self.ops_stats,
3116 TAG,
3117 StatsigErr::EvaluationError(e.to_string())
3118 );
3119 (EvaluationDetails::error(&e.to_string()), None)
3120 }
3121 }
3122 }
3123
3124 #[allow(clippy::too_many_arguments)]
3125 fn evaluate_spec<T>(
3126 &self,
3127 user_internal: &StatsigUserInternal,
3128 spec_name: &str,
3129 make_empty_result: impl FnOnce(EvaluationDetails) -> T,
3130 make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
3131 spec_type: &SpecType,
3132 disable_exposure_logging: Option<bool>,
3133 ) -> T {
3134 let data = self.spec_store.load_data();
3135
3136 let mut context = self.create_standard_eval_context(
3137 user_internal,
3138 &data,
3139 data.snapshot.app_id.as_ref(),
3140 self.override_adapter.as_ref(),
3141 disable_exposure_logging.unwrap_or(false),
3142 );
3143
3144 match Self::evaluate_with_details(&mut context, &data, spec_name, spec_type) {
3145 Ok(eval_details) => make_result(context.result, eval_details),
3146 Err(e) => {
3147 log_error_to_statsig_and_console!(
3148 &self.ops_stats,
3149 TAG,
3150 StatsigErr::EvaluationError(e.to_string())
3151 );
3152 make_empty_result(EvaluationDetails::error(&e.to_string()))
3153 }
3154 }
3155 }
3156
3157 fn evaluate_with_details(
3158 ctx: &mut EvaluatorContext,
3159 spec_store_data: &SpecStoreData,
3160 spec_name: &str,
3161 spec_type: &SpecType,
3162 ) -> Result<EvaluationDetails, StatsigErr> {
3163 let recognition = Evaluator::evaluate(ctx, spec_name, spec_type)?;
3164
3165 if recognition == Recognition::Unrecognized {
3166 return Ok(EvaluationDetails::unrecognized(
3167 &spec_store_data.source,
3168 spec_store_data.lcut(),
3169 spec_store_data.time_received_at,
3170 ));
3171 }
3172
3173 if let Some(reason) = ctx.result.override_reason {
3174 return Ok(EvaluationDetails::recognized_but_overridden(
3175 spec_store_data.lcut(),
3176 spec_store_data.time_received_at,
3177 reason,
3178 ctx.result.version,
3179 ));
3180 }
3181
3182 Ok(EvaluationDetails::recognized(
3183 &spec_store_data.source,
3184 spec_store_data.lcut(),
3185 spec_store_data.time_received_at,
3186 &ctx.result,
3187 ))
3188 }
3189
3190 fn stringify_gcir_response<T: Serialize>(
3191 &self,
3192 input: Result<T, StatsigErr>,
3193 fallback: impl FnOnce() -> T,
3194 ) -> String {
3195 match input {
3196 Ok(value) => serde_json::to_string(&value).unwrap_or_default(),
3197 Err(e) => {
3198 log_error_to_statsig_and_console!(
3199 &self.ops_stats,
3200 TAG,
3201 StatsigErr::GCIRError(e.to_string())
3202 );
3203 serde_json::to_string(&fallback()).unwrap_or_default()
3204 }
3205 }
3206 }
3207
3208 fn get_gate_evaluation(
3209 &self,
3210 user_internal: &StatsigUserInternal,
3211 gate_name: &str,
3212 disable_exposure_logging: Option<bool>,
3213 ) -> (EvaluationDetails, Option<GateEvaluation>) {
3214 self.evaluate_spec(
3215 user_internal,
3216 gate_name,
3217 |eval_details| (eval_details, None),
3218 |mut result, eval_details| {
3219 let evaluation = result_to_gate_eval(gate_name, &mut result);
3220 (eval_details, Some(evaluation))
3221 },
3222 &SpecType::Gate,
3223 disable_exposure_logging,
3224 )
3225 }
3226
3227 fn get_dynamic_config_impl(
3228 &self,
3229 user_internal: &StatsigUserInternal,
3230 config_name: &str,
3231 disable_exposure_logging: Option<bool>,
3232 ) -> DynamicConfig {
3233 self.evaluate_spec(
3234 user_internal,
3235 config_name,
3236 |eval_details| make_dynamic_config(config_name, None, eval_details),
3237 |mut result, eval_details| {
3238 let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
3239 make_dynamic_config(config_name, Some(evaluation), eval_details)
3240 },
3241 &SpecType::DynamicConfig,
3242 disable_exposure_logging,
3243 )
3244 }
3245
3246 fn get_experiment_impl(
3247 &self,
3248 user_internal: &StatsigUserInternal,
3249 experiment_name: &str,
3250 disable_exposure_logging: Option<bool>,
3251 ) -> Experiment {
3252 self.evaluate_spec(
3253 user_internal,
3254 experiment_name,
3255 |eval_details| make_experiment(experiment_name, None, eval_details),
3256 |mut result, eval_details| {
3257 let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
3258 make_experiment(experiment_name, Some(evaluation), eval_details)
3259 },
3260 &SpecType::Experiment,
3261 disable_exposure_logging,
3262 )
3263 }
3264
3265 fn get_layer_impl(
3266 &self,
3267 user_internal: StatsigUserInternal,
3268 layer_name: &str,
3269 evaluation_options: LayerEvaluationOptions,
3270 ) -> Layer {
3271 let disable_exposure_logging = evaluation_options.disable_exposure_logging;
3272
3273 if disable_exposure_logging {
3274 self.event_logger.increment_non_exposure_checks(layer_name);
3275 }
3276
3277 let mut layer = self.evaluate_spec(
3278 &user_internal,
3279 layer_name,
3280 |eval_details| {
3281 make_layer(
3282 user_internal.to_loggable(),
3283 layer_name,
3284 None,
3285 eval_details,
3286 None,
3287 disable_exposure_logging,
3288 )
3289 },
3290 |mut result, eval_details| {
3291 let evaluation = result_to_layer_eval(layer_name, &mut result);
3292 let event_logger_ptr = Arc::downgrade(&self.event_logger);
3293
3294 make_layer(
3295 user_internal.to_loggable(),
3296 layer_name,
3297 Some(evaluation),
3298 eval_details,
3299 Some(event_logger_ptr),
3300 disable_exposure_logging,
3301 )
3302 },
3303 &SpecType::Layer,
3304 Some(evaluation_options.disable_exposure_logging),
3305 );
3306
3307 layer = PersistentValuesManager::try_apply_sticky_value_to_layer(
3308 &self.persistent_values_manager,
3309 &user_internal,
3310 &evaluation_options,
3311 &self.spec_store,
3312 &self.ops_stats,
3313 layer,
3314 );
3315
3316 self.emit_layer_evaluated(&layer);
3317
3318 layer
3319 }
3320
3321 fn internalize_user<'s, 'u>(&'s self, user: &'u StatsigUser) -> StatsigUserInternal<'s, 'u> {
3322 StatsigUserInternal::new(user, Some(self))
3323 }
3324
3325 fn set_default_environment_from_server(&self) {
3326 let data = self.spec_store.load_data();
3327
3328 if let Some(default_env) = data.snapshot.default_environment.as_ref() {
3329 let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
3330
3331 match self
3332 .fallback_environment
3333 .try_lock_for(Duration::from_secs(5))
3334 {
3335 Some(mut fallback_env) => {
3336 *fallback_env = Some(env_map);
3337 }
3338 None => {
3339 log_e!(TAG, "Failed to lock fallback_environment");
3340 }
3341 }
3342 }
3343 }
3344
3345 fn log_init_finish(
3346 &self,
3347 success: bool,
3348 error_message: &Option<String>,
3349 duration_ms: &u64,
3350 specs_info: &SpecsInfo,
3351 ) {
3352 let is_store_populated = specs_info.source != SpecsSource::NoValues;
3353 let source_str = specs_info.source.to_string();
3354 let event = ObservabilityEvent::new_event(
3355 MetricType::Dist,
3356 "initialization".to_string(),
3357 *duration_ms as f64,
3358 Some(HashMap::from([
3359 ("init_success".to_owned(), success.to_string()),
3360 ("source".to_owned(), source_str.clone()),
3361 ("store_populated".to_owned(), is_store_populated.to_string()),
3362 (
3363 "sdk_key".to_owned(),
3364 get_loggable_sdk_key(self.sdk_key.as_str()),
3365 ),
3366 (
3367 "init_source_api".to_owned(),
3368 specs_info.source_api.clone().unwrap_or_default(),
3369 ),
3370 ])),
3371 );
3372
3373 self.ops_stats.log(event);
3374 self.ops_stats.add_marker(
3375 {
3376 let marker = Marker::new(KeyType::Overall, ActionType::End, None)
3377 .with_is_success(success)
3378 .with_config_spec_ready(specs_info.source != SpecsSource::NoValues)
3379 .with_source(source_str);
3380
3381 if let Some(msg) = &error_message {
3382 marker.with_message(msg.to_string())
3383 } else {
3384 marker
3385 }
3386 },
3387 Some(ContextType::Initialize),
3388 );
3389 self.ops_stats
3390 .enqueue_diagnostics_event(None, Some(ContextType::Initialize));
3391 }
3392
3393 fn should_user_third_party_parser(&self) -> bool {
3394 self.options.use_third_party_ua_parser.unwrap_or(false)
3395 }
3396}
3397
3398fn initialize_event_logging_adapter(
3399 sdk_key: &str,
3400 options: &StatsigOptions,
3401) -> Arc<dyn EventLoggingAdapter> {
3402 options
3403 .event_logging_adapter
3404 .clone()
3405 .unwrap_or_else(|| Arc::new(StatsigHttpEventLoggingAdapter::new(sdk_key, Some(options))))
3406}
3407
3408fn initialize_specs_adapter(
3409 sdk_key: &str,
3410 data_store_key: &str,
3411 options: &StatsigOptions,
3412) -> SpecsAdapterHousing {
3413 if let Some(adapter) = options.specs_adapter.clone() {
3414 log_d!(TAG, "Using provided SpecsAdapter: {}", sdk_key);
3415 return SpecsAdapterHousing {
3416 inner: adapter,
3417 as_default_adapter: None,
3418 };
3419 }
3420
3421 if let Some(adapter_config) = options.spec_adapters_config.clone() {
3422 let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
3423 sdk_key,
3424 data_store_key,
3425 adapter_config,
3426 options,
3427 ));
3428
3429 return SpecsAdapterHousing {
3430 inner: adapter,
3431 as_default_adapter: None,
3432 };
3433 }
3434
3435 if let Some(data_store) = options.data_store.clone() {
3436 let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
3437 sdk_key,
3438 data_store_key,
3439 data_store,
3440 options,
3441 ));
3442
3443 return SpecsAdapterHousing {
3444 inner: adapter,
3445 as_default_adapter: None,
3446 };
3447 }
3448
3449 let adapter = Arc::new(StatsigHttpSpecsAdapter::new(sdk_key, Some(options), None));
3450
3451 SpecsAdapterHousing {
3452 inner: adapter.clone(),
3453 as_default_adapter: Some(adapter),
3454 }
3455}
3456
3457fn initialize_id_lists_adapter(sdk_key: &str, options: &StatsigOptions) -> IdListsAdapterHousing {
3458 if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
3459 return IdListsAdapterHousing {
3460 inner: Some(id_lists_adapter),
3461 as_default_adapter: None,
3462 };
3463 }
3464
3465 if options.enable_id_lists.unwrap_or(false) {
3466 let adapter = Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options));
3467
3468 return IdListsAdapterHousing {
3469 inner: Some(adapter.clone()),
3470 as_default_adapter: Some(adapter),
3471 };
3472 }
3473
3474 IdListsAdapterHousing {
3475 inner: None,
3476 as_default_adapter: None,
3477 }
3478}
3479
3480struct IdListsAdapterHousing {
3481 inner: Option<Arc<dyn IdListsAdapter>>,
3482 as_default_adapter: Option<Arc<StatsigHttpIdListsAdapter>>,
3483}
3484
3485struct SpecsAdapterHousing {
3486 inner: Arc<dyn SpecsAdapter>,
3487 as_default_adapter: Option<Arc<StatsigHttpSpecsAdapter>>,
3488}
3489
3490fn setup_ops_stats(
3491 sdk_instance_id: &str,
3492 statsig_runtime: Arc<StatsigRuntime>,
3493 error_observer: &Arc<dyn OpsStatsEventObserver>,
3494 diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
3495 console_capture_observer: &Arc<dyn OpsStatsEventObserver>,
3496 external_observer: &Option<Weak<dyn ObservabilityClient>>,
3497) -> Arc<OpsStatsForInstance> {
3498 let ops_stat = OPS_STATS.get_for_instance(sdk_instance_id);
3499 ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
3500 ops_stat.subscribe(
3501 statsig_runtime.clone(),
3502 Arc::downgrade(diagnostics_observer),
3503 );
3504 ops_stat.subscribe(
3505 statsig_runtime.clone(),
3506 Arc::downgrade(console_capture_observer),
3507 );
3508 if let Some(ob_client) = external_observer {
3509 if let Some(client) = ob_client.upgrade() {
3510 client.init();
3511 let as_observer = client.to_ops_stats_event_observer();
3512 ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
3513 }
3514 }
3515
3516 ops_stat
3517}
3518
3519#[cfg(test)]
3520mod tests {
3521 use rusty_fork::rusty_fork_test;
3522
3523 use super::*;
3524 use crate::{
3525 interned_values::{
3526 interned_store::{preload_mmap_v2_for_test, write_mmap_v2_for_test},
3527 InternedStore,
3528 },
3529 networking::ResponseData,
3530 SpecsUpdate,
3531 };
3532
3533 const EVAL_PROJ_JSON: &[u8] = include_bytes!("../tests/data/eval_proj_dcs.json");
3534
3535 rusty_fork_test! {
3536 #[test]
3537 fn experiment_group_lookups_do_not_materialize_mmap_specs() {
3538 assert!(!InternedStore::has_preloaded_mmap_v2());
3539 let directory = tempfile::tempdir().unwrap();
3540 let path = directory.path().join("interned-store-v2-group-lookup.mmap");
3541 write_mmap_v2_for_test(EVAL_PROJ_JSON, &path).unwrap();
3542 preload_mmap_v2_for_test(&path).unwrap();
3543 assert!(InternedStore::has_preloaded_mmap_v2());
3544
3545 let statsig = Statsig::new("secret-key", None);
3546 statsig
3547 .spec_store
3548 .set_values(SpecsUpdate {
3549 data: ResponseData::from_bytes(EVAL_PROJ_JSON.to_vec()),
3550 source: SpecsSource::Network,
3551 received_at: 2_000,
3552 source_api: Some("mmap-group-test".to_string()),
3553 has_updates: None,
3554 })
3555 .unwrap();
3556
3557 let experiment_name = "test_experiment_no_targeting";
3558 let group_name = "Control";
3559 let group_id = "54QJztEPRLXK7ZCvXeY9q4";
3560 assert_eq!(InternedStore::get_mmap_spec_materialization_len(), 0);
3561
3562 let by_name = statsig.get_experiment_by_group_name(experiment_name, group_name);
3563 let by_id = statsig.get_experiment_by_group_id_advanced(experiment_name, group_id);
3564 assert_eq!(by_name.rule_id, group_id);
3565 assert_eq!(by_id.group_name.as_deref(), Some(group_name));
3566
3567 #[cfg(feature = "ffi-support")]
3568 {
3569 for raw in [
3570 statsig.get_raw_experiment_by_group_name(experiment_name, group_name),
3571 statsig.get_raw_experiment_by_group_id_advanced(experiment_name, group_id),
3572 ] {
3573 let raw: Value = serde_json::from_str(&raw).unwrap();
3574 assert_eq!(raw["ruleID"], group_id);
3575 assert_eq!(raw["groupName"], group_name);
3576 assert_eq!(raw["value"]["value"], "control");
3577 }
3578 }
3579
3580 assert_eq!(InternedStore::get_mmap_spec_materialization_len(), 0);
3581 }
3582 }
3583}