Skip to main content

dm_database_sqllog2db/
error_logger.rs

1/// 错误日志记录器 - 将解析失败的原始数据记录到文件
2use crate::error::{Error, ExportError, Result};
3use log::{debug, info};
4use std::collections::HashMap;
5use std::fs::{File, OpenOptions};
6use std::io::{BufWriter, Write};
7use std::path::{Path, PathBuf};
8
9/// 解析错误记录
10#[derive(Debug)]
11pub struct ParseErrorRecord {
12    /// 错误发生的文件路径
13    pub file_path: String,
14    /// 错误原因/描述
15    pub error_message: String,
16    /// 原始数据内容(导致解析失败的行或片段)
17    pub raw_content: Option<String>,
18    /// 行号(如果适用)
19    pub line_number: Option<usize>,
20}
21
22/// 错误日志记录器
23#[derive(Debug, Default)]
24pub struct ErrorMetrics {
25    /// 总错误数
26    pub total: usize,
27    /// 按分类统计
28    pub by_category: HashMap<String, usize>,
29    /// 解析错误的细分(变体)统计
30    pub parse_variants: HashMap<String, usize>,
31}
32
33impl ErrorMetrics {
34    fn incr_category(&mut self, cat: &str) {
35        *self.by_category.entry(cat.to_string()).or_insert(0) += 1;
36        self.total += 1;
37    }
38
39    fn incr_parse_variant(&mut self, variant: &str) {
40        *self.parse_variants.entry(variant.to_string()).or_insert(0) += 1;
41    }
42}
43
44#[derive(Debug)]
45pub struct ErrorLogger {
46    writer: BufWriter<File>,
47    path: String,
48    count: usize,
49    metrics: ErrorMetrics,
50}
51
52impl ErrorLogger {
53    /// 创建新的错误日志记录器
54    pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
55        let path_ref = path.as_ref();
56        let path_str = path_ref.to_string_lossy().to_string();
57
58        // 创建父目录(如果不存在)
59        if let Some(parent) = path_ref.parent().filter(|p| !p.exists()) {
60            std::fs::create_dir_all(parent).map_err(|e| {
61                Error::Export(ExportError::FileCreateFailed {
62                    path: parent.to_path_buf(),
63                    reason: e.to_string(),
64                })
65            })?;
66        }
67
68        // 打开或创建文件(追加模式)
69        let file = OpenOptions::new()
70            .create(true)
71            .append(true)
72            .open(path_ref)
73            .map_err(|e| {
74                Error::Export(ExportError::FileCreateFailed {
75                    path: path_ref.to_path_buf(),
76                    reason: e.to_string(),
77                })
78            })?;
79
80        info!("Error logger initialized: {path_str}");
81
82        Ok(Self {
83            writer: BufWriter::new(file),
84            path: path_str,
85            count: 0,
86            metrics: ErrorMetrics::default(),
87        })
88    }
89
90    /// 记录一个解析错误
91    pub fn log_error(&mut self, record: &ParseErrorRecord) -> Result<()> {
92        // 将记录以可读文本行写入(file | error | raw | line)
93        let raw = record.raw_content.clone().unwrap_or_default();
94        let line_no = record
95            .line_number
96            .map(|n| n.to_string())
97            .unwrap_or_default();
98        let line = format!(
99            "{} | {} | {} | {}",
100            record.file_path,
101            record.error_message,
102            raw.replace('\n', "\\n"),
103            line_no
104        );
105
106        writeln!(self.writer, "{line}").map_err(|e| {
107            Error::Export(ExportError::FileWriteFailed {
108                path: PathBuf::from(&self.path),
109                reason: e.to_string(),
110            })
111        })?;
112
113        self.count += 1;
114        // 记录分类统计(默认按 parse 分类,若调用方希望其它分类应使用 log_app_error)
115        self.metrics.incr_category("parse");
116        Ok(())
117    }
118
119    /// 记录来自 dm-database-parser-sqllog 的解析错误
120    pub fn log_parse_error(
121        &mut self,
122        file_path: &str,
123        error: &dm_database_parser_sqllog::ParseError,
124    ) -> Result<()> {
125        let record = ParseErrorRecord {
126            file_path: file_path.to_string(),
127            error_message: format!("{error:?}"),
128            raw_content: None, // dm-database-parser-sqllog 的 ParseError 不包含原始内容
129            line_number: None,
130        };
131        // 粗略使用 Debug 字符串作为 variant 标识
132        let variant = format!("{error:?}");
133        self.metrics.incr_parse_variant(&variant);
134        self.log_error(&record)
135    }
136
137    /// 完成错误记录并生成 summary.json
138    /// 刷新缓冲区
139    pub fn flush(&mut self) -> Result<()> {
140        self.writer.flush().map_err(|e| {
141            Error::Export(ExportError::FileWriteFailed {
142                path: PathBuf::from(&self.path),
143                reason: format!("Flush failed: {e}"),
144            })
145        })?;
146        Ok(())
147    }
148
149    /// 完成记录并显示统计信息
150    pub fn finalize(&mut self) -> Result<()> {
151        self.flush()?;
152
153        if self.count > 0 {
154            info!(
155                "Error log written: {} ({} records, categories: {:?})",
156                self.path, self.count, self.metrics.by_category
157            );
158        } else {
159            debug!("No error records to write");
160        }
161        Ok(())
162    }
163}