trojan_config/
analytics.rs1use serde::{Deserialize, Serialize};
4
5use crate::GeoipConfig;
6
7#[derive(Debug, Clone, Serialize, Deserialize, Default)]
9pub struct AnalyticsConfig {
10 #[serde(default)]
12 pub enabled: bool,
13
14 #[serde(default)]
16 pub clickhouse: Option<ClickHouseConfig>,
17
18 #[serde(default)]
20 pub buffer: AnalyticsBufferConfig,
21
22 #[serde(default)]
24 pub sampling: AnalyticsSamplingConfig,
25
26 #[serde(default)]
28 pub privacy: AnalyticsPrivacyConfig,
29
30 #[serde(default)]
32 pub server_id: Option<String>,
33
34 #[serde(default)]
36 pub geoip: Option<GeoipConfig>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct ClickHouseConfig {
42 pub url: String,
44
45 #[serde(default = "default_analytics_database")]
47 pub database: String,
48
49 #[serde(default = "default_analytics_table")]
51 pub table: String,
52
53 #[serde(default)]
55 pub username: Option<String>,
56
57 #[serde(default)]
59 pub password: Option<String>,
60
61 #[serde(default = "default_analytics_connect_timeout")]
63 pub connect_timeout_secs: u64,
64
65 #[serde(default = "default_analytics_write_timeout")]
67 pub write_timeout_secs: u64,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct AnalyticsBufferConfig {
73 #[serde(default = "default_analytics_buffer_size")]
75 pub size: usize,
76
77 #[serde(default = "default_analytics_flush_interval")]
79 pub flush_interval_secs: u64,
80
81 #[serde(default = "default_analytics_batch_size")]
83 pub batch_size: usize,
84
85 #[serde(default)]
87 pub fallback_path: Option<String>,
88}
89
90impl Default for AnalyticsBufferConfig {
91 fn default() -> Self {
92 Self {
93 size: default_analytics_buffer_size(),
94 flush_interval_secs: default_analytics_flush_interval(),
95 batch_size: default_analytics_batch_size(),
96 fallback_path: None,
97 }
98 }
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct AnalyticsSamplingConfig {
104 #[serde(default = "default_analytics_sample_rate")]
106 pub rate: f64,
107
108 #[serde(default)]
110 pub always_record_users: Vec<String>,
111}
112
113impl Default for AnalyticsSamplingConfig {
114 fn default() -> Self {
115 Self {
116 rate: default_analytics_sample_rate(),
117 always_record_users: Vec::new(),
118 }
119 }
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct AnalyticsPrivacyConfig {
125 #[serde(default = "default_true")]
128 pub record_peer_ip: bool,
129
130 #[serde(default)]
132 pub full_user_id: bool,
133
134 #[serde(default = "default_analytics_user_id_prefix_len")]
136 pub user_id_prefix_len: usize,
137
138 #[serde(default = "default_true")]
140 pub record_sni: bool,
141
142 #[serde(default = "default_geo_precision")]
147 pub geo_precision: String,
148}
149
150impl Default for AnalyticsPrivacyConfig {
151 fn default() -> Self {
152 Self {
153 record_peer_ip: true,
154 full_user_id: false,
155 user_id_prefix_len: default_analytics_user_id_prefix_len(),
156 record_sni: true,
157 geo_precision: default_geo_precision(),
158 }
159 }
160}
161
162fn default_geo_precision() -> String {
163 "city".to_string()
164}
165
166fn default_analytics_database() -> String {
169 "trojan".to_string()
170}
171
172fn default_analytics_table() -> String {
173 "connections".to_string()
174}
175
176fn default_analytics_buffer_size() -> usize {
177 10000
178}
179
180fn default_analytics_flush_interval() -> u64 {
181 5
182}
183
184fn default_analytics_batch_size() -> usize {
185 1000
186}
187
188fn default_analytics_sample_rate() -> f64 {
189 1.0
190}
191
192fn default_analytics_user_id_prefix_len() -> usize {
193 8
194}
195
196fn default_analytics_connect_timeout() -> u64 {
197 10
198}
199
200fn default_analytics_write_timeout() -> u64 {
201 30
202}
203
204fn default_true() -> bool {
205 true
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn analytics_config_default() {
214 let cfg = AnalyticsConfig::default();
215 assert!(!cfg.enabled);
216 assert!(cfg.clickhouse.is_none());
217 assert!(cfg.server_id.is_none());
218 assert!(cfg.geoip.is_none());
219 }
220
221 #[test]
222 fn privacy_config_defaults() {
223 let cfg = AnalyticsPrivacyConfig::default();
224 assert!(cfg.record_peer_ip);
225 assert!(!cfg.full_user_id);
226 assert_eq!(cfg.user_id_prefix_len, 8);
227 assert!(cfg.record_sni);
228 assert_eq!(cfg.geo_precision, "city");
229 }
230
231 #[test]
232 fn privacy_config_deserialize_geo_precision() {
233 let toml_str = r#"geo_precision = "country""#;
234 let cfg: AnalyticsPrivacyConfig = toml::from_str(toml_str).unwrap();
235 assert_eq!(cfg.geo_precision, "country");
236 }
237
238 #[test]
239 fn sampling_config_defaults() {
240 let cfg = AnalyticsSamplingConfig::default();
241 assert_eq!(cfg.rate, 1.0);
242 assert!(cfg.always_record_users.is_empty());
243 }
244
245 #[test]
246 fn buffer_config_defaults() {
247 let cfg = AnalyticsBufferConfig::default();
248 assert_eq!(cfg.size, 10000);
249 assert_eq!(cfg.flush_interval_secs, 5);
250 assert_eq!(cfg.batch_size, 1000);
251 assert!(cfg.fallback_path.is_none());
252 }
253
254 #[test]
255 fn analytics_config_with_geoip() {
256 let toml_str = r#"
257enabled = true
258
259[geoip]
260source = "geolite2-city"
261cache_path = "/tmp/geoip.mmdb"
262"#;
263 let cfg: AnalyticsConfig = toml::from_str(toml_str).unwrap();
264 assert!(cfg.enabled);
265 let geoip = cfg.geoip.unwrap();
266 assert_eq!(geoip.source, "geolite2-city");
267 assert_eq!(geoip.cache_path.as_deref(), Some("/tmp/geoip.mmdb"));
268 }
269}