use serde::Serialize;
use tracing::{info, warn, error, debug};
pub struct StructuredLogger {
job_id: Option<String>,
}
impl Default for StructuredLogger {
fn default() -> Self {
Self::new()
}
}
impl StructuredLogger {
pub fn new() -> Self {
Self { job_id: None }
}
pub fn with_job_id(job_id: &str) -> Self {
Self {
job_id: Some(job_id.to_string()),
}
}
pub fn log_request(&self, event: &RequestEvent) {
info!(
job_id = %self.job_id.as_deref().unwrap_or("-"),
url = %event.url,
method = %event.method,
"Request started"
);
}
pub fn log_response(&self, event: &ResponseEvent) {
if event.status_code >= 400 {
warn!(
job_id = %self.job_id.as_deref().unwrap_or("-"),
url = %event.url,
status = event.status_code,
bytes = event.bytes,
duration_ms = event.duration_ms,
"Response error"
);
} else {
info!(
job_id = %self.job_id.as_deref().unwrap_or("-"),
url = %event.url,
status = event.status_code,
bytes = event.bytes,
duration_ms = event.duration_ms,
"Response received"
);
}
}
pub fn log_error(&self, event: &ErrorEvent) {
error!(
job_id = %self.job_id.as_deref().unwrap_or("-"),
url = %event.url,
error_type = %event.error_type,
message = %event.message,
recoverable = event.recoverable,
"Scrape error"
);
}
pub fn log_parse(&self, event: &ParseEvent) {
debug!(
job_id = %self.job_id.as_deref().unwrap_or("-"),
url = %event.url,
text_length = event.text_length,
links_count = event.links_count,
images_count = event.images_count,
duration_ms = event.duration_ms,
"Parsing completed"
);
}
pub fn log_retry(&self, url: &str, attempt: u32, delay_ms: u64) {
warn!(
job_id = %self.job_id.as_deref().unwrap_or("-"),
url = %url,
attempt = attempt,
delay_ms = delay_ms,
"Retrying request"
);
}
pub fn log_rate_limited(&self, url: &str, pause_ms: u64) {
warn!(
job_id = %self.job_id.as_deref().unwrap_or("-"),
url = %url,
pause_ms = pause_ms,
"Rate limited, pausing"
);
}
pub fn log_request_url(&self, url: &url::Url, method: &str) {
self.log_request(&RequestEvent {
url: url.to_string(),
method: method.to_string(),
});
}
pub fn log_response_parts(&self, url: &url::Url, status_code: u16, bytes: u64, duration_ms: u64) {
self.log_response(&ResponseEvent {
url: url.to_string(),
status_code,
bytes,
duration_ms,
});
}
pub fn log_error_parts(&self, url: &url::Url, message: &str, recoverable: bool) {
self.log_error(&ErrorEvent {
url: url.to_string(),
error_type: "scrape_error".to_string(),
message: message.to_string(),
recoverable,
});
}
pub fn log_info(&self, message: &str) {
info!(
job_id = %self.job_id.as_deref().unwrap_or("-"),
"{}", message
);
}
pub fn log_warn(&self, message: &str) {
warn!(
job_id = %self.job_id.as_deref().unwrap_or("-"),
"{}", message
);
}
pub fn log_debug(&self, message: &str) {
debug!(
job_id = %self.job_id.as_deref().unwrap_or("-"),
"{}", message
);
}
}
#[derive(Debug, Serialize)]
pub struct RequestEvent {
pub url: String,
pub method: String,
}
#[derive(Debug, Serialize)]
pub struct ResponseEvent {
pub url: String,
pub status_code: u16,
pub bytes: u64,
pub duration_ms: u64,
}
#[derive(Debug, Serialize)]
pub struct ErrorEvent {
pub url: String,
pub error_type: String,
pub message: String,
pub recoverable: bool,
}
#[derive(Debug, Serialize)]
pub struct ParseEvent {
pub url: String,
pub text_length: usize,
pub links_count: usize,
pub images_count: usize,
pub duration_ms: u64,
}