1use crate::evaluation::cmab_evaluator::{get_cmab_ranked_list, CMABRankedGroup};
2use crate::evaluation::country_lookup::CountryLookup;
3use crate::evaluation::dynamic_value::DynamicValue;
4use crate::evaluation::evaluation_details::EvaluationDetails;
5use crate::evaluation::evaluation_types::GateEvaluation;
6use crate::evaluation::evaluator::{Evaluator, SpecType};
7use crate::evaluation::evaluator_context::EvaluatorContext;
8use crate::evaluation::evaluator_result::{
9 result_to_dynamic_config_eval, result_to_experiment_eval, result_to_gate_eval,
10 result_to_layer_eval, EvaluatorResult,
11};
12use crate::evaluation::user_agent_parsing::{ParsedUserAgentValue, UserAgentParser};
13use crate::event_logging::event_logger::{EventLogger, ExposureTrigger};
14use crate::event_logging::event_queue::queued_config_expo::EnqueueConfigExpoOp;
15use crate::event_logging::event_queue::queued_experiment_expo::EnqueueExperimentExpoOp;
16use crate::event_logging::event_queue::queued_gate_expo::EnqueueGateExpoOp;
17use crate::event_logging::event_queue::queued_layer_param_expo::EnqueueLayerParamExpoOp;
18use crate::event_logging::event_queue::queued_passthrough::EnqueuePassthroughOp;
19use crate::event_logging::statsig_event_internal::{StatsigEventInternal, StatsigLogLineLevel};
20use crate::event_logging_adapter::EventLoggingAdapter;
21use crate::event_logging_adapter::StatsigHttpEventLoggingAdapter;
22use crate::gcir::gcir_formatter::GCIRFormatter;
23use crate::hashing::HashUtil;
24use crate::initialize_response::InitializeResponse;
25use crate::networking::NetworkError;
26use crate::observability::diagnostics_observer::DiagnosticsObserver;
27use crate::observability::observability_client_adapter::{MetricType, ObservabilityEvent};
28use crate::observability::ops_stats::{OpsStatsForInstance, OPS_STATS};
29use crate::observability::sdk_errors_observer::{ErrorBoundaryEvent, SDKErrorsObserver};
30use crate::output_logger::{initialize_output_logger, shutdown_output_logger};
31use crate::persistent_storage::persistent_values_manager::PersistentValuesManager;
32use crate::sdk_diagnostics::diagnostics::{ContextType, Diagnostics};
33use crate::sdk_diagnostics::marker::{ActionType, KeyType, Marker};
34use crate::sdk_event_emitter::SdkEventEmitter;
35use crate::spec_store::SpecStore;
36use crate::specs_adapter::{StatsigCustomizedSpecsAdapter, StatsigHttpSpecsAdapter};
37use crate::statsig_err::StatsigErr;
38use crate::statsig_metadata::StatsigMetadata;
39use crate::statsig_options::StatsigOptions;
40use crate::statsig_runtime::StatsigRuntime;
41use crate::statsig_type_factories::{
42 make_dynamic_config, make_experiment, make_feature_gate, make_layer,
43};
44use crate::statsig_types::{DynamicConfig, Experiment, FeatureGate, Layer, ParameterStore};
45use crate::user::StatsigUserInternal;
46use crate::{
47 dyn_value, log_d, log_e, log_w, read_lock_or_else, ClientInitResponseOptions,
48 GCIRResponseFormat, IdListsAdapter, ObservabilityClient, OpsStatsEventObserver,
49 OverrideAdapter, SpecsAdapter, SpecsInfo, SpecsSource, SpecsUpdateListener,
50 StatsigHttpIdListsAdapter, StatsigLocalOverrideAdapter, StatsigUser,
51};
52use crate::{
53 log_error_to_statsig_and_console,
54 statsig_core_api_options::{
55 DynamicConfigEvaluationOptions, ExperimentEvaluationOptions, FeatureGateEvaluationOptions,
56 LayerEvaluationOptions, ParameterStoreEvaluationOptions,
57 },
58};
59use chrono::Utc;
60use parking_lot::Mutex;
61use serde::de::DeserializeOwned;
62use serde::Serialize;
63use serde_json::json;
64use serde_json::Value;
65use std::borrow::Cow;
66use std::collections::HashMap;
67use std::sync::atomic::{AtomicBool, Ordering};
68use std::sync::{Arc, Weak};
69use std::time::{Duration, Instant};
70use tokio::time::sleep;
71use tokio::try_join;
72
73const TAG: &str = stringify!(Statsig);
74const ERROR_SDK_KEY: &str = "__STATSIG_ERROR_SDK_KEY__";
75const INIT_IP_TAG: &str = "INIT_COUNTRY_LOOKUP";
76const INIT_UA_TAG: &str = "INIT_UA";
77
78lazy_static::lazy_static! {
79 static ref SHARED_INSTANCE: Mutex<Option<Arc<Statsig>>> = Mutex::new(None);
80}
81
82pub struct Statsig {
83 pub statsig_runtime: Arc<StatsigRuntime>,
84 pub options: Arc<StatsigOptions>,
85 pub event_emitter: SdkEventEmitter,
86
87 sdk_key: String,
88 event_logger: Arc<EventLogger>,
89 specs_adapter: SpecsAdapterHousing,
90 event_logging_adapter: Arc<dyn EventLoggingAdapter>,
91 id_lists_adapter: IdListsAdapterHousing,
92 override_adapter: Option<Arc<dyn OverrideAdapter>>,
93 spec_store: Arc<SpecStore>,
94 hashing: Arc<HashUtil>,
95 gcir_formatter: Arc<GCIRFormatter>,
96 statsig_environment: Option<HashMap<String, DynamicValue>>,
97 fallback_environment: Mutex<Option<HashMap<String, DynamicValue>>>,
98 ops_stats: Arc<OpsStatsForInstance>,
99 error_observer: Arc<dyn OpsStatsEventObserver>,
100 diagnostics_observer: Arc<dyn OpsStatsEventObserver>,
101 background_tasks_started: Arc<AtomicBool>,
102 persistent_values_manager: Option<Arc<PersistentValuesManager>>,
103 initialize_details: Mutex<InitializeDetails>,
104}
105
106pub struct StatsigContext {
107 pub sdk_key: String,
108 pub options: Arc<StatsigOptions>,
109 pub local_override_adapter: Option<Arc<dyn OverrideAdapter>>,
110 pub error_observer: Arc<dyn OpsStatsEventObserver>,
111 pub diagnostics_observer: Arc<dyn OpsStatsEventObserver>,
112 pub spec_store: Arc<SpecStore>,
113}
114
115#[derive(Debug, Clone, Serialize)]
116pub struct FailureDetails {
117 pub reason: String,
118 pub error: Option<StatsigErr>,
119}
120
121#[derive(Debug, Clone, Serialize)]
122pub struct InitializeDetails {
123 pub duration: f64,
124 pub init_success: bool,
125 pub is_config_spec_ready: bool,
126 pub is_id_list_ready: Option<bool>,
127 pub source: SpecsSource,
128 pub failure_details: Option<FailureDetails>,
129 pub spec_source_api: Option<String>,
130}
131
132impl Default for InitializeDetails {
133 fn default() -> Self {
134 InitializeDetails {
135 duration: 0.0,
136 init_success: false,
137 is_config_spec_ready: false,
138 is_id_list_ready: None,
139 source: SpecsSource::Uninitialized,
140 failure_details: None,
141 spec_source_api: None,
142 }
143 }
144}
145
146impl InitializeDetails {
147 pub fn from_error(reason: &str, error: Option<StatsigErr>) -> Self {
148 InitializeDetails {
149 duration: 0.0,
150 init_success: false,
151 is_config_spec_ready: false,
152 is_id_list_ready: None,
153 source: SpecsSource::Uninitialized,
154 failure_details: Some(FailureDetails {
155 reason: reason.to_string(),
156 error,
157 }),
158 spec_source_api: None,
159 }
160 }
161}
162
163impl Drop for Statsig {
164 fn drop(&mut self) {
165 self.event_logger.force_shutdown();
166
167 if let Some(adapter) = &self.id_lists_adapter.as_default_adapter {
168 adapter.force_shutdown();
169 }
170
171 if let Some(adapter) = &self.specs_adapter.as_default_adapter {
172 adapter.force_shutdown();
173 }
174
175 shutdown_output_logger();
176
177 log_d!(TAG, "Statsig instance dropped");
178 }
179}
180
181impl Statsig {
182 pub fn new(sdk_key: &str, options: Option<Arc<StatsigOptions>>) -> Self {
183 let statsig_runtime = StatsigRuntime::get_runtime();
184 let options = options.map(|o| o.validate_and_fix()).unwrap_or_default();
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
193 let specs_adapter = initialize_specs_adapter(sdk_key, &options, &hashing);
194 let id_lists_adapter = initialize_id_lists_adapter(sdk_key, &options);
195 let event_logging_adapter = initialize_event_logging_adapter(sdk_key, &options);
196 let override_adapter = match options.override_adapter.as_ref() {
197 Some(adapter) => Some(Arc::clone(adapter)),
198 None => Some(Arc::new(StatsigLocalOverrideAdapter::new()) as Arc<dyn OverrideAdapter>),
199 };
200
201 let event_logger =
202 EventLogger::new(sdk_key, &options, &event_logging_adapter, &statsig_runtime);
203
204 let diagnostics = Arc::new(Diagnostics::new(event_logger.clone(), sdk_key));
205 let diagnostics_observer: Arc<dyn OpsStatsEventObserver> =
206 Arc::new(DiagnosticsObserver::new(diagnostics));
207 let error_observer: Arc<dyn OpsStatsEventObserver> =
208 Arc::new(SDKErrorsObserver::new(sdk_key, &options));
209
210 let ops_stats = setup_ops_stats(
211 sdk_key,
212 statsig_runtime.clone(),
213 &error_observer,
214 &diagnostics_observer,
215 &options.observability_client,
216 );
217
218 let spec_store = Arc::new(SpecStore::new(
219 sdk_key,
220 hashing.sha256(sdk_key),
221 statsig_runtime.clone(),
222 options.data_store.clone(),
223 ));
224
225 let environment = options
226 .environment
227 .as_ref()
228 .map(|env| HashMap::from([("tier".into(), dyn_value!(env.as_str()))]));
229
230 let persistent_values_manager = options.persistent_storage.clone().map(|storage| {
231 Arc::new(PersistentValuesManager {
232 persistent_storage: storage,
233 })
234 });
235
236 StatsigMetadata::update_service_name(options.service_name.clone());
237
238 let use_experimental_ua_parser = options.__experimental_ua_parsing_enabled.unwrap_or(false);
239
240 Statsig {
241 sdk_key: sdk_key.to_string(),
242 options,
243 gcir_formatter: Arc::new(GCIRFormatter::new(
244 &spec_store,
245 &override_adapter,
246 &ops_stats,
247 use_experimental_ua_parser,
248 )),
249 hashing,
250 statsig_environment: environment,
251 fallback_environment: Mutex::new(None),
252 override_adapter,
253 spec_store,
254 specs_adapter,
255 event_logging_adapter,
256 event_logger,
257 id_lists_adapter,
258 statsig_runtime,
259 ops_stats,
260 error_observer,
261 diagnostics_observer,
262 background_tasks_started: Arc::new(AtomicBool::new(false)),
263 persistent_values_manager,
264 initialize_details: Mutex::new(InitializeDetails::default()),
265 event_emitter: SdkEventEmitter::default(),
266 }
267 }
268
269 pub async fn initialize(&self) -> Result<(), StatsigErr> {
284 let details = self.initialize_with_details().await?;
285
286 if let Some(failure_details) = details.failure_details {
287 Err(failure_details
288 .error
289 .unwrap_or(StatsigErr::InitializationError(failure_details.reason)))
290 } else {
291 Ok(())
292 }
293 }
294
295 pub async fn initialize_with_details(&self) -> Result<InitializeDetails, StatsigErr> {
304 self.ops_stats.add_marker(
305 Marker::new(KeyType::Overall, ActionType::Start, None),
306 Some(ContextType::Initialize),
307 );
308
309 let init_details = if let Some(timeout_ms) = self.options.init_timeout_ms {
310 self.apply_timeout_to_init(timeout_ms).await
311 } else {
312 self.initialize_impl_with_details().await
313 };
314 self.log_init_details(&init_details);
315 if let Ok(details) = &init_details {
316 match self.initialize_details.try_lock_for(Duration::from_secs(5)) {
317 Some(mut curr_init_details) => {
318 *curr_init_details = details.clone();
319 }
320 None => {
321 log_e!(TAG, "Failed to lock initialize_details");
322 }
323 }
324 }
325 init_details
326 }
327
328 pub fn get_initialize_details(&self) -> InitializeDetails {
329 match self.initialize_details.try_lock_for(Duration::from_secs(5)) {
330 Some(details) => details.clone(),
331 None => InitializeDetails::from_error(
332 "Failed to lock initialize_details",
333 Some(StatsigErr::LockFailure(
334 "Failed to lock initialize_details".to_string(),
335 )),
336 ),
337 }
338 }
339
340 pub fn is_initialized(&self) -> bool {
341 match self.initialize_details.try_lock_for(Duration::from_secs(5)) {
342 Some(details) => details.init_success,
343 None => false,
344 }
345 }
346
347 pub async fn shutdown(&self) -> Result<(), StatsigErr> {
348 self.shutdown_with_timeout(Duration::from_secs(3)).await
349 }
350
351 pub async fn shutdown_with_timeout(&self, timeout: Duration) -> Result<(), StatsigErr> {
352 log_d!(
353 TAG,
354 "Shutting down Statsig with timeout {}ms",
355 timeout.as_millis()
356 );
357
358 let start = Instant::now();
359 let shutdown_result = tokio::select! {
360 () = tokio::time::sleep(timeout) => {
361 log_w!(TAG, "Statsig shutdown timed out. {}", start.elapsed().as_millis());
362 Err(StatsigErr::ShutdownFailure(
363 "Shutdown timed out".to_string()
364 ))
365 }
366 sub_result = async {
367 let id_list_shutdown: Pin<Box<_>> = if let Some(adapter) = &self.id_lists_adapter.inner {
368 adapter.shutdown(timeout)
369 } else {
370 Box::pin(async { Ok(()) })
371 };
372
373 shutdown_output_logger();
374
375 try_join!(
376 id_list_shutdown,
377 self.event_logger.shutdown(),
378 self.specs_adapter.inner.shutdown(timeout, &self.statsig_runtime),
379 )
380 } => {
381 match sub_result {
382 Ok(_) => {
383 log_d!(TAG, "All Statsig tasks shutdown successfully");
384 Ok(())
385 }
386 Err(e) => {
387 log_w!(TAG, "Error during shutdown: {:?}", e);
388 Err(e)
389 }
390 }
391 }
392 };
393
394 self.statsig_runtime.shutdown();
395 shutdown_result
396 }
397
398 async fn start_background_tasks(
399 statsig_runtime: Arc<StatsigRuntime>,
400 id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
401 specs_adapter: Arc<dyn SpecsAdapter>,
402 ops_stats: Arc<OpsStatsForInstance>,
403 bg_tasks_started: Arc<AtomicBool>,
404 ) -> bool {
405 if bg_tasks_started.load(Ordering::SeqCst) {
406 return true;
407 }
408
409 let mut success = true;
410
411 if let Some(adapter) = &id_lists_adapter {
412 if let Err(e) = adapter
413 .clone()
414 .schedule_background_sync(&statsig_runtime)
415 .await
416 {
417 success = false;
418 log_w!(TAG, "Failed to schedule idlist background job {}", e);
419 }
420 }
421
422 if let Err(e) = specs_adapter
423 .clone()
424 .schedule_background_sync(&statsig_runtime)
425 .await
426 {
427 success = false;
428 log_error_to_statsig_and_console!(
429 ops_stats,
430 TAG,
431 StatsigErr::SpecsAdapterSkipPoll(format!(
432 "Failed to schedule specs adapter background job: {e}"
433 ))
434 );
435 }
436
437 bg_tasks_started.store(true, Ordering::SeqCst);
438
439 success
440 }
441
442 async fn apply_timeout_to_init(
443 &self,
444 timeout_ms: u64,
445 ) -> Result<InitializeDetails, StatsigErr> {
446 let timeout = Duration::from_millis(timeout_ms);
447
448 let init_future = self.initialize_impl_with_details();
449 let timeout_future = sleep(timeout);
450
451 let statsig_runtime = self.statsig_runtime.clone();
452 let id_lists_adapter = self.id_lists_adapter.inner.clone();
453 let specs_adapter = self.specs_adapter.inner.clone();
454 let ops_stats = self.ops_stats.clone();
455 let background_tasks_started = self.background_tasks_started.clone();
456 let statsig_runtime_for_closure = statsig_runtime.clone();
458
459 tokio::select! {
460 result = init_future => {
461 result
462 },
463 _ = timeout_future => {
464 statsig_runtime.spawn(
465 "start_background_tasks",
466 |_shutdown_notify| async move {
467 Self::start_background_tasks(
468 statsig_runtime_for_closure,
469 id_lists_adapter,
470 specs_adapter,
471 ops_stats,
472 background_tasks_started,
473 ).await;
474 }
475 )?;
476 Ok(self.timeout_failure(timeout_ms))
477 },
478 }
479 }
480
481 async fn initialize_impl_with_details(&self) -> Result<InitializeDetails, StatsigErr> {
482 let start_time = Instant::now();
483 self.spec_store.set_source(SpecsSource::Loading);
484 self.specs_adapter.inner.initialize(self.spec_store.clone());
485 let use_experimental_ua_parser = self.should_use_experimental_ua_parser();
486
487 let mut error_message = None;
488 let mut id_list_ready = None;
489
490 let init_country_lookup = if !self.options.disable_country_lookup.unwrap_or_default() {
491 Some(self.statsig_runtime.spawn(INIT_IP_TAG, |_| async {
492 CountryLookup::load_country_lookup();
493 }))
494 } else {
495 None
496 };
497
498 let init_ua = if !self.options.disable_user_agent_parsing.unwrap_or_default()
499 && !use_experimental_ua_parser
500 {
501 Some(self.statsig_runtime.spawn(INIT_UA_TAG, |_| async {
502 UserAgentParser::load_parser();
503 }))
504 } else {
505 None
506 };
507
508 let init_res = match self
509 .specs_adapter
510 .inner
511 .clone()
512 .start(&self.statsig_runtime)
513 .await
514 {
515 Ok(()) => Ok(()),
516 Err(e) => {
517 self.spec_store.set_source(SpecsSource::NoValues);
518 error_message = Some(format!("Failed to start specs adapter: {e}"));
519 Err(e)
520 }
521 };
522
523 if let Some(adapter) = &self.id_lists_adapter.inner {
524 match adapter
525 .clone()
526 .start(&self.statsig_runtime, self.spec_store.clone())
527 .await
528 {
529 Ok(()) => {
530 id_list_ready = Some(true);
531 }
532 Err(e) => {
533 id_list_ready = Some(false);
534 error_message.get_or_insert_with(|| format!("Failed to sync ID lists: {e}"));
535 }
536 }
537 if let Err(e) = adapter
538 .clone()
539 .schedule_background_sync(&self.statsig_runtime)
540 .await
541 {
542 log_w!(TAG, "Failed to schedule id_list background job {}", e);
543 }
544 }
545
546 if let Err(e) = self
547 .event_logging_adapter
548 .clone()
549 .start(&self.statsig_runtime)
550 .await
551 {
552 log_error_to_statsig_and_console!(
553 self.ops_stats.clone(),
554 TAG,
555 StatsigErr::UnstartedAdapter(format!("Failed to start event logging adapter: {e}"))
556 );
557 }
558
559 let spec_info = self.spec_store.get_current_specs_info();
560 let duration = start_time.elapsed().as_millis() as f64;
561
562 self.set_default_environment_from_server();
563
564 if self.options.wait_for_country_lookup_init.unwrap_or(false) {
565 match init_country_lookup {
566 Some(Ok(task_id)) => {
567 let _ = self
568 .statsig_runtime
569 .await_join_handle(INIT_IP_TAG, &task_id)
570 .await;
571 }
572 Some(Err(e)) => {
573 log_error_to_statsig_and_console!(
574 self.ops_stats.clone(),
575 TAG,
576 StatsigErr::UnstartedAdapter(format!(
577 "Failed to spawn country lookup task: {e}"
578 ))
579 );
580 }
581 _ => {}
582 }
583 }
584 if self.options.wait_for_user_agent_init.unwrap_or(false) {
585 match init_ua {
586 Some(Ok(task_id)) => {
587 let _ = self
588 .statsig_runtime
589 .await_join_handle(INIT_UA_TAG, &task_id)
590 .await;
591 }
592 Some(Err(e)) => {
593 log_error_to_statsig_and_console!(
594 self.ops_stats.clone(),
595 TAG,
596 StatsigErr::UnstartedAdapter(format!(
597 "Failed to spawn user agent parser task: {e}"
598 ))
599 );
600 }
601 _ => {}
602 }
603 }
604
605 let error = init_res.clone().err();
606
607 let success = Self::start_background_tasks(
608 self.statsig_runtime.clone(),
609 self.id_lists_adapter.inner.clone(),
610 self.specs_adapter.inner.clone(),
611 self.ops_stats.clone(),
612 self.background_tasks_started.clone(),
613 )
614 .await;
615
616 Ok(self.construct_initialize_details(success, duration, spec_info, id_list_ready, error))
617 }
618
619 fn construct_initialize_details(
620 &self,
621 init_success: bool,
622 duration: f64,
623 specs_info: SpecsInfo,
624 is_id_list_ready: Option<bool>,
625 error: Option<StatsigErr>,
626 ) -> InitializeDetails {
627 let is_config_spec_ready = matches!(specs_info.lcut, Some(v) if v != 0);
628
629 let failure_details =
630 if let Some(StatsigErr::NetworkError(NetworkError::DisableNetworkOn(_))) = error {
631 None
632 } else {
633 error.as_ref().map(|e| FailureDetails {
634 reason: e.to_string(),
635 error: Some(e.clone()),
636 })
637 };
638
639 InitializeDetails {
640 init_success,
641 is_config_spec_ready,
642 is_id_list_ready,
643 source: specs_info.source.clone(),
644 failure_details,
645 duration,
646 spec_source_api: specs_info.source_api.clone(),
647 }
648 }
649
650 fn timeout_failure(&self, timeout_ms: u64) -> InitializeDetails {
651 InitializeDetails {
652 init_success: false,
653 is_config_spec_ready: false,
654 is_id_list_ready: None,
655 source: SpecsSource::Uninitialized,
656 failure_details: Some(FailureDetails {
657 reason: "Initialization timed out".to_string(),
658 error: None,
659 }),
660 duration: timeout_ms as f64,
661 spec_source_api: None,
662 }
663 }
664
665 fn log_init_details(&self, init_details: &Result<InitializeDetails, StatsigErr>) {
666 match init_details {
667 Ok(details) => {
668 self.log_init_finish(
669 details.init_success,
670 &None,
671 &details.duration,
672 &self.spec_store.get_current_specs_info(),
673 );
674 if let Some(failure) = &details.failure_details {
675 log_error_to_statsig_and_console!(
676 self.ops_stats,
677 TAG,
678 StatsigErr::InitializationError(failure.reason.clone())
679 );
680 }
681 }
682 Err(err) => {
683 log_w!(TAG, "Initialization error: {:?}", err);
685 }
686 }
687 }
688
689 pub fn get_context(&self) -> StatsigContext {
690 StatsigContext {
691 sdk_key: self.sdk_key.clone(),
692 options: self.options.clone(),
693 local_override_adapter: self.override_adapter.clone(),
694 error_observer: self.error_observer.clone(),
695 diagnostics_observer: self.diagnostics_observer.clone(),
696 spec_store: self.spec_store.clone(),
697 }
698 }
699
700 pub fn log_event(
701 &self,
702 user: &StatsigUser,
703 event_name: &str,
704 value: Option<String>,
705 metadata: Option<HashMap<String, String>>,
706 ) {
707 let user_internal = self.internalize_user(user);
708
709 self.event_logger.enqueue(EnqueuePassthroughOp {
710 event: StatsigEventInternal::new_custom_event(
711 user_internal.to_loggable(),
712 event_name.to_string(),
713 value.map(|v| json!(v)),
714 metadata,
715 ),
716 });
717 }
718
719 pub fn log_event_with_number(
720 &self,
721 user: &StatsigUser,
722 event_name: &str,
723 value: Option<f64>,
724 metadata: Option<HashMap<String, String>>,
725 ) {
726 let user_internal = self.internalize_user(user);
727 self.event_logger.enqueue(EnqueuePassthroughOp {
728 event: StatsigEventInternal::new_custom_event(
729 user_internal.to_loggable(),
730 event_name.to_string(),
731 value.map(|v| json!(v)),
732 metadata,
733 ),
734 });
735 }
736
737 pub fn forward_log_line_event(
738 &self,
739 user: &StatsigUser,
740 log_level: StatsigLogLineLevel,
741 value: Option<String>,
742 metadata: Option<HashMap<String, String>>,
743 ) {
744 let user_internal = self.internalize_user(user);
745 self.event_logger.enqueue(EnqueuePassthroughOp {
746 event: StatsigEventInternal::new_statsig_log_line_event(
747 user_internal.to_loggable(),
748 log_level,
749 value,
750 metadata,
751 ),
752 });
753 }
754
755 pub fn log_layer_param_exposure_with_layer_json(
756 &self,
757 layer_json: String,
758 parameter_name: String,
759 ) {
760 let layer = match serde_json::from_str::<Layer>(&layer_json) {
761 Ok(layer) => layer,
762 Err(e) => {
763 log_error_to_statsig_and_console!(
764 self.ops_stats.clone(),
765 TAG,
766 StatsigErr::ShutdownFailure(e.to_string())
767 );
768 return;
769 }
770 };
771
772 self.log_layer_param_exposure_with_layer(layer, parameter_name);
773 }
774
775 pub fn log_layer_param_exposure_with_layer(&self, layer: Layer, parameter_name: String) {
776 if layer.__disable_exposure {
777 self.event_logger.increment_non_exposure_checks(&layer.name);
778 return;
779 }
780
781 self.event_logger
782 .enqueue(EnqueueLayerParamExpoOp::LayerOwned(
783 Utc::now().timestamp_millis() as u64,
784 Box::new(layer),
785 parameter_name,
786 ExposureTrigger::Auto,
787 ));
788 }
789
790 pub async fn flush_events(&self) {
791 let _ = self.event_logger.flush_all_pending_events().await;
792 }
793
794 pub fn get_client_init_response(&self, user: &StatsigUser) -> InitializeResponse {
795 self.get_client_init_response_with_options(user, self.gcir_formatter.get_default_options())
796 }
797
798 pub fn get_client_init_response_with_options(
799 &self,
800 user: &StatsigUser,
801 options: &ClientInitResponseOptions,
802 ) -> InitializeResponse {
803 let user_internal = self.internalize_user(user);
804 self.gcir_formatter
805 .get_as_v1_format(user_internal, &self.hashing, options)
806 }
807
808 pub fn get_client_init_response_as_string(&self, user: &StatsigUser) -> String {
809 serde_json::to_string(&self.get_client_init_response(user)).unwrap_or_default()
810 }
811
812 pub fn get_client_init_response_with_options_as_string(
813 &self,
814 user: &StatsigUser,
815 options: &ClientInitResponseOptions,
816 ) -> String {
817 let user_internal = self.internalize_user(user);
818 let response = match options.response_format {
819 Some(GCIRResponseFormat::InitializeWithSecondaryExposureMapping) => {
820 json!(self
821 .gcir_formatter
822 .get_as_v2_format(user_internal, &self.hashing, options))
823 }
824 _ => json!(self
825 .gcir_formatter
826 .get_as_v1_format(user_internal, &self.hashing, options)),
827 };
828
829 json!(response).to_string()
830 }
831
832 pub fn get_string_parameter_from_store(
833 &self,
834 user: &StatsigUser,
835 parameter_store_name: &str,
836 parameter_name: &str,
837 fallback: Option<String>,
838 options: Option<ParameterStoreEvaluationOptions>,
839 ) -> Option<String> {
840 self.get_parameter_from_store(
841 user,
842 parameter_store_name,
843 parameter_name,
844 fallback,
845 options,
846 )
847 }
848
849 pub fn get_boolean_parameter_from_store(
850 &self,
851 user: &StatsigUser,
852 parameter_store_name: &str,
853 parameter_name: &str,
854 fallback: Option<bool>,
855 options: Option<ParameterStoreEvaluationOptions>,
856 ) -> Option<bool> {
857 self.get_parameter_from_store(
858 user,
859 parameter_store_name,
860 parameter_name,
861 fallback,
862 options,
863 )
864 }
865
866 pub fn get_float_parameter_from_store(
867 &self,
868 user: &StatsigUser,
869 parameter_store_name: &str,
870 parameter_name: &str,
871 fallback: Option<f64>,
872 options: Option<ParameterStoreEvaluationOptions>,
873 ) -> Option<f64> {
874 self.get_parameter_from_store(
875 user,
876 parameter_store_name,
877 parameter_name,
878 fallback,
879 options,
880 )
881 }
882
883 pub fn get_integer_parameter_from_store(
884 &self,
885 user: &StatsigUser,
886 parameter_store_name: &str,
887 parameter_name: &str,
888 fallback: Option<i64>,
889 options: Option<ParameterStoreEvaluationOptions>,
890 ) -> Option<i64> {
891 self.get_parameter_from_store(
892 user,
893 parameter_store_name,
894 parameter_name,
895 fallback,
896 options,
897 )
898 }
899
900 pub fn get_array_parameter_from_store(
901 &self,
902 user: &StatsigUser,
903 parameter_store_name: &str,
904 parameter_name: &str,
905 fallback: Option<Vec<Value>>,
906 options: Option<ParameterStoreEvaluationOptions>,
907 ) -> Option<Vec<Value>> {
908 self.get_parameter_from_store(
909 user,
910 parameter_store_name,
911 parameter_name,
912 fallback,
913 options,
914 )
915 }
916
917 pub fn get_object_parameter_from_store(
918 &self,
919 user: &StatsigUser,
920 parameter_store_name: &str,
921 parameter_name: &str,
922 fallback: Option<HashMap<String, Value>>,
923 options: Option<ParameterStoreEvaluationOptions>,
924 ) -> Option<HashMap<String, Value>> {
925 self.get_parameter_from_store(
926 user,
927 parameter_store_name,
928 parameter_name,
929 fallback,
930 options,
931 )
932 }
933
934 pub fn get_parameter_from_store<T: DeserializeOwned>(
935 &self,
936 user: &StatsigUser,
937 parameter_store_name: &str,
938 parameter_name: &str,
939 fallback: Option<T>,
940 options: Option<ParameterStoreEvaluationOptions>,
941 ) -> Option<T> {
942 let store = self
943 .get_parameter_store_with_options(parameter_store_name, options.unwrap_or_default());
944 match fallback {
945 Some(fallback) => Some(store.get(user, parameter_name, fallback)),
946 None => store.get_opt(user, parameter_name),
947 }
948 }
949
950 pub fn get_parameter_store(&self, parameter_store_name: &str) -> ParameterStore<'_> {
951 self.get_parameter_store_with_options(
952 parameter_store_name,
953 ParameterStoreEvaluationOptions::default(),
954 )
955 }
956
957 pub fn get_parameter_store_with_options(
958 &self,
959 parameter_store_name: &str,
960 options: ParameterStoreEvaluationOptions,
961 ) -> ParameterStore<'_> {
962 self.event_logger
963 .increment_non_exposure_checks(parameter_store_name);
964
965 let data = read_lock_or_else!(self.spec_store.data, {
966 log_error_to_statsig_and_console!(
967 self.ops_stats.clone(),
968 TAG,
969 StatsigErr::LockFailure(
970 "Failed to acquire read lock for spec store data".to_string()
971 )
972 );
973 return ParameterStore {
974 name: parameter_store_name.to_string(),
975 parameters: HashMap::new(),
976 details: EvaluationDetails::unrecognized_no_data(),
977 options,
978 _statsig_ref: self,
979 };
980 });
981
982 let stores = &data.values.param_stores;
983 let store = match stores {
984 Some(stores) => stores.get(parameter_store_name),
985 None => {
986 return ParameterStore {
987 name: parameter_store_name.to_string(),
988 parameters: HashMap::new(),
989 details: EvaluationDetails::unrecognized(&data),
990 options,
991 _statsig_ref: self,
992 };
993 }
994 };
995 match store {
996 Some(store) => ParameterStore {
997 name: parameter_store_name.to_string(),
998 parameters: store.parameters.clone(),
999 details: EvaluationDetails::recognized(&data, &EvaluatorResult::default()),
1000 options,
1001 _statsig_ref: self,
1002 },
1003 None => ParameterStore {
1004 name: parameter_store_name.to_string(),
1005 parameters: HashMap::new(),
1006 details: EvaluationDetails::unrecognized(&data),
1007 options,
1008 _statsig_ref: self,
1009 },
1010 }
1011 }
1012}
1013
1014impl Statsig {
1019 pub fn identify(&self, user: &StatsigUser) {
1020 let user_internal = self.internalize_user(user);
1021
1022 self.event_logger.enqueue(EnqueuePassthroughOp {
1023 event: StatsigEventInternal::new_custom_event(
1024 user_internal.to_loggable(),
1025 "statsig::identify".to_string(),
1026 None,
1027 None,
1028 ),
1029 });
1030 }
1031}
1032
1033impl Statsig {
1038 pub fn get_cmab_ranked_groups(
1039 &self,
1040 user: &StatsigUser,
1041 cmab_name: &str,
1042 ) -> Vec<CMABRankedGroup> {
1043 self.event_logger.increment_non_exposure_checks(cmab_name);
1044
1045 let data = read_lock_or_else!(self.spec_store.data, {
1046 log_error_to_statsig_and_console!(
1047 self.ops_stats.clone(),
1048 TAG,
1049 StatsigErr::LockFailure(
1050 "Failed to acquire read lock for spec store data".to_string()
1051 )
1052 );
1053 return vec![];
1054 });
1055 let user_internal = self.internalize_user(user);
1056 get_cmab_ranked_list(
1057 &mut EvaluatorContext::new(
1058 &user_internal,
1059 &data,
1060 &self.hashing,
1061 data.values.app_id.as_ref(),
1062 self.override_adapter.as_ref(),
1063 self.should_use_experimental_ua_parser(),
1064 ),
1065 cmab_name,
1066 )
1067 }
1068
1069 pub fn log_cmab_exposure_for_group(
1070 &self,
1071 user: &StatsigUser,
1072 cmab_name: &str,
1073 group_id: String,
1074 ) {
1075 let user_internal = self.internalize_user(user);
1076
1077 let mut experiment = self.get_experiment_impl(&user_internal, cmab_name);
1078 experiment.rule_id = group_id;
1079
1080 self.event_logger.enqueue(EnqueueExperimentExpoOp {
1081 exposure_time: Utc::now().timestamp_millis() as u64,
1082 user: &user_internal,
1083 experiment: &experiment,
1084 trigger: ExposureTrigger::Manual,
1085 });
1086 }
1087}
1088
1089impl Statsig {
1094 pub fn shared() -> Arc<Statsig> {
1095 let lock = match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
1096 Some(lock) => lock,
1097 None => {
1098 log_e!(
1099 TAG,
1100 "Statsig::shared() mutex error: Failed to lock SHARED_INSTANCE"
1101 );
1102 return Arc::new(Statsig::new(ERROR_SDK_KEY, None));
1103 }
1104 };
1105
1106 match lock.as_ref() {
1107 Some(statsig) => statsig.clone(),
1108 None => {
1109 log_e!(
1110 TAG,
1111 "Statsig::shared() called, but no instance has been set with Statsig::new_shared(...)"
1112 );
1113 Arc::new(Statsig::new(ERROR_SDK_KEY, None))
1114 }
1115 }
1116 }
1117
1118 pub fn new_shared(
1119 sdk_key: &str,
1120 options: Option<Arc<StatsigOptions>>,
1121 ) -> Result<Arc<Statsig>, StatsigErr> {
1122 match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
1123 Some(mut lock) => {
1124 if lock.is_some() {
1125 let message = "Statsig shared instance already exists. Call Statsig::remove_shared() before creating a new instance.";
1126 log_e!(TAG, "{}", message);
1127 return Err(StatsigErr::SharedInstanceFailure(message.to_string()));
1128 }
1129
1130 let statsig = Arc::new(Statsig::new(sdk_key, options));
1131 *lock = Some(statsig.clone());
1132 Ok(statsig)
1133 }
1134 None => {
1135 let message = "Statsig::new_shared() mutex error: Failed to lock SHARED_INSTANCE";
1136 log_e!(TAG, "{}", message);
1137 Err(StatsigErr::SharedInstanceFailure(message.to_string()))
1138 }
1139 }
1140 }
1141
1142 pub fn remove_shared() {
1143 match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
1144 Some(mut lock) => {
1145 *lock = None;
1146 }
1147 None => {
1148 log_e!(
1149 TAG,
1150 "Statsig::remove_shared() mutex error: Failed to lock SHARED_INSTANCE"
1151 );
1152 }
1153 }
1154 }
1155
1156 pub fn has_shared_instance() -> bool {
1157 match SHARED_INSTANCE.try_lock_for(Duration::from_secs(5)) {
1158 Some(lock) => lock.is_some(),
1159 None => false,
1160 }
1161 }
1162}
1163
1164impl Statsig {
1169 pub fn check_gate(&self, user: &StatsigUser, gate_name: &str) -> bool {
1170 self.check_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1171 }
1172
1173 pub fn check_gate_with_options(
1174 &self,
1175 user: &StatsigUser,
1176 gate_name: &str,
1177 options: FeatureGateEvaluationOptions,
1178 ) -> bool {
1179 let user_internal = self.internalize_user(user);
1180 let disable_exposure_logging = options.disable_exposure_logging;
1181 let (details, evaluation) = self.get_gate_evaluation(&user_internal, gate_name);
1182
1183 let value = evaluation.as_ref().map(|e| e.value).unwrap_or_default();
1184 let rule_id = evaluation
1185 .as_ref()
1186 .map(|e| e.base.rule_id.clone())
1187 .unwrap_or_default();
1188
1189 if disable_exposure_logging {
1190 log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1191 self.event_logger.increment_non_exposure_checks(gate_name);
1192 } else {
1193 self.event_logger.enqueue(EnqueueGateExpoOp {
1194 exposure_time: Utc::now().timestamp_millis() as u64,
1195 user: &user_internal,
1196 queried_gate_name: gate_name,
1197 evaluation: evaluation.map(Cow::Owned),
1198 details: details.clone(),
1199 trigger: ExposureTrigger::Auto,
1200 });
1201 }
1202
1203 self.emit_gate_evaluated(gate_name, rule_id.as_str(), value, &details.reason);
1204
1205 value
1206 }
1207
1208 pub fn get_feature_gate(&self, user: &StatsigUser, gate_name: &str) -> FeatureGate {
1209 self.get_feature_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1210 }
1211
1212 pub fn get_feature_gate_with_options(
1213 &self,
1214 user: &StatsigUser,
1215 gate_name: &str,
1216 options: FeatureGateEvaluationOptions,
1217 ) -> FeatureGate {
1218 let user_internal = self.internalize_user(user);
1219 let disable_exposure_logging = options.disable_exposure_logging;
1220 let (details, evaluation) = self.get_gate_evaluation(&user_internal, gate_name);
1221
1222 if disable_exposure_logging {
1223 log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1224 self.event_logger.increment_non_exposure_checks(gate_name);
1225 } else {
1226 self.event_logger.enqueue(EnqueueGateExpoOp {
1227 exposure_time: Utc::now().timestamp_millis() as u64,
1228 user: &user_internal,
1229 queried_gate_name: gate_name,
1230 evaluation: evaluation.as_ref().map(Cow::Borrowed),
1231 details: details.clone(),
1232 trigger: ExposureTrigger::Auto,
1233 });
1234 }
1235
1236 let gate = make_feature_gate(gate_name, evaluation, details);
1237 self.emit_gate_evaluated(gate_name, &gate.rule_id, gate.value, &gate.details.reason);
1238 gate
1239 }
1240
1241 pub fn manually_log_gate_exposure(&self, user: &StatsigUser, gate_name: &str) {
1242 let user_internal = self.internalize_user(user);
1243 let (details, evaluation) = self.get_gate_evaluation(&user_internal, gate_name);
1244 self.event_logger.enqueue(EnqueueGateExpoOp {
1245 exposure_time: Utc::now().timestamp_millis() as u64,
1246 user: &user_internal,
1247 queried_gate_name: gate_name,
1248 evaluation: evaluation.map(Cow::Owned),
1249 details: details.clone(),
1250 trigger: ExposureTrigger::Manual,
1251 });
1252 }
1253
1254 pub fn get_fields_needed_for_gate(&self, gate_name: &str) -> Vec<String> {
1255 let data = read_lock_or_else!(self.spec_store.data, {
1256 log_error_to_statsig_and_console!(
1257 self.ops_stats.clone(),
1258 TAG,
1259 StatsigErr::LockFailure(
1260 "Failed to acquire read lock for spec store data".to_string()
1261 )
1262 );
1263 return vec![];
1264 });
1265
1266 let gate = data.values.feature_gates.get(gate_name);
1267 match gate {
1268 Some(gate) => match &gate.spec.fields_used {
1269 Some(fields) => fields.iter().map(|f| f.unperformant_to_string()).collect(),
1270 None => vec![],
1271 },
1272 None => vec![],
1273 }
1274 }
1275}
1276
1277impl Statsig {
1282 pub fn override_gate(&self, gate_name: &str, value: bool, id: Option<&str>) {
1283 if let Some(adapter) = &self.override_adapter {
1284 adapter.override_gate(gate_name, value, id);
1285 }
1286 }
1287
1288 pub fn override_dynamic_config(
1289 &self,
1290 config_name: &str,
1291 value: HashMap<String, serde_json::Value>,
1292 id: Option<&str>,
1293 ) {
1294 if let Some(adapter) = &self.override_adapter {
1295 adapter.override_dynamic_config(config_name, value, id);
1296 }
1297 }
1298
1299 pub fn override_layer(
1300 &self,
1301 layer_name: &str,
1302 value: HashMap<String, serde_json::Value>,
1303 id: Option<&str>,
1304 ) {
1305 if let Some(adapter) = &self.override_adapter {
1306 adapter.override_layer(layer_name, value, id);
1307 }
1308 }
1309
1310 pub fn override_experiment(
1311 &self,
1312 experiment_name: &str,
1313 value: HashMap<String, serde_json::Value>,
1314 id: Option<&str>,
1315 ) {
1316 if let Some(adapter) = &self.override_adapter {
1317 adapter.override_experiment(experiment_name, value, id);
1318 }
1319 }
1320
1321 pub fn override_experiment_by_group_name(
1322 &self,
1323 experiment_name: &str,
1324 group_name: &str,
1325 id: Option<&str>,
1326 ) {
1327 if let Some(adapter) = &self.override_adapter {
1328 adapter.override_experiment_by_group_name(experiment_name, group_name, id);
1329 }
1330 }
1331
1332 pub fn remove_gate_override(&self, gate_name: &str, id: Option<&str>) {
1333 if let Some(adapter) = &self.override_adapter {
1334 adapter.remove_gate_override(gate_name, id);
1335 }
1336 }
1337
1338 pub fn remove_dynamic_config_override(&self, config_name: &str, id: Option<&str>) {
1339 if let Some(adapter) = &self.override_adapter {
1340 adapter.remove_dynamic_config_override(config_name, id);
1341 }
1342 }
1343
1344 pub fn remove_experiment_override(&self, experiment_name: &str, id: Option<&str>) {
1345 if let Some(adapter) = &self.override_adapter {
1346 adapter.remove_experiment_override(experiment_name, id);
1347 }
1348 }
1349
1350 pub fn remove_layer_override(&self, layer_name: &str, id: Option<&str>) {
1351 if let Some(adapter) = &self.override_adapter {
1352 adapter.remove_layer_override(layer_name, id);
1353 }
1354 }
1355
1356 pub fn remove_all_overrides(&self) {
1357 if let Some(adapter) = &self.override_adapter {
1358 adapter.remove_all_overrides();
1359 }
1360 }
1361}
1362
1363impl Statsig {
1368 pub fn get_feature_gate_list(&self) -> Vec<String> {
1369 let data = read_lock_or_else!(self.spec_store.data, {
1370 log_error_to_statsig_and_console!(
1371 &self.ops_stats,
1372 TAG,
1373 StatsigErr::LockFailure(
1374 "Failed to acquire read lock for spec store data".to_string()
1375 )
1376 );
1377 return vec![];
1378 });
1379
1380 data.values.feature_gates.unperformant_keys()
1381 }
1382
1383 pub fn get_dynamic_config_list(&self) -> Vec<String> {
1384 let data = read_lock_or_else!(self.spec_store.data, {
1385 log_error_to_statsig_and_console!(
1386 &self.ops_stats,
1387 TAG,
1388 StatsigErr::LockFailure(
1389 "Failed to acquire read lock for spec store data".to_string()
1390 )
1391 );
1392 return vec![];
1393 });
1394
1395 data.values
1396 .dynamic_configs
1397 .unperformant_keys_entity_filter("dynamic_config")
1398 }
1399
1400 pub fn get_experiment_list(&self) -> Vec<String> {
1401 let data = read_lock_or_else!(self.spec_store.data, {
1402 log_error_to_statsig_and_console!(
1403 &self.ops_stats,
1404 TAG,
1405 StatsigErr::LockFailure(
1406 "Failed to acquire read lock for spec store data".to_string()
1407 )
1408 );
1409 return vec![];
1410 });
1411
1412 data.values
1413 .dynamic_configs
1414 .unperformant_keys_entity_filter("experiment")
1415 }
1416
1417 pub fn get_autotune_list(&self) -> Vec<String> {
1418 let data = read_lock_or_else!(self.spec_store.data, {
1419 log_error_to_statsig_and_console!(
1420 &self.ops_stats,
1421 TAG,
1422 StatsigErr::LockFailure(
1423 "Failed to acquire read lock for spec store data".to_string()
1424 )
1425 );
1426 return vec![];
1427 });
1428
1429 data.values
1430 .dynamic_configs
1431 .unperformant_keys_entity_filter("autotune")
1432 }
1433
1434 pub fn get_parameter_store_list(&self) -> Vec<String> {
1435 let data = read_lock_or_else!(self.spec_store.data, {
1436 log_error_to_statsig_and_console!(
1437 &self.ops_stats,
1438 TAG,
1439 StatsigErr::LockFailure(
1440 "Failed to acquire read lock for spec store data".to_string()
1441 )
1442 );
1443 return vec![];
1444 });
1445
1446 match &data.values.param_stores {
1447 Some(param_stores) => param_stores.keys().cloned().collect(),
1448 None => vec![],
1449 }
1450 }
1451
1452 pub fn get_layer_list(&self) -> Vec<String> {
1453 let data = read_lock_or_else!(self.spec_store.data, {
1454 log_error_to_statsig_and_console!(
1455 &self.ops_stats,
1456 TAG,
1457 StatsigErr::LockFailure(
1458 "Failed to acquire read lock for spec store data".to_string()
1459 )
1460 );
1461 return vec![];
1462 });
1463
1464 data.values.layer_configs.unperformant_keys()
1465 }
1466
1467 pub fn __get_parsed_user_agent_value(
1468 &self,
1469 user: &StatsigUser,
1470 ) -> Option<ParsedUserAgentValue> {
1471 UserAgentParser::get_parsed_user_agent_value_for_user(user, &self.options)
1472 }
1473}
1474
1475impl Statsig {
1480 pub fn get_dynamic_config(
1481 &self,
1482 user: &StatsigUser,
1483 dynamic_config_name: &str,
1484 ) -> DynamicConfig {
1485 self.get_dynamic_config_with_options(
1486 user,
1487 dynamic_config_name,
1488 DynamicConfigEvaluationOptions::default(),
1489 )
1490 }
1491
1492 pub fn get_dynamic_config_with_options(
1493 &self,
1494 user: &StatsigUser,
1495 dynamic_config_name: &str,
1496 options: DynamicConfigEvaluationOptions,
1497 ) -> DynamicConfig {
1498 let user_internal = self.internalize_user(user);
1499 let disable_exposure_logging = options.disable_exposure_logging;
1500 let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1501
1502 if disable_exposure_logging {
1503 log_d!(
1504 TAG,
1505 "Exposure logging is disabled for Dynamic Config {}",
1506 dynamic_config_name
1507 );
1508 self.event_logger
1509 .increment_non_exposure_checks(dynamic_config_name);
1510 } else {
1511 self.event_logger.enqueue(EnqueueConfigExpoOp {
1512 exposure_time: Utc::now().timestamp_millis() as u64,
1513 user: &user_internal,
1514 config: &dynamic_config,
1515 trigger: ExposureTrigger::Auto,
1516 });
1517 }
1518
1519 self.emit_dynamic_config_evaluated(&dynamic_config);
1520
1521 dynamic_config
1522 }
1523
1524 pub fn manually_log_dynamic_config_exposure(
1525 &self,
1526 user: &StatsigUser,
1527 dynamic_config_name: &str,
1528 ) {
1529 let user_internal = self.internalize_user(user);
1530 let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1531 self.event_logger.enqueue(EnqueueConfigExpoOp {
1532 exposure_time: Utc::now().timestamp_millis() as u64,
1533 user: &user_internal,
1534 config: &dynamic_config,
1535 trigger: ExposureTrigger::Manual,
1536 });
1537 }
1538
1539 pub fn get_fields_needed_for_dynamic_config(&self, config_name: &str) -> Vec<String> {
1540 let data = read_lock_or_else!(self.spec_store.data, {
1541 log_error_to_statsig_and_console!(
1542 self.ops_stats.clone(),
1543 TAG,
1544 StatsigErr::LockFailure(
1545 "Failed to acquire read lock for spec store data".to_string()
1546 )
1547 );
1548 return vec![];
1549 });
1550
1551 let config = data.values.dynamic_configs.get(config_name);
1552 match config {
1553 Some(config) => match &config.spec.fields_used {
1554 Some(fields) => fields.iter().map(|f| f.unperformant_to_string()).collect(),
1555 None => vec![],
1556 },
1557 None => vec![],
1558 }
1559 }
1560}
1561
1562impl Statsig {
1567 pub fn get_experiment(&self, user: &StatsigUser, experiment_name: &str) -> Experiment {
1568 self.get_experiment_with_options(
1569 user,
1570 experiment_name,
1571 ExperimentEvaluationOptions::default(),
1572 )
1573 }
1574
1575 pub fn get_experiment_with_options(
1576 &self,
1577 user: &StatsigUser,
1578 experiment_name: &str,
1579 options: ExperimentEvaluationOptions,
1580 ) -> Experiment {
1581 let user_internal = self.internalize_user(user);
1582 let disable_exposure_logging = options.disable_exposure_logging;
1583 let mut experiment = self.get_experiment_impl(&user_internal, experiment_name);
1584 if let Some(persisted_experiment) = self.persistent_values_manager.as_ref().and_then(|m| {
1585 m.try_apply_sticky_value_to_experiment(&user_internal, &options, &experiment)
1586 }) {
1587 experiment = persisted_experiment
1588 }
1589
1590 if disable_exposure_logging {
1591 log_d!(
1592 TAG,
1593 "Exposure logging is disabled for experiment {}",
1594 experiment_name
1595 );
1596 self.event_logger
1597 .increment_non_exposure_checks(experiment_name);
1598 } else {
1599 self.event_logger.enqueue(EnqueueExperimentExpoOp {
1600 exposure_time: Utc::now().timestamp_millis() as u64,
1601 user: &user_internal,
1602 experiment: &experiment,
1603 trigger: ExposureTrigger::Auto,
1604 });
1605 }
1606
1607 self.emit_experiment_evaluated(&experiment);
1608
1609 experiment
1610 }
1611
1612 pub fn manually_log_experiment_exposure(&self, user: &StatsigUser, experiment_name: &str) {
1613 let user_internal = self.internalize_user(user);
1614 let experiment = self.get_experiment_impl(&user_internal, experiment_name);
1615 self.event_logger.enqueue(EnqueueExperimentExpoOp {
1616 exposure_time: Utc::now().timestamp_millis() as u64,
1617 user: &user_internal,
1618 experiment: &experiment,
1619 trigger: ExposureTrigger::Manual,
1620 });
1621 }
1622
1623 pub fn get_fields_needed_for_experiment(&self, experiment_name: &str) -> Vec<String> {
1624 let data = read_lock_or_else!(self.spec_store.data, {
1625 log_error_to_statsig_and_console!(
1626 self.ops_stats.clone(),
1627 TAG,
1628 StatsigErr::LockFailure(
1629 "Failed to acquire read lock for spec store data".to_string()
1630 )
1631 );
1632 return vec![];
1633 });
1634
1635 let config = data.values.dynamic_configs.get(experiment_name);
1636 match config {
1637 Some(config) => match &config.spec.fields_used {
1638 Some(fields) => fields.iter().map(|f| f.unperformant_to_string()).collect(),
1639 None => vec![],
1640 },
1641 None => vec![],
1642 }
1643 }
1644
1645 pub fn get_experiment_by_group_name(
1646 &self,
1647 experiment_name: &str,
1648 group_name: &str,
1649 ) -> Experiment {
1650 let data = read_lock_or_else!(self.spec_store.data, {
1651 log_error_to_statsig_and_console!(
1652 self.ops_stats.clone(),
1653 TAG,
1654 StatsigErr::LockFailure(
1655 "Failed to acquire read lock for spec store data".to_string()
1656 )
1657 );
1658 return make_experiment(
1659 experiment_name,
1660 None,
1661 EvaluationDetails::error("Failed to acquire read lock for spec store data"),
1662 );
1663 });
1664
1665 let Some(exp) = data.values.dynamic_configs.get(experiment_name) else {
1666 return make_experiment(
1667 experiment_name,
1668 None,
1669 EvaluationDetails::unrecognized(&data),
1670 );
1671 };
1672
1673 if let Some(rule) = exp
1674 .spec
1675 .rules
1676 .iter()
1677 .find(|rule| rule.group_name.as_deref() == Some(group_name))
1678 {
1679 let value = rule.return_value.get_json().unwrap_or_default();
1680 let rule_id = String::from(rule.id.as_str());
1681 let id_type = rule.id_type.value.unperformant_to_string();
1682 let group_name = rule.group_name.as_ref().map(|g| g.unperformant_to_string());
1683
1684 return Experiment {
1685 name: experiment_name.to_string(),
1686 value,
1687 rule_id,
1688 id_type,
1689 group_name,
1690 details: EvaluationDetails::recognized_without_eval_result(&data),
1691 is_experiment_active: exp.spec.is_active.unwrap_or(false),
1692 __evaluation: None,
1693 };
1694 }
1695
1696 make_experiment(
1697 experiment_name,
1698 None,
1699 EvaluationDetails::unrecognized(&data),
1700 )
1701 }
1702}
1703
1704impl Statsig {
1709 pub fn get_layer(&self, user: &StatsigUser, layer_name: &str) -> Layer {
1710 self.get_layer_with_options(user, layer_name, LayerEvaluationOptions::default())
1711 }
1712
1713 pub fn get_layer_with_options(
1714 &self,
1715 user: &StatsigUser,
1716 layer_name: &str,
1717 options: LayerEvaluationOptions,
1718 ) -> Layer {
1719 let user_internal = self.internalize_user(user);
1720 self.get_layer_impl(user_internal, layer_name, options)
1721 }
1722
1723 pub fn get_prompt(&self, user: &StatsigUser, prompt_name: &str) -> Layer {
1724 self.get_layer(user, prompt_name)
1725 }
1726
1727 pub fn get_prompt_with_options(
1728 &self,
1729 user: &StatsigUser,
1730 prompt_name: &str,
1731 options: LayerEvaluationOptions,
1732 ) -> Layer {
1733 self.get_layer_with_options(user, prompt_name, options)
1734 }
1735
1736 pub fn manually_log_layer_parameter_exposure(
1737 &self,
1738 user: &StatsigUser,
1739 layer_name: &str,
1740 parameter_name: String,
1741 ) {
1742 let user_internal = self.internalize_user(user);
1743 let layer =
1744 self.get_layer_impl(user_internal, layer_name, LayerEvaluationOptions::default());
1745
1746 self.event_logger
1747 .enqueue(EnqueueLayerParamExpoOp::LayerOwned(
1748 Utc::now().timestamp_millis() as u64,
1749 Box::new(layer),
1750 parameter_name,
1751 ExposureTrigger::Manual,
1752 ));
1753 }
1754
1755 pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> {
1756 let data = read_lock_or_else!(self.spec_store.data, {
1757 log_error_to_statsig_and_console!(
1758 self.ops_stats.clone(),
1759 TAG,
1760 StatsigErr::LockFailure(
1761 "Failed to acquire read lock for spec store data".to_string()
1762 )
1763 );
1764 return vec![];
1765 });
1766
1767 let layer = data.values.layer_configs.get(layer_name);
1768 match layer {
1769 Some(layer) => match &layer.spec.fields_used {
1770 Some(fields) => fields.iter().map(|f| f.unperformant_to_string()).collect(),
1771 None => vec![],
1772 },
1773 None => vec![],
1774 }
1775 }
1776}
1777
1778impl Statsig {
1783 pub(crate) fn get_from_statsig_env(&self, key: &str) -> Option<DynamicValue> {
1784 if let Some(env) = &self.statsig_environment {
1785 return env.get(key).cloned();
1786 }
1787
1788 if let Some(fallback_env) = self
1789 .fallback_environment
1790 .try_lock_for(Duration::from_secs(5))
1791 {
1792 if let Some(env) = &*fallback_env {
1793 return env.get(key).cloned();
1794 }
1795 }
1796
1797 None
1798 }
1799
1800 pub(crate) fn get_value_from_global_custom_fields(&self, key: &str) -> Option<&DynamicValue> {
1801 if let Some(env) = &self.options.global_custom_fields {
1802 return env.get(key);
1803 }
1804
1805 None
1806 }
1807
1808 pub(crate) fn use_global_custom_fields<T>(
1809 &self,
1810 f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1811 ) -> T {
1812 f(self.options.global_custom_fields.as_ref())
1813 }
1814
1815 pub(crate) fn use_statsig_env<T>(
1816 &self,
1817 f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1818 ) -> T {
1819 if let Some(env) = &self.statsig_environment {
1820 return f(Some(env));
1821 }
1822
1823 if let Some(fallback_env) = self
1824 .fallback_environment
1825 .try_lock_for(Duration::from_secs(5))
1826 {
1827 if let Some(env) = &*fallback_env {
1828 return f(Some(env));
1829 }
1830 }
1831
1832 f(None)
1833 }
1834}
1835
1836impl Statsig {
1841 fn evaluate_spec<T>(
1842 &self,
1843 user_internal: &StatsigUserInternal,
1844 spec_name: &str,
1845 make_empty_result: impl FnOnce(EvaluationDetails) -> T,
1846 make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
1847 spec_type: &SpecType,
1848 ) -> T {
1849 let data = read_lock_or_else!(self.spec_store.data, {
1850 log_error_to_statsig_and_console!(
1851 &self.ops_stats,
1852 TAG,
1853 StatsigErr::LockFailure(
1854 "Failed to acquire read lock for spec store data".to_string()
1855 )
1856 );
1857 return make_empty_result(EvaluationDetails::unrecognized_no_data());
1858 });
1859
1860 let app_id = data.values.app_id.as_ref();
1861 let mut context = EvaluatorContext::new(
1862 user_internal,
1863 &data,
1864 &self.hashing,
1865 app_id,
1866 self.override_adapter.as_ref(),
1867 self.should_use_experimental_ua_parser(),
1868 );
1869
1870 match Evaluator::evaluate_with_details(&mut context, spec_name, spec_type) {
1871 Ok(eval_details) => make_result(context.result, eval_details),
1872 Err(e) => {
1873 log_error_to_statsig_and_console!(
1874 &self.ops_stats,
1875 TAG,
1876 StatsigErr::EvaluationError(e.to_string())
1877 );
1878 make_empty_result(EvaluationDetails::error(&e.to_string()))
1879 }
1880 }
1881 }
1882
1883 fn get_gate_evaluation(
1884 &self,
1885 user_internal: &StatsigUserInternal,
1886 gate_name: &str,
1887 ) -> (EvaluationDetails, Option<GateEvaluation>) {
1888 self.evaluate_spec(
1889 user_internal,
1890 gate_name,
1891 |eval_details| (eval_details, None),
1892 |mut result, eval_details| {
1893 let evaluation = result_to_gate_eval(gate_name, &mut result);
1894 (eval_details, Some(evaluation))
1895 },
1896 &SpecType::Gate,
1897 )
1898 }
1899
1900 fn get_dynamic_config_impl(
1901 &self,
1902 user_internal: &StatsigUserInternal,
1903 config_name: &str,
1904 ) -> DynamicConfig {
1905 self.evaluate_spec(
1906 user_internal,
1907 config_name,
1908 |eval_details| make_dynamic_config(config_name, None, eval_details),
1909 |mut result, eval_details| {
1910 let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
1911 make_dynamic_config(config_name, Some(evaluation), eval_details)
1912 },
1913 &SpecType::DynamicConfig,
1914 )
1915 }
1916
1917 fn get_experiment_impl(
1918 &self,
1919 user_internal: &StatsigUserInternal,
1920 experiment_name: &str,
1921 ) -> Experiment {
1922 self.evaluate_spec(
1923 user_internal,
1924 experiment_name,
1925 |eval_details| make_experiment(experiment_name, None, eval_details),
1926 |mut result, eval_details| {
1927 let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
1928 make_experiment(experiment_name, Some(evaluation), eval_details)
1929 },
1930 &SpecType::Experiment,
1931 )
1932 }
1933
1934 fn get_layer_impl(
1935 &self,
1936 user_internal: StatsigUserInternal,
1937 layer_name: &str,
1938 evaluation_options: LayerEvaluationOptions,
1939 ) -> Layer {
1940 let disable_exposure_logging = evaluation_options.disable_exposure_logging;
1941
1942 if disable_exposure_logging {
1943 self.event_logger.increment_non_exposure_checks(layer_name);
1944 }
1945
1946 let mut layer = self.evaluate_spec(
1947 &user_internal,
1948 layer_name,
1949 |eval_details| {
1950 make_layer(
1951 user_internal.to_loggable(),
1952 layer_name,
1953 None,
1954 eval_details,
1955 None,
1956 disable_exposure_logging,
1957 )
1958 },
1959 |mut result, eval_details| {
1960 let evaluation = result_to_layer_eval(layer_name, &mut result);
1961 let event_logger_ptr = Arc::downgrade(&self.event_logger);
1962
1963 make_layer(
1964 user_internal.to_loggable(),
1965 layer_name,
1966 Some(evaluation),
1967 eval_details,
1968 Some(event_logger_ptr),
1969 disable_exposure_logging,
1970 )
1971 },
1972 &SpecType::Layer,
1973 );
1974
1975 let data = read_lock_or_else!(self.spec_store.data, {
1976 log_error_to_statsig_and_console!(
1977 &self.ops_stats,
1978 TAG,
1979 StatsigErr::LockFailure(
1980 "Failed to acquire read lock for spec store data".to_string()
1981 )
1982 );
1983 return layer;
1984 });
1985 if let Some(persisted_layer) = self.persistent_values_manager.as_ref().and_then(|p| {
1986 let event_logger_ptr = Arc::downgrade(&self.event_logger);
1987 p.try_apply_sticky_value_to_layer(
1988 &user_internal,
1989 &evaluation_options,
1990 &layer,
1991 Some(event_logger_ptr),
1992 disable_exposure_logging,
1993 &data,
1994 )
1995 }) {
1996 layer = persisted_layer
1997 }
1998
1999 self.emit_layer_evaluated(&layer);
2000
2001 layer
2002 }
2003
2004 fn internalize_user<'s, 'u>(&'s self, user: &'u StatsigUser) -> StatsigUserInternal<'s, 'u> {
2005 StatsigUserInternal::new(user, Some(self))
2006 }
2007
2008 fn set_default_environment_from_server(&self) {
2009 let data = read_lock_or_else!(self.spec_store.data, {
2010 return;
2011 });
2012
2013 if let Some(default_env) = data.values.default_environment.as_ref() {
2014 let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
2015
2016 match self
2017 .fallback_environment
2018 .try_lock_for(Duration::from_secs(5))
2019 {
2020 Some(mut fallback_env) => {
2021 *fallback_env = Some(env_map);
2022 }
2023 None => {
2024 log_e!(TAG, "Failed to lock fallback_environment");
2025 }
2026 }
2027 }
2028 }
2029
2030 fn log_init_finish(
2031 &self,
2032 success: bool,
2033 error_message: &Option<String>,
2034 duration: &f64,
2035 specs_info: &SpecsInfo,
2036 ) {
2037 let is_store_populated = specs_info.source != SpecsSource::NoValues;
2038 let source_str = specs_info.source.to_string();
2039
2040 let event = ObservabilityEvent::new_event(
2041 MetricType::Dist,
2042 "initialization".to_string(),
2043 *duration,
2044 Some(HashMap::from([
2045 ("success".to_owned(), success.to_string()),
2046 ("source".to_owned(), source_str.clone()),
2047 ("store_populated".to_owned(), is_store_populated.to_string()),
2048 (
2049 "spec_source_api".to_owned(),
2050 specs_info.source_api.clone().unwrap_or_default(),
2051 ),
2052 ])),
2053 );
2054
2055 self.ops_stats.log(event);
2056 self.ops_stats.add_marker(
2057 {
2058 let marker = Marker::new(KeyType::Overall, ActionType::End, None)
2059 .with_is_success(success)
2060 .with_config_spec_ready(specs_info.source != SpecsSource::NoValues)
2061 .with_source(source_str);
2062
2063 if let Some(msg) = &error_message {
2064 marker.with_message(msg.to_string())
2065 } else {
2066 marker
2067 }
2068 },
2069 Some(ContextType::Initialize),
2070 );
2071 self.ops_stats
2072 .enqueue_diagnostics_event(None, Some(ContextType::Initialize));
2073 }
2074
2075 fn should_use_experimental_ua_parser(&self) -> bool {
2076 self.options
2077 .__experimental_ua_parsing_enabled
2078 .unwrap_or(false)
2079 }
2080}
2081
2082fn initialize_event_logging_adapter(
2083 sdk_key: &str,
2084 options: &StatsigOptions,
2085) -> Arc<dyn EventLoggingAdapter> {
2086 options
2087 .event_logging_adapter
2088 .clone()
2089 .unwrap_or_else(|| Arc::new(StatsigHttpEventLoggingAdapter::new(sdk_key, Some(options))))
2090}
2091
2092fn initialize_specs_adapter(
2093 sdk_key: &str,
2094 options: &StatsigOptions,
2095 hashing: &HashUtil,
2096) -> SpecsAdapterHousing {
2097 if let Some(adapter) = options.specs_adapter.clone() {
2098 log_d!(TAG, "Using provided SpecsAdapter: {}", sdk_key);
2099 return SpecsAdapterHousing {
2100 inner: adapter,
2101 as_default_adapter: None,
2102 };
2103 }
2104
2105 if let Some(adapter_config) = options.spec_adapters_config.clone() {
2106 let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
2107 sdk_key,
2108 adapter_config,
2109 options,
2110 hashing,
2111 ));
2112
2113 return SpecsAdapterHousing {
2114 inner: adapter,
2115 as_default_adapter: None,
2116 };
2117 }
2118
2119 if let Some(data_adapter) = options.data_store.clone() {
2120 let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
2121 sdk_key,
2122 data_adapter,
2123 options,
2124 hashing,
2125 ));
2126
2127 return SpecsAdapterHousing {
2128 inner: adapter,
2129 as_default_adapter: None,
2130 };
2131 }
2132
2133 let adapter = Arc::new(StatsigHttpSpecsAdapter::new(sdk_key, Some(options), None));
2134
2135 SpecsAdapterHousing {
2136 inner: adapter.clone(),
2137 as_default_adapter: Some(adapter),
2138 }
2139}
2140
2141fn initialize_id_lists_adapter(sdk_key: &str, options: &StatsigOptions) -> IdListsAdapterHousing {
2142 if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
2143 return IdListsAdapterHousing {
2144 inner: Some(id_lists_adapter),
2145 as_default_adapter: None,
2146 };
2147 }
2148
2149 if options.enable_id_lists.unwrap_or(false) {
2150 let adapter = Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options));
2151
2152 return IdListsAdapterHousing {
2153 inner: Some(adapter.clone()),
2154 as_default_adapter: Some(adapter),
2155 };
2156 }
2157
2158 IdListsAdapterHousing {
2159 inner: None,
2160 as_default_adapter: None,
2161 }
2162}
2163
2164struct IdListsAdapterHousing {
2165 inner: Option<Arc<dyn IdListsAdapter>>,
2166 as_default_adapter: Option<Arc<StatsigHttpIdListsAdapter>>,
2167}
2168
2169struct SpecsAdapterHousing {
2170 inner: Arc<dyn SpecsAdapter>,
2171 as_default_adapter: Option<Arc<StatsigHttpSpecsAdapter>>,
2172}
2173
2174fn setup_ops_stats(
2175 sdk_key: &str,
2176 statsig_runtime: Arc<StatsigRuntime>,
2177 error_observer: &Arc<dyn OpsStatsEventObserver>,
2178 diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
2179 external_observer: &Option<Weak<dyn ObservabilityClient>>,
2180) -> Arc<OpsStatsForInstance> {
2181 let ops_stat = OPS_STATS.get_for_instance(sdk_key);
2182 ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
2183 ops_stat.subscribe(
2184 statsig_runtime.clone(),
2185 Arc::downgrade(diagnostics_observer),
2186 );
2187
2188 if let Some(ob_client) = external_observer {
2189 if let Some(client) = ob_client.upgrade() {
2190 client.init();
2191 let as_observer = client.to_ops_stats_event_observer();
2192 ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
2193 }
2194 }
2195
2196 ops_stat
2197}