use log::{debug, error, info, trace, warn};
#[cfg(feature = "tracing")]
use tracing::{
debug as tracing_debug, error as tracing_error, info as tracing_info, trace as tracing_trace,
warn as tracing_warn,
};
#[derive(Debug, Clone, PartialEq)]
pub enum LogLevel {
Off,
Error,
Warn,
Info,
Debug,
Trace,
}
impl Default for LogLevel {
fn default() -> Self {
LogLevel::Info
}
}
#[derive(Debug, Clone)]
pub struct LogConfig {
pub level: LogLevel,
pub log_requests: bool,
pub log_responses: bool,
pub log_responses_err: bool,
pub log_sensitive_data: bool,
pub log_performance: bool,
}
impl Default for LogConfig {
fn default() -> Self {
Self {
level: LogLevel::Info,
log_requests: true,
log_responses: false, log_responses_err: true,
log_sensitive_data: false, log_performance: true,
}
}
}
#[derive(Debug)]
pub struct Logger {
config: LogConfig,
}
impl Logger {
pub fn new(config: LogConfig) -> Self {
Self { config }
}
pub fn should_log(&self, level: &LogLevel) -> bool {
use LogLevel::*;
match (&self.config.level, level) {
(Off, _) => false,
(Error, Error) => true,
(Warn, Error | Warn) => true,
(Info, Error | Warn | Info) => true,
(Debug, Error | Warn | Info | Debug) => true,
(Trace, _) => true,
_ => false,
}
}
pub fn log_safe<F>(&self, level: LogLevel, message_fn: F, sensitive_data: Option<&str>)
where
F: FnOnce() -> String,
{
if !self.should_log(&level) {
return;
}
let message = message_fn();
let full_message = if self.config.log_sensitive_data {
if let Some(sensitive) = sensitive_data {
format!("{} [Sensitive data: {}]", message, sensitive)
} else {
message
}
} else {
message
};
#[cfg(feature = "tracing")]
match level {
LogLevel::Error => tracing_error!("{}", full_message),
LogLevel::Warn => tracing_warn!("{}", full_message),
LogLevel::Info => tracing_info!("{}", full_message),
LogLevel::Debug => tracing_debug!("{}", full_message),
LogLevel::Trace => tracing_trace!("{}", full_message),
LogLevel::Off => {}
}
#[cfg(not(feature = "tracing"))]
match level {
LogLevel::Error => error!("{}", full_message),
LogLevel::Warn => warn!("{}", full_message),
LogLevel::Info => info!("{}", full_message),
LogLevel::Debug => debug!("{}", full_message),
LogLevel::Trace => trace!("{}", full_message),
LogLevel::Off => {}
}
}
pub fn log_api_start(
&self,
request_id: &str,
api_name: &str,
params_count: usize,
fields_count: usize,
) {
let request_id = request_id.to_string();
let api_name = api_name.to_string();
self.log_safe(
LogLevel::Info,
move || {
format!(
"[{}] Starting Tushare API call: {}, params count: {}, fields count: {}",
request_id, api_name, params_count, fields_count
)
},
None,
);
}
pub fn log_request_details(
&self,
request_id: &str,
api_name: &str,
params: &str,
fields: &str,
token_preview: Option<&str>,
) {
if !self.config.log_requests {
return;
}
let request_id = request_id.to_string();
let api_name = api_name.to_string();
let params = params.to_string();
let fields = fields.to_string();
self.log_safe(
LogLevel::Debug,
move || {
format!(
"[{}] API request details - API: {}, params: {}, fields: {}",
request_id, api_name, params, fields
)
},
token_preview,
);
}
pub fn log_http_request(&self, request_id: &str) {
let request_id = request_id.to_string();
self.log_safe(
LogLevel::Debug,
move || format!("[{}] Sending HTTP request to Tushare API", request_id),
None,
);
}
pub fn log_http_error(&self, request_id: &str, elapsed: std::time::Duration, error: &str) {
let request_id = request_id.to_string();
let error = error.to_string();
self.log_safe(
LogLevel::Error,
move || {
format!(
"[{}] HTTP request failed, duration: {:?}, error: {}",
request_id, elapsed, error
)
},
None,
);
}
pub fn log_http_response(&self, request_id: &str, status_code: u16) {
let request_id = request_id.to_string();
self.log_safe(
LogLevel::Debug,
move || {
format!(
"[{}] Received HTTP response, status code: {}",
request_id, status_code
)
},
None,
);
}
pub fn log_response_read_error(
&self,
request_id: &str,
elapsed: std::time::Duration,
error: &str,
) {
let request_id = request_id.to_string();
let error = error.to_string();
self.log_safe(
LogLevel::Error,
move || {
format!(
"[{}] Failed to read response content, duration: {:?}, error: {}",
request_id, elapsed, error
)
},
None,
);
}
pub fn log_raw_response(&self, request_id: &str, response_text: &str) {
if !self.config.log_responses {
return;
}
let request_id = request_id.to_string();
let response_text = response_text.to_string();
self.log_safe(
LogLevel::Trace,
move || {
format!(
"[{}] Raw response content length = {}, body = {}",
request_id,
response_text.len(),
response_text
)
},
None,
);
}
pub fn log_json_parse_error(
&self,
request_id: &str,
elapsed: std::time::Duration,
error: &str,
response_text: &str,
) {
let request_id = request_id.to_string();
let error = error.to_string();
let response_preview = if self.config.log_responses_err {
response_text.to_string()
} else {
"[Hidden]".to_string()
};
self.log_safe(
LogLevel::Error,
move || format!(
"[{}] JSON parsing failed, duration: {:?}, error: {}, response content length: {}, response content: {}",
request_id, elapsed, error, response_preview.len(), response_preview
),
None,
);
}
pub fn log_api_error(
&self,
request_id: &str,
elapsed: std::time::Duration,
code: i32,
message: &str,
) {
let request_id = request_id.to_string();
let message = message.to_string();
self.log_safe(
LogLevel::Error,
move || {
format!(
"[{}] API returned error, duration: {:?}, error code: {}, error message: {}",
request_id, elapsed, code, message
)
},
None,
);
}
pub fn log_api_success(
&self,
request_id: &str,
elapsed: std::time::Duration,
data_count: usize,
) {
let request_id = request_id.to_string();
if self.config.log_performance {
self.log_safe(
LogLevel::Info,
move || {
format!(
"[{}] API call successful, duration: {:?}, data rows returned: {}",
request_id, elapsed, data_count
)
},
None,
);
} else {
self.log_safe(
LogLevel::Info,
move || format!("[{}] API call successful", request_id),
None,
);
}
}
pub fn log_response_details(&self, request_id: &str, response_request_id: &str, fields: &str) {
if !self.config.log_responses {
return;
}
let request_id = request_id.to_string();
let response_request_id = response_request_id.to_string();
let fields = fields.to_string();
self.log_safe(
LogLevel::Debug,
move || {
format!(
"[{}] Response details - Request ID: {}, fields: {}",
request_id, response_request_id, fields
)
},
None,
);
}
pub fn config(&self) -> &LogConfig {
&self.config
}
}