Skip to main content

tushare_api/
logging.rs

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