Skip to main content

dm_database_driver_log/
unified.rs

1//! 面向内置驱动格式的统一 API。
2
3use std::fs::File;
4use std::io::{BufRead, BufReader};
5use std::marker::PhantomData;
6use std::path::{Path, PathBuf};
7
8use crate::core::LogRecord;
9use crate::encoding::{self, FileEncodingHint};
10use crate::error::ParseError;
11
12/// 内置日志格式的种类。
13#[derive(Copy, Clone, Debug, PartialEq, Eq)]
14pub enum LogFormatKind {
15    #[cfg(feature = "jdbc")]
16    Jdbc,
17    #[cfg(feature = "dm-provider")]
18    DmProvider,
19}
20
21/// JDBC 或 DM Provider 日志事件。
22#[derive(Debug, Clone, PartialEq)]
23pub enum LogEvent {
24    #[cfg(feature = "jdbc")]
25    Jdbc(crate::formats::jdbc::DriverLogEvent),
26    #[cfg(feature = "dm-provider")]
27    DmProvider(crate::formats::dm_provider::DmProviderEvent),
28}
29
30impl LogEvent {
31    /// 返回事件所属的日志格式。
32    pub fn format(&self) -> LogFormatKind {
33        match self {
34            #[cfg(feature = "jdbc")]
35            Self::Jdbc(_) => LogFormatKind::Jdbc,
36            #[cfg(feature = "dm-provider")]
37            Self::DmProvider(_) => LogFormatKind::DmProvider,
38        }
39    }
40
41    /// 返回驱动方法或 Provider 方法名。
42    pub fn method(&self) -> &str {
43        match self {
44            #[cfg(feature = "jdbc")]
45            Self::Jdbc(event) => &event.method,
46            #[cfg(feature = "dm-provider")]
47            Self::DmProvider(event) => &event.method,
48        }
49    }
50
51    /// 返回统一分类。
52    pub fn category(&self) -> &str {
53        match self {
54            #[cfg(feature = "jdbc")]
55            Self::Jdbc(event) => event.category,
56            #[cfg(feature = "dm-provider")]
57            Self::DmProvider(event) => event.category,
58        }
59    }
60
61    /// 返回统一换算后的耗时(毫秒)。
62    pub fn used_time_ms(&self) -> Option<f64> {
63        match self {
64            #[cfg(feature = "jdbc")]
65            Self::Jdbc(event) => event.used_time_ms,
66            #[cfg(feature = "dm-provider")]
67            Self::DmProvider(event) => event.used_time_ms,
68        }
69    }
70
71    /// 返回执行编号。
72    pub fn exec_id(&self) -> Option<i64> {
73        match self {
74            #[cfg(feature = "jdbc")]
75            Self::Jdbc(event) => event.exec_id,
76            #[cfg(feature = "dm-provider")]
77            Self::DmProvider(event) => event.exec_id,
78        }
79    }
80
81    /// 返回物理起始行号;单条解析返回 0。
82    pub fn line_number(&self) -> u64 {
83        match self {
84            #[cfg(feature = "jdbc")]
85            Self::Jdbc(event) => event.line_number,
86            #[cfg(feature = "dm-provider")]
87            Self::DmProvider(event) => event.line_number,
88        }
89    }
90
91    /// 返回记录原文。
92    pub fn raw(&self) -> &str {
93        match self {
94            #[cfg(feature = "jdbc")]
95            Self::Jdbc(event) => &event.raw,
96            #[cfg(feature = "dm-provider")]
97            Self::DmProvider(event) => &event.raw,
98        }
99    }
100
101    /// 如果事件来自 JDBC,返回 JDBC 专属字段。
102    #[cfg(feature = "jdbc")]
103    pub fn as_jdbc(&self) -> Option<&crate::formats::jdbc::DriverLogEvent> {
104        match self {
105            Self::Jdbc(event) => Some(event),
106            #[cfg(feature = "dm-provider")]
107            Self::DmProvider(_) => None,
108        }
109    }
110
111    /// 如果事件来自 DM Provider,返回 Provider 专属字段。
112    #[cfg(feature = "dm-provider")]
113    pub fn as_dm_provider(&self) -> Option<&crate::formats::dm_provider::DmProviderEvent> {
114        match self {
115            #[cfg(feature = "jdbc")]
116            Self::Jdbc(_) => None,
117            Self::DmProvider(event) => Some(event),
118        }
119    }
120}
121
122impl LogRecord for LogEvent {
123    fn method(&self) -> &str {
124        self.method()
125    }
126
127    fn category(&self) -> &str {
128        self.category()
129    }
130
131    fn used_time_ms(&self) -> Option<f64> {
132        self.used_time_ms()
133    }
134
135    fn exec_id(&self) -> Option<i64> {
136        self.exec_id()
137    }
138}
139
140/// 自动识别 JDBC 或 DM Provider 格式的文件解析器构建器。
141pub struct LogParserBuilder {
142    path: PathBuf,
143    encoding: FileEncodingHint,
144    _format: PhantomData<LogEvent>,
145}
146
147impl LogParserBuilder {
148    /// 创建一个统一格式解析器构建器。
149    pub fn new<P: AsRef<Path>>(path: P) -> Self {
150        Self {
151            path: path.as_ref().to_path_buf(),
152            encoding: FileEncodingHint::Auto,
153            _format: PhantomData,
154        }
155    }
156
157    /// 设置输入文件编码;默认自动识别 UTF-8 或 GB18030。
158    pub fn encoding_hint(mut self, hint: FileEncodingHint) -> Self {
159        self.encoding = hint;
160        self
161    }
162
163    /// 打开文件并根据第一条记录选择内置格式。
164    pub fn build(self) -> Result<LogParser, ParseError> {
165        let format = detect_path_format(&self.path, self.encoding)?;
166        Ok(LogParser {
167            path: self.path,
168            encoding: self.encoding,
169            format,
170        })
171    }
172}
173
174/// 统一的内置驱动日志解析器。
175#[derive(Debug)]
176pub struct LogParser {
177    path: PathBuf,
178    encoding: FileEncodingHint,
179    format: LogFormatKind,
180}
181
182impl LogParser {
183    /// 返回自动识别出的日志格式。
184    pub fn format(&self) -> LogFormatKind {
185        self.format
186    }
187
188    /// 返回输入文件路径。
189    pub fn path(&self) -> &Path {
190        &self.path
191    }
192
193    /// 创建一次新的流式迭代器。
194    pub fn iter(&self) -> Result<LogIterator, ParseError> {
195        let inner =
196            match self.format {
197                #[cfg(feature = "jdbc")]
198                LogFormatKind::Jdbc => InnerIterator::Jdbc(
199                    crate::core::LogParserBuilder::<crate::formats::jdbc::JdbcFormat>::new(
200                        &self.path,
201                    )
202                    .encoding_hint(self.encoding)
203                    .build()?
204                    .iter()?,
205                ),
206                #[cfg(feature = "dm-provider")]
207                LogFormatKind::DmProvider => {
208                    InnerIterator::DmProvider(
209                        crate::core::LogParserBuilder::<
210                            crate::formats::dm_provider::DmProviderFormat,
211                        >::new(&self.path)
212                        .encoding_hint(self.encoding)
213                        .build()?
214                        .iter()?,
215                    )
216                }
217            };
218        Ok(LogIterator { inner })
219    }
220}
221
222enum InnerIterator {
223    #[cfg(feature = "jdbc")]
224    Jdbc(crate::core::LogIterator<crate::formats::jdbc::JdbcFormat>),
225    #[cfg(feature = "dm-provider")]
226    DmProvider(crate::core::LogIterator<crate::formats::dm_provider::DmProviderFormat>),
227}
228
229/// 统一事件迭代器。
230pub struct LogIterator {
231    inner: InnerIterator,
232}
233
234impl LogIterator {
235    /// 丢弃格式错误的记录,只返回成功解析的事件。
236    pub fn skip_errors(self) -> impl Iterator<Item = LogEvent> {
237        self.filter_map(Result::ok)
238    }
239
240    /// 按方法名筛选事件。
241    pub fn filter_by_method(
242        self,
243        method: &str,
244    ) -> impl Iterator<Item = Result<LogEvent, ParseError>> + '_ {
245        self.filter(move |result| match result {
246            Ok(event) => event.method() == method,
247            Err(_) => true,
248        })
249    }
250
251    /// 按分类筛选事件。
252    pub fn filter_by_category(
253        self,
254        category: &str,
255    ) -> impl Iterator<Item = Result<LogEvent, ParseError>> + '_ {
256        self.filter(move |result| match result {
257            Ok(event) => event.category() == category,
258            Err(_) => true,
259        })
260    }
261
262    /// 按最小耗时(毫秒)筛选事件。
263    pub fn filter_by_used_time(
264        self,
265        min_ms: f64,
266    ) -> impl Iterator<Item = Result<LogEvent, ParseError>> {
267        self.filter(move |result| match result {
268            Ok(event) => event.used_time_ms().is_some_and(|value| value >= min_ms),
269            Err(_) => true,
270        })
271    }
272
273    /// 按执行编号筛选事件。
274    pub fn filter_by_exec_id(
275        self,
276        exec_id: i64,
277    ) -> impl Iterator<Item = Result<LogEvent, ParseError>> {
278        self.filter(move |result| match result {
279            Ok(event) => event.exec_id() == Some(exec_id),
280            Err(_) => true,
281        })
282    }
283}
284
285impl Iterator for LogIterator {
286    type Item = Result<LogEvent, ParseError>;
287
288    fn next(&mut self) -> Option<Self::Item> {
289        match &mut self.inner {
290            #[cfg(feature = "jdbc")]
291            InnerIterator::Jdbc(iterator) => {
292                iterator.next().map(|result| result.map(LogEvent::Jdbc))
293            }
294            #[cfg(feature = "dm-provider")]
295            InnerIterator::DmProvider(iterator) => iterator
296                .next()
297                .map(|result| result.map(LogEvent::DmProvider)),
298        }
299    }
300}
301
302/// 自动识别并解析一条 JDBC 或 DM Provider 记录。
303pub fn parse_line(line: &str) -> Result<LogEvent, ParseError> {
304    let format = detect_line_format(line)
305        .or_else(default_format)
306        .ok_or_else(|| ParseError::invalid(0, "unknown driver log format").with_context(line, 0))?;
307    parse_line_with_format(line, format)
308}
309
310/// 自动识别并解析一条字节记录。
311pub fn parse_bytes(line: &[u8]) -> Result<LogEvent, ParseError> {
312    parse_bytes_with_encoding(line, FileEncodingHint::Auto)
313}
314
315/// 按指定编码自动识别并解析一条字节记录。
316pub fn parse_bytes_with_encoding(
317    line: &[u8],
318    encoding: FileEncodingHint,
319) -> Result<LogEvent, ParseError> {
320    parse_line(&encoding::decode(line, encoding))
321}
322
323fn parse_line_with_format(line: &str, format: LogFormatKind) -> Result<LogEvent, ParseError> {
324    match format {
325        #[cfg(feature = "jdbc")]
326        LogFormatKind::Jdbc => {
327            crate::formats::jdbc::parse(line).map(|event| LogEvent::Jdbc(event.to_owned(line, 0)))
328        }
329        #[cfg(feature = "dm-provider")]
330        LogFormatKind::DmProvider => {
331            crate::formats::dm_provider::parse_line(line).map(LogEvent::DmProvider)
332        }
333    }
334}
335
336fn detect_path_format(
337    path: &Path,
338    encoding: FileEncodingHint,
339) -> Result<LogFormatKind, ParseError> {
340    let file = File::open(path)
341        .map_err(|error| ParseError::IoError(format!("{}: {error}", path.display())))?;
342    let mut reader = BufReader::new(file);
343    let mut bytes = Vec::new();
344    let mut line_number = 0;
345
346    loop {
347        bytes.clear();
348        let bytes_read = reader
349            .read_until(b'\n', &mut bytes)
350            .map_err(|error| ParseError::IoError(error.to_string()))?;
351        if bytes_read == 0 {
352            return default_format()
353                .ok_or_else(|| ParseError::invalid(0, "no built-in driver log format is enabled"));
354        }
355        line_number += 1;
356        while matches!(bytes.last(), Some(b'\n' | b'\r')) {
357            bytes.pop();
358        }
359        let line = encoding::decode(&bytes, encoding);
360        if line.trim().is_empty() {
361            continue;
362        }
363        return detect_line_format(&line).ok_or_else(|| {
364            ParseError::invalid(0, "unknown driver log format").with_context(&line, line_number)
365        });
366    }
367}
368
369fn detect_line_format(line: &str) -> Option<LogFormatKind> {
370    #[cfg(all(feature = "jdbc", feature = "dm-provider"))]
371    {
372        let header_end = line.find(']')?;
373        let suffix = &line[header_end + 1..];
374        if suffix.contains(" - [") {
375            return Some(LogFormatKind::Jdbc);
376        }
377        if suffix.contains(" (") {
378            return Some(LogFormatKind::DmProvider);
379        }
380        None
381    }
382
383    #[cfg(all(feature = "jdbc", not(feature = "dm-provider")))]
384    {
385        let _ = line;
386        Some(LogFormatKind::Jdbc)
387    }
388
389    #[cfg(all(feature = "dm-provider", not(feature = "jdbc")))]
390    {
391        let _ = line;
392        Some(LogFormatKind::DmProvider)
393    }
394}
395
396fn default_format() -> Option<LogFormatKind> {
397    #[cfg(feature = "jdbc")]
398    {
399        Some(LogFormatKind::Jdbc)
400    }
401    #[cfg(all(not(feature = "jdbc"), feature = "dm-provider"))]
402    {
403        Some(LogFormatKind::DmProvider)
404    }
405    #[cfg(not(any(feature = "jdbc", feature = "dm-provider")))]
406    {
407        None
408    }
409}