tushare_api/
logging.rs

1use log::{info, debug, error, trace, warn};
2
3#[cfg(feature = "tracing")]
4use tracing::{info as tracing_info, debug as tracing_debug, error as tracing_error, trace as tracing_trace, warn as tracing_warn};
5
6/// Log level configuration
7#[derive(Debug, Clone, PartialEq)]
8pub enum LogLevel {
9    /// Disable logging
10    Off,
11    /// Log errors only
12    Error,
13    /// Log errors and warnings
14    Warn,
15    /// Log basic information (default)
16    Info,
17    /// Log detailed debug information
18    Debug,
19    /// Log all information including raw data
20    Trace,
21}
22
23impl Default for LogLevel {
24    fn default() -> Self {
25        LogLevel::Info
26    }
27}
28
29/// Log configuration
30#[derive(Debug, Clone)]
31pub struct LogConfig {
32    /// Log level
33    pub level: LogLevel,
34    /// Whether to log request parameters
35    pub log_requests: bool,
36    /// Whether to log response content
37    pub log_responses: bool,
38    pub log_responses_err: bool,
39    /// Whether to log sensitive data (such as token)
40    pub log_sensitive_data: bool,
41    /// Whether to log performance metrics
42    pub log_performance: bool,
43}
44
45impl Default for LogConfig {
46    fn default() -> Self {
47        Self {
48            level: LogLevel::Info,
49            log_requests: true,
50            log_responses: false, // Responses can be large, not logged by default
51            log_responses_err: true,
52            log_sensitive_data: false, // Sensitive data not logged by default
53            log_performance: true,
54        }
55    }
56}
57
58/// Logger
59#[derive(Debug)]
60pub struct Logger {
61    config: LogConfig,
62}
63
64impl Logger {
65    /// Create a new logger
66    pub fn new(config: LogConfig) -> Self {
67        Self { config }
68    }
69
70    /// Check if logging should be performed for the specified level
71    pub fn should_log(&self, level: &LogLevel) -> bool {
72        use LogLevel::*;
73        match (&self.config.level, level) {
74            (Off, _) => false,
75            (Error, Error) => true,
76            (Warn, Error | Warn) => true,
77            (Info, Error | Warn | Info) => true,
78            (Debug, Error | Warn | Info | Debug) => true,
79            (Trace, _) => true,
80            _ => false,
81        }
82    }
83
84    /// Safely log messages with lazy evaluation (decides whether to record sensitive information based on configuration)
85    pub fn log_safe<F>(&self, level: LogLevel, message_fn: F, sensitive_data: Option<&str>)
86    where
87        F: FnOnce() -> String,
88    {
89        if !self.should_log(&level) {
90            return;
91        }
92
93        let message = message_fn();
94        let full_message = if self.config.log_sensitive_data {
95            if let Some(sensitive) = sensitive_data {
96                format!("{} [Sensitive data: {}]", message, sensitive)
97            } else {
98                message
99            }
100        } else {
101            message
102        };
103
104        // Choose logging backend based on compile features
105        #[cfg(feature = "tracing")]
106        match level {
107            LogLevel::Error => tracing_error!("{}", full_message),
108            LogLevel::Warn => tracing_warn!("{}", full_message),
109            LogLevel::Info => tracing_info!("{}", full_message),
110            LogLevel::Debug => tracing_debug!("{}", full_message),
111            LogLevel::Trace => tracing_trace!("{}", full_message),
112            LogLevel::Off => {}
113        }
114        
115        #[cfg(not(feature = "tracing"))]
116        match level {
117            LogLevel::Error => error!("{}", full_message),
118            LogLevel::Warn => warn!("{}", full_message),
119            LogLevel::Info => info!("{}", full_message),
120            LogLevel::Debug => debug!("{}", full_message),
121            LogLevel::Trace => trace!("{}", full_message),
122            LogLevel::Off => {}
123        }
124    }
125
126    /// Log API call start
127    pub fn log_api_start(&self, request_id: &str, api_name: &str, params_count: usize, fields_count: usize) {
128        let request_id = request_id.to_string();
129        let api_name = api_name.to_string();
130        self.log_safe(
131            LogLevel::Info,
132            move || format!(
133                "[{}] Starting Tushare API call: {}, params count: {}, fields count: {}",
134                request_id, api_name, params_count, fields_count
135            ),
136            None,
137        );
138    }
139
140    /// Log request details
141    pub fn log_request_details(&self, request_id: &str, api_name: &str, params: &str, fields: &str, token_preview: Option<&str>) {
142        if !self.config.log_requests {
143            return;
144        }
145
146        let request_id = request_id.to_string();
147        let api_name = api_name.to_string();
148        let params = params.to_string();
149        let fields = fields.to_string();
150        self.log_safe(
151            LogLevel::Debug,
152            move || format!(
153                "[{}] API request details - API: {}, params: {}, fields: {}",
154                request_id, api_name, params, fields
155            ),
156            token_preview,
157        );
158    }
159
160    /// Log HTTP request sending
161    pub fn log_http_request(&self, request_id: &str) {
162        let request_id = request_id.to_string();
163        self.log_safe(
164            LogLevel::Debug,
165            move || format!("[{}] Sending HTTP request to Tushare API", request_id),
166            None,
167        );
168    }
169
170    /// Log HTTP request failure
171    pub fn log_http_error(&self, request_id: &str, elapsed: std::time::Duration, error: &str) {
172        let request_id = request_id.to_string();
173        let error = error.to_string();
174        self.log_safe(
175            LogLevel::Error,
176            move || format!(
177                "[{}] HTTP request failed, duration: {:?}, error: {}",
178                request_id, elapsed, error
179            ),
180            None,
181        );
182    }
183
184    /// Log HTTP response reception
185    pub fn log_http_response(&self, request_id: &str, status_code: u16) {
186        let request_id = request_id.to_string();
187        self.log_safe(
188            LogLevel::Debug,
189            move || format!("[{}] Received HTTP response, status code: {}", request_id, status_code),
190            None,
191        );
192    }
193
194    /// Log response reading failure
195    pub fn log_response_read_error(&self, request_id: &str, elapsed: std::time::Duration, error: &str) {
196        let request_id = request_id.to_string();
197        let error = error.to_string();
198        self.log_safe(
199            LogLevel::Error,
200            move || format!(
201                "[{}] Failed to read response content, duration: {:?}, error: {}",
202                request_id, elapsed, error
203            ),
204            None,
205        );
206    }
207
208    /// Log raw response content
209    pub fn log_raw_response(&self, request_id: &str, response_text: &str) {
210        if !self.config.log_responses {
211            return;
212        }
213
214        let request_id = request_id.to_string();
215        let response_text = response_text.to_string();
216        self.log_safe(
217            LogLevel::Trace,
218            move || format!("[{}] Raw response content: {}", request_id, response_text),
219            None,
220        );
221    }
222
223    /// Log JSON parsing failure
224    pub fn log_json_parse_error(&self, request_id: &str, elapsed: std::time::Duration, error: &str, response_text: &str) {
225        let request_id = request_id.to_string();
226        let error = error.to_string();
227        let response_preview = if self.config.log_responses_err {
228            response_text.to_string()
229        } else {
230            "[Hidden]".to_string()
231        };
232
233        self.log_safe(
234            LogLevel::Error,
235            move || format!(
236                "[{}] JSON parsing failed, duration: {:?}, error: {}, response content: {}",
237                request_id, elapsed, error, response_preview
238            ),
239            None,
240        );
241    }
242
243    /// Log API error
244    pub fn log_api_error(&self, request_id: &str, elapsed: std::time::Duration, code: i32, message: &str) {
245        let request_id = request_id.to_string();
246        let message = message.to_string();
247        self.log_safe(
248            LogLevel::Error,
249            move || format!(
250                "[{}] API returned error, duration: {:?}, error code: {}, error message: {}",
251                request_id, elapsed, code, message
252            ),
253            None,
254        );
255    }
256
257    /// Log API call success
258    pub fn log_api_success(&self, request_id: &str, elapsed: std::time::Duration, data_count: usize) {
259        let request_id = request_id.to_string();
260        if self.config.log_performance {
261            self.log_safe(
262                LogLevel::Info,
263                move || format!(
264                    "[{}] API call successful, duration: {:?}, data rows returned: {}",
265                    request_id, elapsed, data_count
266                ),
267                None,
268            );
269        } else {
270            self.log_safe(
271                LogLevel::Info,
272                move || format!("[{}] API call successful", request_id),
273                None,
274            );
275        }
276    }
277
278    /// Log response details
279    pub fn log_response_details(&self, request_id: &str, response_request_id: &str, fields: &str) {
280        if !self.config.log_responses {
281            return;
282        }
283
284        let request_id = request_id.to_string();
285        let response_request_id = response_request_id.to_string();
286        let fields = fields.to_string();
287        self.log_safe(
288            LogLevel::Debug,
289            move || format!(
290                "[{}] Response details - Request ID: {}, fields: {}",
291                request_id, response_request_id, fields
292            ),
293            None,
294        );
295    }
296
297    /// Get reference to log configuration
298    pub fn config(&self) -> &LogConfig {
299        &self.config
300    }
301}