statsig_rust/console_capture/
console_capture_instances.rs1use lazy_static::lazy_static;
2use std::{
3 collections::HashMap,
4 sync::{Arc, Weak},
5};
6
7use crate::console_capture::console_log_line_levels::StatsigLogLineLevel;
8use parking_lot::RwLock;
9
10use crate::{
11 log_e, observability::ops_stats::OpsStatsForInstance, user::StatsigUserLoggable, DynamicValue,
12 StatsigOptions, OPS_STATS,
13};
14
15const TAG: &str = stringify!(ConsoleCaptureRegistry);
16
17lazy_static! {
18 pub static ref CONSOLE_CAPTURE_REGISTRY: ConsoleCaptureRegistry = ConsoleCaptureRegistry {
19 instances_map: RwLock::new(HashMap::new())
20 };
21}
22
23pub struct ConsoleCaptureInstance {
24 enabled: bool,
25 allowed_log_levels: Vec<StatsigLogLineLevel>,
26 ops_stats_instance: Arc<OpsStatsForInstance>,
27 console_capture_user: StatsigUserLoggable,
28}
29
30pub struct ConsoleCaptureRegistry {
31 instances_map: RwLock<HashMap<String, Weak<ConsoleCaptureInstance>>>,
32}
33
34impl ConsoleCaptureInstance {
35 pub fn new(
36 sdk_key: &str,
37 statsig_options: &StatsigOptions,
38 environment: &Option<HashMap<String, DynamicValue>>,
39 ) -> Self {
40 let console_capture_options = statsig_options
41 .console_capture_options
42 .clone()
43 .unwrap_or_default();
44 let enabled = console_capture_options.enabled;
45 let ops_stats_instance = OPS_STATS.get_for_instance(sdk_key);
46 let allowed_log_levels = console_capture_options
47 .log_levels
48 .filter(|levels| !levels.is_empty())
49 .unwrap_or(vec![StatsigLogLineLevel::Warn, StatsigLogLineLevel::Error]);
50
51 let loggable_user = if let Some(console_capture_user) = console_capture_options.user {
52 StatsigUserLoggable::new(
53 &console_capture_user.data,
54 environment.clone(),
55 statsig_options.global_custom_fields.clone(),
56 )
57 } else {
58 StatsigUserLoggable::default_console_capture_user(
59 environment.clone(),
60 statsig_options.global_custom_fields.clone(),
61 )
62 };
63
64 Self {
65 enabled,
66 ops_stats_instance,
67 allowed_log_levels,
68 console_capture_user: loggable_user,
69 }
70 }
71
72 pub fn is_enabled(&self) -> bool {
73 self.enabled
74 }
75}
76
77impl ConsoleCaptureRegistry {
78 pub fn get_for_instance(
79 &self,
80 sdk_key: &str,
81 options: &StatsigOptions,
82 environment: &Option<HashMap<String, DynamicValue>>,
83 ) -> Arc<ConsoleCaptureInstance> {
84 match self
85 .instances_map
86 .try_read_for(std::time::Duration::from_secs(5))
87 {
88 Some(read_guard) => {
89 if let Some(instance) = read_guard.get(sdk_key) {
90 if let Some(instance) = instance.upgrade() {
91 return instance.clone();
92 }
93 }
94 }
95 None => {
96 log_e!(
97 TAG,
98 "Failed to get read guard: Failed to lock instances_map"
99 );
100 }
101 }
102
103 let instance = Arc::new(ConsoleCaptureInstance::new(sdk_key, options, environment));
104 match self
105 .instances_map
106 .try_write_for(std::time::Duration::from_secs(5))
107 {
108 Some(mut write_guard) => {
109 write_guard.insert(sdk_key.into(), Arc::downgrade(&instance));
110 }
111 None => {
112 log_e!(
113 TAG,
114 "Failed to get write guard: Failed to lock instances_map"
115 );
116 }
117 }
118
119 instance
120 }
121
122 fn get_and_upgrade_instance_for_key(
124 &self,
125 sdk_key: &str,
126 ) -> Option<Arc<ConsoleCaptureInstance>> {
127 match self
128 .instances_map
129 .try_read_for(std::time::Duration::from_secs(5))
130 {
131 Some(read_guard) => read_guard.get(sdk_key).cloned()?.upgrade(),
132 None => None,
133 }
134 }
135
136 pub fn is_enabled(&self, sdk_key: &str) -> bool {
137 let instance = self.get_and_upgrade_instance_for_key(sdk_key);
138
139 instance
140 .map(|instance| instance.is_enabled())
141 .unwrap_or(false)
142 }
143
144 pub fn enqueue_console_capture_event(
145 &self,
146 sdk_key: &str,
147 level: String,
148 payload: Vec<String>,
149 timestamp: u64,
150 stack_trace: Option<String>,
151 ) {
152 if !self.is_enabled(sdk_key) {
153 return;
154 }
155
156 if is_internal_log(&payload) {
157 return;
158 }
159
160 let Some(log_level) = StatsigLogLineLevel::from_string(&level) else {
161 log_e!(TAG, "Failed to parse log level: {}", level);
162 return;
163 };
164
165 let Some(instance) = self.get_and_upgrade_instance_for_key(sdk_key) else {
166 log_e!(
167 TAG,
168 "Failed to get and upgrade instance for key: {}",
169 sdk_key
170 );
171 return;
172 };
173
174 if !instance.allowed_log_levels.contains(&log_level) {
175 log_e!(TAG, "Log level not allowed: {:?}", log_level);
176 return;
177 }
178
179 let user = instance.console_capture_user.clone();
180
181 instance.ops_stats_instance.enqueue_console_capture_event(
182 level,
183 payload,
184 timestamp,
185 user,
186 stack_trace,
187 );
188 }
189}
190
191fn is_internal_log(payload: &[String]) -> bool {
192 payload
193 .iter()
194 .any(|p| p.contains("Statsig::") || p.contains("[Statsig]"))
195}