1use 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#[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#[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 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 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 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 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 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 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 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 #[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 #[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
140pub struct LogParserBuilder {
142 path: PathBuf,
143 encoding: FileEncodingHint,
144 _format: PhantomData<LogEvent>,
145}
146
147impl LogParserBuilder {
148 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 pub fn encoding_hint(mut self, hint: FileEncodingHint) -> Self {
159 self.encoding = hint;
160 self
161 }
162
163 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#[derive(Debug)]
176pub struct LogParser {
177 path: PathBuf,
178 encoding: FileEncodingHint,
179 format: LogFormatKind,
180}
181
182impl LogParser {
183 pub fn format(&self) -> LogFormatKind {
185 self.format
186 }
187
188 pub fn path(&self) -> &Path {
190 &self.path
191 }
192
193 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
229pub struct LogIterator {
231 inner: InnerIterator,
232}
233
234impl LogIterator {
235 pub fn skip_errors(self) -> impl Iterator<Item = LogEvent> {
237 self.filter_map(Result::ok)
238 }
239
240 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 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 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 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
302pub 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
310pub fn parse_bytes(line: &[u8]) -> Result<LogEvent, ParseError> {
312 parse_bytes_with_encoding(line, FileEncodingHint::Auto)
313}
314
315pub 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}