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::user_data::UserDataMap,
12 user::StatsigUserLoggable, DynamicValue, 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 sdk_instance_id = statsig_options.get_sdk_instance_id(sdk_key);
46 let ops_stats_instance = OPS_STATS.get_for_instance(sdk_instance_id);
47 let allowed_log_levels = console_capture_options
48 .log_levels
49 .filter(|levels| !levels.is_empty())
50 .unwrap_or(vec![StatsigLogLineLevel::Warn, StatsigLogLineLevel::Error]);
51
52 let loggable_user = if let Some(console_capture_user) = console_capture_options.user {
53 StatsigUserLoggable::new(
54 &console_capture_user.data,
55 to_user_data_map(environment),
56 statsig_options.global_custom_fields.clone(),
57 )
58 } else {
59 StatsigUserLoggable::default_console_capture_user(
60 to_user_data_map(environment),
61 statsig_options.global_custom_fields.clone(),
62 )
63 };
64
65 Self {
66 enabled,
67 ops_stats_instance,
68 allowed_log_levels,
69 console_capture_user: loggable_user,
70 }
71 }
72
73 pub fn is_enabled(&self) -> bool {
74 self.enabled
75 }
76}
77
78fn to_user_data_map(environment: &Option<HashMap<String, DynamicValue>>) -> Option<UserDataMap> {
79 environment.as_ref().map(|environment| {
80 environment
81 .iter()
82 .map(|(key, value)| (key.clone(), value.clone()))
83 .collect()
84 })
85}
86
87impl ConsoleCaptureRegistry {
88 pub fn get_for_instance(
89 &self,
90 sdk_key: &str,
91 options: &StatsigOptions,
92 environment: &Option<HashMap<String, DynamicValue>>,
93 ) -> Arc<ConsoleCaptureInstance> {
94 match self
95 .instances_map
96 .try_read_for(std::time::Duration::from_secs(5))
97 {
98 Some(read_guard) => {
99 let sdk_instance_id = options.get_sdk_instance_id(sdk_key);
100 if let Some(instance) = read_guard.get(sdk_instance_id) {
101 if let Some(instance) = instance.upgrade() {
102 return instance.clone();
103 }
104 }
105 }
106 None => {
107 log_e!(
108 TAG,
109 "Failed to get read guard: Failed to lock instances_map"
110 );
111 }
112 }
113
114 let instance = Arc::new(ConsoleCaptureInstance::new(sdk_key, options, environment));
115 match self
116 .instances_map
117 .try_write_for(std::time::Duration::from_secs(5))
118 {
119 Some(mut write_guard) => {
120 let sdk_instance_id = options.get_sdk_instance_id(sdk_key);
121 write_guard.insert(sdk_instance_id.into(), Arc::downgrade(&instance));
122 }
123 None => {
124 log_e!(
125 TAG,
126 "Failed to get write guard: Failed to lock instances_map"
127 );
128 }
129 }
130
131 instance
132 }
133
134 fn get_and_upgrade_instance_for_key(
136 &self,
137 sdk_key: &str,
138 ) -> Option<Arc<ConsoleCaptureInstance>> {
139 match self
140 .instances_map
141 .try_read_for(std::time::Duration::from_secs(5))
142 {
143 Some(read_guard) => read_guard.get(sdk_key).cloned()?.upgrade(),
144 None => None,
145 }
146 }
147
148 pub fn is_enabled(&self, sdk_key: &str) -> bool {
149 let instance = self.get_and_upgrade_instance_for_key(sdk_key);
150
151 instance
152 .map(|instance| instance.is_enabled())
153 .unwrap_or(false)
154 }
155
156 pub fn enqueue_console_capture_event(
157 &self,
158 sdk_key: &str,
159 level: String,
160 payload: Vec<String>,
161 timestamp: u64,
162 stack_trace: Option<String>,
163 ) {
164 if !self.is_enabled(sdk_key) {
165 return;
166 }
167
168 if is_internal_log(&payload) {
169 return;
170 }
171
172 let Some(log_level) = StatsigLogLineLevel::from_string(&level) else {
173 log_e!(TAG, "Failed to parse log level: {}", level);
174 return;
175 };
176
177 let Some(instance) = self.get_and_upgrade_instance_for_key(sdk_key) else {
178 log_e!(
179 TAG,
180 "Failed to get and upgrade instance for key: {}",
181 sdk_key
182 );
183 return;
184 };
185
186 if !instance.allowed_log_levels.contains(&log_level) {
187 log_e!(TAG, "Log level not allowed: {:?}", log_level);
188 return;
189 }
190
191 let user = instance.console_capture_user.clone();
192
193 instance.ops_stats_instance.enqueue_console_capture_event(
194 level,
195 payload,
196 timestamp,
197 user,
198 stack_trace,
199 );
200 }
201}
202
203fn is_internal_log(payload: &[String]) -> bool {
204 payload
205 .iter()
206 .any(|p| p.contains("Statsig::") || p.contains("[Statsig]"))
207}