|
38 | 38 | use chrono::{DateTime, FixedOffset}; |
39 | 39 | use std::{borrow::Cow, collections::HashSet, fmt, sync::Arc}; |
40 | 40 |
|
| 41 | +#[cfg(test)] |
| 42 | +mod tests; |
| 43 | + |
41 | 44 | // --------------------------------------------------------------------------- |
42 | 45 | // Log level |
43 | 46 | // --------------------------------------------------------------------------- |
@@ -138,10 +141,10 @@ impl fmt::Display for LogSource { |
138 | 141 | } |
139 | 142 |
|
140 | 143 | // --------------------------------------------------------------------------- |
141 | | -// Log source (borrowed) |
| 144 | +// Log source |
142 | 145 | // --------------------------------------------------------------------------- |
143 | 146 |
|
144 | | -/// Zero-copy version of [`LogSource`]. Borrows slices from the original log line. |
| 147 | +/// String slices from the original log line. |
145 | 148 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
146 | 149 | pub struct LogSourceRef<'a> { |
147 | 150 | /// The component prefix, e.g. `"1P"`, `"client"`, `"status"`. |
@@ -202,10 +205,6 @@ impl fmt::Display for LogSourceRef<'_> { |
202 | 205 | } |
203 | 206 | } |
204 | 207 |
|
205 | | -// --------------------------------------------------------------------------- |
206 | | -// Shared helpers for LogSource / LogSourceRef |
207 | | -// --------------------------------------------------------------------------- |
208 | | - |
209 | 208 | /// Extract the file path portion from a detail string like `"crate/path/file.rs:42"`. |
210 | 209 | fn extract_file_path(detail: &str) -> Option<&str> { |
211 | 210 | let colon_pos = detail.rfind(':')?; |
@@ -275,11 +274,18 @@ impl<'a> LogEntry<'a> { |
275 | 274 | cache: &mut StringCache, |
276 | 275 | ) -> Vec<Self> { |
277 | 276 | let title_arc = cache.cached(log_file_title); |
278 | | - parse_log_lines( |
279 | | - content, |
280 | | - |line| Self::parse_line(&title_arc, line, cache), |
281 | | - |entry, line| entry.continuation.push(line), |
282 | | - ) |
| 277 | + let mut entries = Vec::with_capacity(content.lines().count()); |
| 278 | + for line in content.lines().filter(|line| !line.trim_start().is_empty()) { |
| 279 | + match Self::parse_line(&title_arc, line, cache) { |
| 280 | + Some(entry) => entries.push(entry), |
| 281 | + None => { |
| 282 | + if let Some(last) = entries.last_mut() { |
| 283 | + last.continuation.push(line); |
| 284 | + } |
| 285 | + } |
| 286 | + } |
| 287 | + } |
| 288 | + entries |
283 | 289 | } |
284 | 290 |
|
285 | 291 | /// Attempt to parse a single log line into a zero-copy [`LogEntry`]. |
@@ -351,10 +357,6 @@ impl fmt::Display for LogEntry<'_> { |
351 | 357 | } |
352 | 358 | } |
353 | 359 |
|
354 | | -// --------------------------------------------------------------------------- |
355 | | -// String Cache |
356 | | -// --------------------------------------------------------------------------- |
357 | | - |
358 | 360 | /// A simple string cache backed by a [`HashMap`]. Converts `&str` values |
359 | 361 | /// into `Arc<str>`, returning the same `Arc` for duplicate strings. |
360 | 362 | /// |
@@ -397,34 +399,6 @@ impl StringCache { |
397 | 399 | } |
398 | 400 | } |
399 | 401 |
|
400 | | -// --------------------------------------------------------------------------- |
401 | | -// Shared line-parsing logic |
402 | | -// --------------------------------------------------------------------------- |
403 | | - |
404 | | -/// Generic log content parser shared by owned and zero-copy paths. |
405 | | -/// |
406 | | -/// Iterates lines, calling `try_parse` on each. If it returns `Some(entry)`, |
407 | | -/// the entry is collected. Otherwise the line is a continuation and is |
408 | | -/// attached to the last entry via `push_continuation`. |
409 | | -fn parse_log_lines<'a, T>( |
410 | | - content: &'a str, |
411 | | - mut try_parse: impl FnMut(&'a str) -> Option<T>, |
412 | | - mut push_continuation: impl FnMut(&mut T, &'a str), |
413 | | -) -> Vec<T> { |
414 | | - let mut entries = Vec::with_capacity(content.lines().count()); |
415 | | - for line in content.lines().filter(|line| !line.trim_start().is_empty()) { |
416 | | - match try_parse(line) { |
417 | | - Some(entry) => entries.push(entry), |
418 | | - None => { |
419 | | - if let Some(last) = entries.last_mut() { |
420 | | - push_continuation(last, line); |
421 | | - } |
422 | | - } |
423 | | - } |
424 | | - } |
425 | | - entries |
426 | | -} |
427 | | - |
428 | 402 | struct LogLineFields<'a> { |
429 | 403 | level: LogLevel, |
430 | 404 | timestamp: DateTime<FixedOffset>, |
@@ -565,10 +539,3 @@ fn parse_bracketed(s: &str) -> Option<(&str, &str)> { |
565 | 539 | let close = s.find(']')?; |
566 | 540 | Some((&s[1..close], &s[close + 1..])) |
567 | 541 | } |
568 | | - |
569 | | -// --------------------------------------------------------------------------- |
570 | | -// Tests |
571 | | -// --------------------------------------------------------------------------- |
572 | | - |
573 | | -#[cfg(test)] |
574 | | -mod tests; |
0 commit comments