Skip to main content

trojan_config/
analytics.rs

1//! Analytics configuration for connection event collection.
2
3use serde::{Deserialize, Serialize};
4
5use crate::GeoipConfig;
6
7/// Analytics configuration for connection event collection.
8#[derive(Debug, Clone, Serialize, Deserialize, Default)]
9pub struct AnalyticsConfig {
10    /// Whether analytics is enabled (runtime switch).
11    #[serde(default)]
12    pub enabled: bool,
13
14    /// ClickHouse configuration.
15    #[serde(default)]
16    pub clickhouse: Option<ClickHouseConfig>,
17
18    /// Buffer configuration.
19    #[serde(default)]
20    pub buffer: AnalyticsBufferConfig,
21
22    /// Sampling configuration.
23    #[serde(default)]
24    pub sampling: AnalyticsSamplingConfig,
25
26    /// Privacy configuration.
27    #[serde(default)]
28    pub privacy: AnalyticsPrivacyConfig,
29
30    /// Server identifier for multi-instance deployments.
31    #[serde(default)]
32    pub server_id: Option<String>,
33
34    /// GeoIP database for analytics geo fields (city-level).
35    #[serde(default)]
36    pub geoip: Option<GeoipConfig>,
37}
38
39/// ClickHouse connection configuration.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct ClickHouseConfig {
42    /// ClickHouse URL (e.g., "http://localhost:8123").
43    pub url: String,
44
45    /// Database name.
46    #[serde(default = "default_analytics_database")]
47    pub database: String,
48
49    /// Table name.
50    #[serde(default = "default_analytics_table")]
51    pub table: String,
52
53    /// Username (optional).
54    #[serde(default)]
55    pub username: Option<String>,
56
57    /// Password (optional).
58    #[serde(default)]
59    pub password: Option<String>,
60
61    /// Connection timeout in seconds.
62    #[serde(default = "default_analytics_connect_timeout")]
63    pub connect_timeout_secs: u64,
64
65    /// Write timeout in seconds.
66    #[serde(default = "default_analytics_write_timeout")]
67    pub write_timeout_secs: u64,
68}
69
70/// Buffer configuration for event batching.
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct AnalyticsBufferConfig {
73    /// Maximum number of events to buffer in memory.
74    #[serde(default = "default_analytics_buffer_size")]
75    pub size: usize,
76
77    /// Flush interval in seconds.
78    #[serde(default = "default_analytics_flush_interval")]
79    pub flush_interval_secs: u64,
80
81    /// Batch size for writes.
82    #[serde(default = "default_analytics_batch_size")]
83    pub batch_size: usize,
84
85    /// Fallback file path for failed writes.
86    #[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/// Sampling configuration for high-traffic scenarios.
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct AnalyticsSamplingConfig {
104    /// Sampling rate (0.0 - 1.0, where 1.0 = 100%).
105    #[serde(default = "default_analytics_sample_rate")]
106    pub rate: f64,
107
108    /// Users to always record (not affected by sampling).
109    #[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/// Privacy configuration for data collection.
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct AnalyticsPrivacyConfig {
125    /// Whether to record client IP addresses.
126    /// When false, GeoIP fields are also left empty.
127    #[serde(default = "default_true")]
128    pub record_peer_ip: bool,
129
130    /// Whether to record full user ID (false = prefix only).
131    #[serde(default)]
132    pub full_user_id: bool,
133
134    /// User ID prefix length when full_user_id is false.
135    #[serde(default = "default_analytics_user_id_prefix_len")]
136    pub user_id_prefix_len: usize,
137
138    /// Whether to record SNI.
139    #[serde(default = "default_true")]
140    pub record_sni: bool,
141
142    /// GeoIP precision for analytics events: "city", "country", or "none".
143    /// - "city": record all geo fields (country, region, city, ASN, org, lat/lon)
144    /// - "country": record only country code
145    /// - "none": do not record any geo information
146    #[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
166// Analytics default value functions
167
168fn 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}