1use crate::core::{LogFormat, LogRecord, RecordFraming};
19use crate::error::ParseError;
20
21#[derive(Debug, Clone, Default)]
23pub struct Event<'a> {
24 pub level: &'a str,
25 pub event_time_text: &'a str,
26 pub event_time_ms: Option<i64>,
28 pub tid: Option<i32>,
29 pub thread: &'a str,
30 pub conn_id: Option<i32>,
31 pub pstmt_id: Option<i32>,
32 pub rs_id: Option<i32>,
33 pub result_set_id: Option<i32>,
35 pub handle_id: Option<i32>,
36 pub session_id_hex: Option<&'a str>,
37 pub method: &'a str,
38 pub arg_types: &'a str,
39 pub return_value: Option<&'a str>,
40 pub params: Option<&'a str>,
41 pub param_index: Option<i32>,
42 pub param_value: Option<&'a str>,
43 pub column_name: Option<&'a str>,
44 pub used_time_text: Option<&'a str>,
45 pub used_time_ms: Option<f64>,
46 pub exec_id: Option<i64>,
47 pub cmd: Option<&'a str>,
48 pub category: &'static str,
49}
50
51impl<'a> Event<'a> {
52 pub fn to_owned(&self, raw: &str, line_number: u64) -> DriverLogEvent {
54 DriverLogEvent {
55 line_number,
56 raw: raw.to_owned(),
57 level: self.level.to_owned(),
58 event_time_text: self.event_time_text.to_owned(),
59 event_time_ms: self.event_time_ms,
60 tid: self.tid,
61 thread: self.thread.to_owned(),
62 conn_id: self.conn_id,
63 pstmt_id: self.pstmt_id,
64 rs_id: self.rs_id,
65 result_set_id: self.result_set_id,
66 handle_id: self.handle_id,
67 session_id_hex: self.session_id_hex.map(str::to_owned),
68 method: self.method.to_owned(),
69 arg_types: self.arg_types.to_owned(),
70 return_value: self.return_value.map(str::to_owned),
71 params: self.params.map(str::to_owned),
72 param_index: self.param_index,
73 param_value: self.param_value.map(str::to_owned),
74 column_name: self.column_name.map(str::to_owned),
75 used_time_text: self.used_time_text.map(str::to_owned),
76 used_time_ms: self.used_time_ms,
77 exec_id: self.exec_id,
78 cmd: self.cmd.map(str::to_owned),
79 category: self.category,
80 }
81 }
82}
83
84#[derive(Debug, Clone, PartialEq)]
89pub struct DriverLogEvent {
90 pub line_number: u64,
91 pub raw: String,
92 pub level: String,
93 pub event_time_text: String,
94 pub event_time_ms: Option<i64>,
95 pub tid: Option<i32>,
96 pub thread: String,
97 pub conn_id: Option<i32>,
98 pub pstmt_id: Option<i32>,
99 pub rs_id: Option<i32>,
100 pub result_set_id: Option<i32>,
101 pub handle_id: Option<i32>,
102 pub session_id_hex: Option<String>,
103 pub method: String,
104 pub arg_types: String,
105 pub return_value: Option<String>,
106 pub params: Option<String>,
107 pub param_index: Option<i32>,
108 pub param_value: Option<String>,
109 pub column_name: Option<String>,
110 pub used_time_text: Option<String>,
111 pub used_time_ms: Option<f64>,
112 pub exec_id: Option<i64>,
113 pub cmd: Option<String>,
114 pub category: &'static str,
115}
116
117#[derive(Copy, Clone, Debug, Default)]
119pub struct JdbcFormat;
120
121impl LogRecord for DriverLogEvent {
122 fn method(&self) -> &str {
123 &self.method
124 }
125
126 fn category(&self) -> &str {
127 self.category
128 }
129
130 fn used_time_ms(&self) -> Option<f64> {
131 self.used_time_ms
132 }
133
134 fn exec_id(&self) -> Option<i64> {
135 self.exec_id
136 }
137}
138
139impl LogFormat for JdbcFormat {
140 type Event = DriverLogEvent;
141
142 const FRAMING: RecordFraming = RecordFraming::Line;
143
144 fn is_record_start(line: &str) -> bool {
145 line.starts_with('[')
146 }
147
148 fn parse_record(record: &str, line_number: u64) -> Result<Self::Event, ParseError> {
149 parse(record).map(|event| event.to_owned(record, line_number))
150 }
151}
152
153const MARK_PARAMS: &[u8] = b"[PARAMS]: ";
154const MARK_USED_TIME: &[u8] = b"[USED TIME]: ";
155const MARK_EXEC_ID: &[u8] = b"[EXEC_ID]: ";
156const MARK_CMD: &[u8] = b"CMD_";
157const MARK_SESSION: &[u8] = b"sessionID-";
158
159pub fn parse(line: &str) -> Result<Event<'_>, ParseError> {
162 parse_inner(line).map_err(|e| e.with_context(line, 0))
163}
164
165fn parse_inner(line: &str) -> Result<Event<'_>, ParseError> {
166 let b = line.as_bytes();
167 if b.is_empty() {
168 return Err(ParseError::invalid(0, "empty line"));
169 }
170 if b[0] != b'[' {
171 return Err(ParseError::invalid(0, "line does not start with '['"));
172 }
173
174 let head_end = find(b, b"]", 1).ok_or(ParseError::invalid(1, "unterminated level header"))?;
176 let head = &b[1..head_end];
177 let sep = find(head, b" - ", 0).ok_or(ParseError::invalid(1, "missing ' - ' in header"))?;
178 let level = trim(&head[..sep]);
179 let ts = trim(&head[sep + 3..]);
180
181 let mut i = skip_spaces(b, head_end + 1);
183 expect(b, i, b"tid:")?;
184 i += 4;
185 let (tid, ni) = take_i64(b, i).ok_or(ParseError::invalid(i, "bad tid"))?;
186 i = skip_spaces(b, ni);
187 expect(b, i, b"-")?;
188 i = skip_spaces(b, i + 1);
189 expect(b, i, b"[")?;
190 let thread_end = find(b, b"]", i + 1).ok_or(ParseError::invalid(i, "unterminated thread"))?;
191 let thread = trim(&b[i + 1..thread_end]);
192 i = skip_spaces(b, thread_end + 1);
193 expect(b, i, b"{")?;
194 let ids_end = find(b, b"}", i + 1).ok_or(ParseError::invalid(i, "unterminated id block"))?;
195 let ids = trim(&b[i + 1..ids_end]);
196 let rest = trim_start(&b[ids_end + 1..]);
197 let rb = rest.as_bytes();
198
199 let popen = find(rb, b"(", 0).ok_or(ParseError::invalid(0, "missing '(' after method"))?;
201 let method = trim(&rb[..popen]);
202 let pclose =
203 find(rb, b")", popen + 1).ok_or(ParseError::invalid(popen, "unterminated method args"))?;
204 let arg_types = trim(&rb[popen + 1..pclose]);
205 let after = pclose + 1;
206
207 let m_params = find_marker(rb, MARK_PARAMS, after);
209 let m_time = find_marker(rb, MARK_USED_TIME, after);
210 let m_exec = find_marker(rb, MARK_EXEC_ID, after);
211 let m_cmd = find_marker(rb, MARK_CMD, after);
212 let first_tail = [m_params, m_time, m_exec, m_cmd]
213 .into_iter()
214 .flatten()
215 .min();
216
217 let mut ev = Event {
218 level,
219 event_time_text: ts,
220 event_time_ms: epoch_millis(ts),
221 tid: i32::try_from(tid).ok(),
222 thread,
223 method,
224 arg_types,
225 category: category_of(method),
226 ..Default::default()
227 };
228
229 if rb.get(after) == Some(&b':') {
231 let end = first_tail.unwrap_or(rb.len());
232 if end > after + 1 {
233 let raw = trim_semi(trim(&rb[after + 1..end]));
234 if !raw.is_empty() {
235 ev.return_value = Some(raw);
236 }
237 }
238 }
239
240 if let Some(p) = m_params {
242 let start = p + MARK_PARAMS.len();
243 let end = [m_time, m_exec, m_cmd]
244 .into_iter()
245 .flatten()
246 .filter(|x| *x > start)
247 .min()
248 .unwrap_or(rb.len());
249 if end > start {
250 let raw = trim_semi(trim(&rb[start..end]));
251 if !raw.is_empty() {
252 ev.params = Some(raw);
253 }
254 }
255 }
256
257 if let Some(p) = m_time {
259 let start = p + MARK_USED_TIME.len();
260 if let Some(m) = find(rb, b"ms", start) {
261 let num = trim(&rb[start..m]);
262 if !num.is_empty() {
263 ev.used_time_text = Some(num);
264 ev.used_time_ms = num.parse::<f64>().ok();
265 }
266 }
267 }
268
269 if let Some(p) = m_exec
271 && let Some((v, _)) = take_i64(rb, p + MARK_EXEC_ID.len())
272 {
273 ev.exec_id = Some(v);
274 }
275
276 if let Some(p) = m_cmd {
278 let seg = &rb[p..];
279 let mut e = 0;
280 while e < seg.len()
281 && (seg[e].is_ascii_uppercase() || seg[e].is_ascii_digit() || seg[e] == b'_')
282 {
283 e += 1;
284 }
285 if e > MARK_CMD.len() {
286 ev.cmd = rest.get(p..p + e);
287 }
288 }
289
290 for token in ids.split(',') {
292 let t = trim(token.as_bytes());
293 let tb = t.as_bytes();
294 if let Some(v) = strip_digits(tb, b"conn-") {
295 ev.conn_id = i32::try_from(v).ok();
296 } else if let Some(v) = strip_digits(tb, b"pstmt-") {
297 ev.pstmt_id = i32::try_from(v).ok();
298 } else if let Some(v) = strip_digits(tb, b"rs-") {
299 ev.rs_id = i32::try_from(v).ok();
300 } else if let Some(v) = strip_digits(tb, b"handle-") {
301 ev.handle_id = i32::try_from(v).ok();
302 }
303 }
304
305 if let Some(rv) = ev.return_value {
307 let vrb = rv.as_bytes();
308 if let Some(v) = digits_after(vrb, b"rs-") {
309 ev.result_set_id = i32::try_from(v).ok();
311 } else if let Some(v) = digits_after(vrb, b"pstmt-") {
312 ev.pstmt_id = i32::try_from(v).ok();
314 if let Some(h) = find(vrb, b"handle-", 0).and_then(|i| take_i64(vrb, i + 7)) {
315 ev.handle_id = i32::try_from(h.0).ok();
316 }
317 if let Some(s) = find(vrb, MARK_SESSION, 0) {
318 let start = s + MARK_SESSION.len();
319 let seg = &vrb[start..];
320 let mut e = 0;
321 while e < seg.len()
322 && (seg[e].is_ascii_hexdigit() || seg[e] == b'x' || seg[e] == b'X')
323 {
324 e += 1;
325 }
326 ev.session_id_hex = rv.get(start..start + e);
327 }
328 }
329 }
330 ev.result_set_id = ev.result_set_id.or(ev.rs_id);
331
332 if ev.category == "bind"
334 && let Some(p) = ev.params
335 {
336 let pb = p.as_bytes();
337 if let Some((idx, ni)) = take_i64(pb, 0) {
338 ev.param_index = i32::try_from(idx).ok();
339 let mut value_start = ni.min(pb.len());
340 while value_start < pb.len()
341 && (pb[value_start] == b',' || pb[value_start].is_ascii_whitespace())
342 {
343 value_start += 1;
344 }
345 let v = trim(&pb[value_start..]);
346 if !v.is_empty() {
347 ev.param_value = Some(v);
348 }
349 }
350 }
351
352 if ev.method.starts_with("get")
354 && let Some(p) = ev.params
355 && p.len() >= 2
356 && p.starts_with('"')
357 && p.ends_with('"')
358 {
359 ev.column_name = Some(&p[1..p.len() - 1]);
360 }
361
362 Ok(ev)
363}
364
365pub fn category_of(method: &str) -> &'static str {
367 if method.is_empty() {
368 return "unparsed";
369 }
370 if method == "prepareStatement" {
371 return "prepare";
372 }
373 if method.starts_with("set") {
374 return "bind";
375 }
376 if method.starts_with("execute") {
377 return "execute";
378 }
379 if method == "next" {
380 return "fetch_next";
381 }
382 if method == "wasNull" {
383 return "null_check";
384 }
385 if method.starts_with("clear") {
386 return "clear";
387 }
388 if method == "close" {
389 return "close";
390 }
391 if method == "access" {
392 return "driver_access";
393 }
394 if method.starts_with("get") {
395 return match method {
396 "getString" | "getTimestamp" | "getLong" | "getInt" | "getShort" | "getDouble"
397 | "getFloat" | "getBoolean" | "getBytes" | "getBigDecimal" | "getDate" | "getTime"
398 | "getObject" | "getBlob" | "getClob" | "getNString" | "getNClob" | "getURL"
399 | "getAsciiStream" | "getBinaryStream" | "getCharacterStream" => "read_value",
400 _ => "read_meta",
401 };
402 }
403 "other"
404}
405
406pub fn find(hay: &[u8], needle: &[u8], from: usize) -> Option<usize> {
411 if needle.is_empty() || hay.len() < needle.len() || from > hay.len() {
412 return None;
413 }
414 let first = needle[0];
415 let last_start = hay.len() - needle.len();
416 let mut i = from;
417 while i <= last_start {
418 if hay[i] == first && &hay[i..i + needle.len()] == needle {
419 return Some(i);
420 }
421 i += 1;
422 }
423 None
424}
425
426fn find_marker(hay: &[u8], needle: &[u8], from: usize) -> Option<usize> {
429 let mut i = from;
430 let mut quote: Option<u8> = None;
431 let mut escaped = false;
432 while i < hay.len() {
433 let ch = hay[i];
434 if let Some(q) = quote {
435 if escaped {
436 escaped = false;
437 } else if ch == b'\\' {
438 escaped = true;
439 } else if ch == q {
440 quote = None;
441 }
442 i += 1;
443 continue;
444 }
445 if ch == b'\'' || ch == b'"' {
446 quote = Some(ch);
447 i += 1;
448 continue;
449 }
450 if hay[i..].starts_with(needle) && is_field_boundary(hay, i) {
451 return Some(i);
452 }
453 i += 1;
454 }
455 None
456}
457
458fn is_field_boundary(b: &[u8], pos: usize) -> bool {
459 let mut j = pos;
460 while j > 0 && b[j - 1] == b' ' {
461 j -= 1;
462 }
463 j > 0 && b[j - 1] == b';'
464}
465
466fn skip_spaces(b: &[u8], mut i: usize) -> usize {
467 while i < b.len() && b[i] == b' ' {
468 i += 1;
469 }
470 i
471}
472
473fn expect(b: &[u8], i: usize, lit: &[u8]) -> Result<(), ParseError> {
474 if b.len() >= i + lit.len() && &b[i..i + lit.len()] == lit {
475 Ok(())
476 } else {
477 let what = match lit {
478 b"tid:" => "expected 'tid:'",
479 b"-" => "expected '-'",
480 b"[" => "expected '['",
481 b"{" => "expected '{'",
482 _ => "unexpected token",
483 };
484 Err(ParseError::invalid(i, what))
485 }
486}
487
488pub fn trim(b: &[u8]) -> &str {
490 let mut s = 0;
491 let mut e = b.len();
492 while s < e && (b[s] == b' ' || b[s] == b'\t' || b[s] == b'\r' || b[s] == b'\n') {
493 s += 1;
494 }
495 while e > s && (b[e - 1] == b' ' || b[e - 1] == b'\t' || b[e - 1] == b'\r' || b[e - 1] == b'\n')
496 {
497 e -= 1;
498 }
499 unsafe { std::str::from_utf8_unchecked(&b[s..e]) }
500}
501
502fn trim_start(b: &[u8]) -> &str {
503 let mut s = 0;
504 while s < b.len() && (b[s] == b' ' || b[s] == b'\t') {
505 s += 1;
506 }
507 unsafe { std::str::from_utf8_unchecked(&b[s..]) }
508}
509
510fn trim_semi(s: &str) -> &str {
512 let b = s.as_bytes();
513 let mut e = b.len();
514 while e > 0 && (b[e - 1] == b';' || b[e - 1] == b' ') {
515 e -= 1;
516 }
517 &s[..e]
518}
519
520fn take_i64(b: &[u8], i: usize) -> Option<(i64, usize)> {
522 let mut j = i;
523 let mut v: i64 = 0;
524 while j < b.len() && b[j].is_ascii_digit() {
525 v = v.checked_mul(10)?.checked_add((b[j] - b'0') as i64)?;
526 j += 1;
527 }
528 if j == i { None } else { Some((v, j)) }
529}
530
531fn strip_digits(b: &[u8], prefix: &[u8]) -> Option<i64> {
533 if b.len() > prefix.len() && b.starts_with(prefix) {
534 let (v, end) = take_i64(b, prefix.len())?;
535 if end == b.len() { Some(v) } else { None }
536 } else {
537 None
538 }
539}
540
541fn digits_after(b: &[u8], prefix: &[u8]) -> Option<i64> {
543 if b.starts_with(prefix) {
544 take_i64(b, prefix.len()).map(|(v, _)| v)
545 } else {
546 None
547 }
548}
549
550pub fn epoch_millis(ts: &str) -> Option<i64> {
552 let b = ts.as_bytes();
553 if b.len() < 23 {
554 return None;
555 }
556 if b[4] != b'-'
557 || b[7] != b'-'
558 || b[10] != b' '
559 || b[13] != b':'
560 || b[16] != b':'
561 || b[19] != b'.'
562 {
563 return None;
564 }
565 let num = |off: usize, len: usize| -> Option<i64> {
566 take_i64(b, off).and_then(|(v, end)| if end == off + len { Some(v) } else { None })
567 };
568 let (y, mo, d) = (num(0, 4)?, num(5, 2)?, num(8, 2)?);
569 let (h, mi, s) = (num(11, 2)?, num(14, 2)?, num(17, 2)?);
570 let ms = num(20, 3)?;
571 let days = days_from_civil(y, mo, d);
572 Some((days * 86_400 + h * 3_600 + mi * 60 + s) * 1_000 + ms)
573}
574
575fn days_from_civil(y: i64, m: i64, d: i64) -> i64 {
577 let y = if m <= 2 { y - 1 } else { y };
578 let era = if y >= 0 { y } else { y - 399 } / 400;
579 let yoe = y - era * 400;
580 let mp = if m > 2 { m - 3 } else { m + 9 };
581 let doy = (153 * mp + 2) / 5 + d - 1;
582 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
583 era * 146_097 + doe - 719_468
584}