• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jtmoon79 / super-speedy-syslog-searcher / 28690460673

04 Jul 2026 01:15AM UTC coverage: 68.982% (+0.4%) from 68.549%
28690460673

push

github

jtmoon79
(BIN) allow -a -b accept timezone

19 of 32 new or added lines in 1 file covered. (59.38%)

2275 existing lines in 13 files now uncovered.

16951 of 24573 relevant lines covered (68.98%)

178397.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.94
/src/readers/syslogprocessor.rs
1
// src/readers/syslogprocessor.rs
2
// …
3

4
//! Implements a [`SyslogProcessor`], the driver of the processing stages for
5
//! a "syslog" file using a [`SyslineReader`].
6
//!
7
//! A "syslog" file in this context means any text-based file with logged
8
//! messages with a datetime stamp.
9
//! The file may use a formally defined log message format (e.g. RFC 5424)
10
//! or an ad-hoc log message format (most log files).<br/>
11
//! The two common assumptions are that:
12
//! 1. each log message has a datetime stamp on the first line
13
//! 2. log messages are in chronological order
14
//!
15
//! Sibling of [`FixedStructReader`]. But far more complicated due to the
16
//! ad-hoc nature of log files.
17
//!
18
//! This is an _s4lib_ structure used by the binary program _s4_.
19
//!
20
//! [`FixedStructReader`]: crate::readers::fixedstructreader::FixedStructReader
21
//! [`SyslineReader`]: crate::readers::syslinereader::SyslineReader
22
//! [`SyslogProcessor`]: SyslogProcessor
23

24
#![allow(non_snake_case)]
25

26
use std::fmt;
27
use std::fmt::Debug;
28
use std::io::{
29
    Error,
30
    ErrorKind,
31
    Result,
32
};
33
use std::time::Duration as StdDuration;
34

35
use ::chrono::Datelike;
36
use ::lazy_static::lazy_static;
37
use ::rangemap::RangeMap;
38
use ::si_trace_print::{
39
    def1n,
40
    def1x,
41
    def1ñ,
42
    defn,
43
    defo,
44
    defx,
45
    defñ,
46
};
47

48
use crate::common::{
49
    CharSz,
50
    Count,
51
    FPath,
52
    FileOffset,
53
    FileProcessingResult,
54
    FileSz,
55
    FileType,
56
    FileTypeTextEncoding,
57
    PathId,
58
    SYSLOG_SZ_MAX,
59
};
60
use crate::data::datetime::{
61
    datetime_minus_systemtime,
62
    dt_after_or_before,
63
    systemtime_to_datetime,
64
    DateTimeL,
65
    DateTimeLOpt,
66
    Duration,
67
    FixedOffset,
68
    Result_Filter_DateTime1,
69
    SystemTime,
70
    Year,
71
    UPTIME_DEFAULT_OFFSET,
72
};
73
use crate::data::sysline::SyslineP;
74
#[cfg(test)]
75
use crate::readers::blockreader::SetDroppedBlocks;
76
use crate::readers::blockreader::{
77
    BlockIndex,
78
    BlockOffset,
79
    BlockP,
80
    BlockSz,
81
    ResultFindReadBlock,
82
};
83
#[doc(hidden)]
84
pub use crate::readers::linereader::ResultFindLine;
85
#[cfg(test)]
86
use crate::readers::linereader::SetDroppedLines;
87
use crate::readers::summary::Summary;
88
use crate::readers::filepreprocessor::detect_filetype_text_encoding;
89
#[cfg(test)]
90
use crate::readers::syslinereader::SetDroppedSyslines;
91
#[doc(hidden)]
92
pub use crate::readers::syslinereader::{
93
    DateTimePatternCounts,
94
    ResultFindSysline,
95
    SummarySyslineReader,
96
    SyslineReader,
97
};
98
use crate::{
99
    de_err,
100
    de_wrn,
101
    e_err,
102
};
103

104
// ---------------
105
// SyslogProcessor
106

107
/// `SYSLOG_SZ_MAX` as a `BlockSz`.
108
pub(crate) const SYSLOG_SZ_MAX_BSZ: BlockSz = SYSLOG_SZ_MAX as BlockSz;
109

110
/// Typed [`FileProcessingResult`] for "block zero analysis".
111
///
112
/// [`FileProcessingResult`]: crate::common::FileProcessingResult
113
pub type FileProcessingResultBlockZero = FileProcessingResult<std::io::Error>;
114

115
/// Enum for the [`SyslogProcessor`] processing stages. Each file processed
116
/// advances through these stages. Sometimes stages may be skipped.
117
///
118
/// [`SyslogProcessor`]: self::SyslogProcessor
119
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
120
pub enum ProcessingStage {
121
    /// Does the file exist and is it a parseable type?
122
    Stage0ValidFileCheck,
123
    /// Check file can be parsed by trying to parse it. Determine the
124
    /// datetime patterns of any found [`Sysline`s].<br/>
125
    /// If no `Sysline`s are found then advance to `Stage4Summary`.
126
    ///
127
    /// [`Sysline`s]: crate::data::sysline::Sysline
128
    Stage1BlockzeroAnalysis,
129
    /// Find the first [`Sysline`] in the syslog file.<br/>
130
    /// If passed CLI option `--after` then find the first `Sysline` with
131
    /// datetime at or after the user-passed [`DateTimeL`].
132
    ///
133
    /// [`Sysline`]: crate::data::sysline::Sysline
134
    /// [`DateTimeL`]: crate::data::datetime::DateTimeL
135
    Stage2FindDt,
136
    /// Advanced through the syslog file to the end.<br/>
137
    /// If passed CLI option `--before` then process up to
138
    /// the last [`Sysline`] with datetime at or before the user-passed
139
    /// [`DateTimeL`]. Otherwise, process all remaining Syslines.
140
    ///
141
    /// While advancing, try to [`drop`] previously processed data `Block`s,
142
    /// `Line`s, and `Sysline`s to lessen memory allocated.
143
    /// a.k.a. "_streaming stage_".
144
    ///
145
    /// Also see function [`find_sysline`].
146
    ///
147
    /// [`Sysline`]: crate::data::sysline::Sysline
148
    /// [`DateTimeL`]: crate::data::datetime::DateTimeL
149
    /// [`find_sysline`]: self::SyslogProcessor#method.find_sysline
150
    /// [`drop`]: self::SyslogProcessor#method.drop_data_try
151
    Stage3StreamSyslines,
152
    /// If passed CLI option `--summary` then print a summary of
153
    /// various information about the processed file.
154
    Stage4Summary,
155
}
156

157
/// [`BlockSz`] in a [`Range`].
158
///
159
/// [`Range`]: std::ops::Range
160
/// [`BlockSz`]: crate::readers::blockreader::BlockSz
161
type BszRange = std::ops::Range<BlockSz>;
162

163
/// Map [`BlockSz`] to a [`Count`].
164
///
165
/// [`BlockSz`]: crate::readers::blockreader::BlockSz
166
/// [`Count`]: crate::common::Count
167
type MapBszRangeToCount = RangeMap<u64, Count>;
168

169
lazy_static! {
170
    /// For files in `blockzero_analyis`, the number of [`Line`]s needed to
171
    /// be found within block zero.
172
    ///
173
    /// [`Line`]: crate::data::line::Line
174
    pub static ref BLOCKZERO_ANALYSIS_LINE_COUNT_MIN_MAP: MapBszRangeToCount = {
175
        defñ!("lazy_static! BLOCKZERO_ANALYSIS_LINE_COUNT_MIN_MAP::new()");
176

177
        let mut m = MapBszRangeToCount::new();
178
        m.insert(BszRange{start: 0, end: SYSLOG_SZ_MAX_BSZ}, 1);
179
        m.insert(BszRange{start: SYSLOG_SZ_MAX_BSZ, end: SYSLOG_SZ_MAX_BSZ * 3}, 3);
180
        m.insert(BszRange{start: SYSLOG_SZ_MAX_BSZ * 3, end: BlockSz::MAX}, 3);
181

182
        m
183
    };
184

185
    /// For files in `blockzero_analyis`, the number of [`Sysline`]s needed to
186
    /// be found within block zero.
187
    ///
188
    /// [`Sysline`]: crate::data::sysline::Sysline
189
    pub static ref BLOCKZERO_ANALYSIS_SYSLINE_COUNT_MIN_MAP: MapBszRangeToCount = {
190
        defñ!("lazy_static! BLOCKZERO_ANALYSIS_SYSLINE_COUNT_MIN_MAP::new()");
191

192
        let mut m = MapBszRangeToCount::new();
193
        m.insert(BszRange{start: 0, end: SYSLOG_SZ_MAX_BSZ}, 1);
194
        m.insert(BszRange{start: SYSLOG_SZ_MAX_BSZ, end: BlockSz::MAX}, 2);
195

196
        m
197
    };
198
}
199

200

201
/// 25 hours.
202
/// For processing syslog files without a year.
203
/// If there is a datetime jump backwards more than this value then
204
/// a year rollover happened.
205
///
206
/// e.g. given log messages
207
///     Dec 31 23:59:59 [INFO] One!
208
///     Jan 1 00:00:00 [INFO] Happy New Year!!!
209
/// These messages interpreted as the same year would be a jump backwards
210
/// in time.
211
/// Of course, this apparent "jump backwards" means the year changed.
212
// XXX: cannot make `const` because `secs` is a private field
213
const BACKWARDS_TIME_JUMP_MEANS_NEW_YEAR: Duration = Duration::try_seconds(60 * 60 * 25).unwrap();
214

215
/// The `SyslogProcessor` uses [`SyslineReader`] to find [`Sysline`s] in a file.
216
///
217
/// A `SyslogProcessor` has knowledge of:
218
/// - the different stages of processing a syslog file
219
/// - stores optional datetime filters and searches with them
220
/// - handles special cases of a syslog file with a datetime format without a year
221
///
222
/// A `SyslogProcessor` is driven by a thread to fully process one syslog file.
223
///
224
/// During "[streaming stage]", the `SyslogProcessor` will proactively `drop`
225
/// data that has been processed and printed. It does so by calling
226
/// private function [`drop_data_try`] during function [`find_sysline`].
227
///
228
/// A `SyslogProcessor` presumes syslog messages are in chronological order.
229
///
230
/// [`Sysline`s]: crate::data::sysline::Sysline
231
/// [`SyslineReader`]: crate::readers::syslinereader::SyslineReader
232
/// [`LineReader`]: crate::readers::linereader::LineReader
233
/// [`BlockReader`]: crate::readers::blockreader::BlockReader
234
/// [`drop_data_try`]: self::SyslogProcessor#method.drop_data_try
235
/// [`find_sysline`]: self::SyslogProcessor#method.find_sysline
236
/// [streaming stage]: self::ProcessingStage#variant.Stage3StreamSyslines
237
pub struct SyslogProcessor {
238
    syslinereader: SyslineReader,
239
    /// Current `ProcessingStage`.
240
    processingstage: ProcessingStage,
241
    /// `FixedOffset` timezone for datetime formats without a timezone.
242
    tz_offset: FixedOffset,
243
    /// Optional filter, syslines _after_ this `DateTimeL`.
244
    filter_dt_after_opt: DateTimeLOpt,
245
    /// Optional filter, syslines _before_ this `DateTimeL`.
246
    filter_dt_before_opt: DateTimeLOpt,
247
    /// Internal sanity check, has `self.blockzero_analysis()` completed?
248
    blockzero_analysis_done: bool,
249
    /// Internal tracking of last `blockoffset` passed to `drop_block`.
250
    drop_block_last: BlockOffset,
251
    /// Optional `Year` value used to start `process_missing_year()`.
252
    /// Only needed for syslog files with datetime format without a year.
253
    missing_year: Option<Year>,
254
    /// The last [`Error`], if any, as a `String`. Set by [`set_error`].
255
    ///
256
    /// Annoyingly, cannot [Clone or Copy `Error`].
257
    ///
258
    /// [`Error`]: std::io::Error
259
    /// [Clone or Copy `Error`]: https://github.com/rust-lang/rust/issues/24135
260
    /// [`set_error`]: self::SyslogProcessor#method.set_error
261
    // TRACKING: https://github.com/rust-lang/rust/issues/24135
262
    error: Option<String>,
263
}
264

265
impl Debug for SyslogProcessor {
266
    fn fmt(
400✔
267
        &self,
400✔
268
        f: &mut fmt::Formatter,
400✔
269
    ) -> fmt::Result {
400✔
270
        f.debug_struct("SyslogProcessor")
400✔
271
            .field("Path", &self.path())
400✔
272
            .field("Processing Stage", &self.processingstage)
400✔
273
            .field("BlockSz", &self.blocksz())
400✔
274
            .field("TimeOffset", &self.tz_offset)
400✔
275
            .field("filter_dt_after_opt", &self.filter_dt_after_opt)
400✔
276
            .field("filter_dt_before_opt", &self.filter_dt_before_opt)
400✔
277
            .field("BO Analysis done?", &self.blockzero_analysis_done)
400✔
278
            .field("filetype", &self.filetype())
400✔
279
            .field("Reprocessed missing year?", &self.did_process_missing_year())
400✔
280
            .field("Missing Year", &self.missing_year)
400✔
281
            .field("Error?", &self.error)
400✔
282
            .finish()
400✔
283
    }
400✔
284
}
285

286
// TODO: [2023/04] remove redundant variable prefix name `syslogprocessor_`
287
#[derive(Clone, Debug, Default, Eq, PartialEq)]
288
pub struct SummarySyslogProcessor {
289
    /// `SyslogProcessor::missing_year`
290
    pub syslogprocessor_missing_year: Option<Year>,
291
}
292

293
impl SyslogProcessor {
294
    /// Maximum number of datetime patterns for matching the remainder of a
295
    /// syslog file.
296
    const DT_PATTERN_MAX: usize = SyslineReader::DT_PATTERN_MAX;
297

298
    /// `SyslogProcessor` has it's own miminum requirements for `BlockSz`.
299
    ///
300
    /// Necessary for `blockzero_analysis` functions to have chance at success.
301
    #[cfg(not(any(debug_assertions, test)))]
302
    pub const BLOCKSZ_MIN: BlockSz = 0x40;
303
    #[doc(hidden)]
304
    #[cfg(any(debug_assertions, test))]
305
    pub const BLOCKSZ_MIN: BlockSz = crate::readers::blockreader::BLOCKSZ_MIN;
306

307
    /// Minimum number of bytes needed to perform `blockzero_analysis_bytes`.
308
    ///
309
    /// Pretty sure this is smaller than the smallest possible timestamp that
310
    /// can be processed by the `DTPD!` in `DATETIME_PARSE_DATAS`.
311
    /// In other words, a file that only has a minimal datetimestamp followed by
312
    /// an empty log message.
313
    ///
314
    /// It's okay if this is too small as the later processing stages will
315
    /// be certain of any possible datetime patterns.
316
    pub const BLOCKZERO_ANALYSIS_BYTES_MIN: BlockSz = 6;
317

318
    /// If the first number of bytes are zero bytes (NULL bytes) then
319
    /// stop processing the file. It's extremely unlikely this is a syslog
320
    /// file and more likely it's some sort of binary data file.
321
    pub const BLOCKZERO_ANALYSIS_BYTES_NULL_MAX: usize = 128;
322
    /// Same as [`SyslogProcessor::BLOCKZERO_ANALYSIS_BYTES_NULL_MAX`] but for 0xFF bytes.
323
    pub const BLOCKZERO_ANALYSIS_BYTES_FF_MAX: usize = Self::BLOCKZERO_ANALYSIS_BYTES_NULL_MAX;
324

325
    /// Allow "streaming stage" to drop data?
326
    /// Compile-time "option" to aid manual debugging.
327
    #[doc(hidden)]
328
    const STREAM_STAGE_DROP: bool = true;
329

330
    /// Use LRU caches in underlying components?
331
    ///
332
    /// XXX: For development and testing experiments!
333
    #[doc(hidden)]
334
    const LRU_CACHE_ENABLE: bool = true;
335

336
    /// Create a new `SyslogProcessor`.
337
    ///
338
    /// **NOTE:** should not attempt any block reads here,
339
    /// similar to other `*Readers::new()`
340
    pub fn new(
485✔
341
        path_id: PathId,
485✔
342
        path: FPath,
485✔
343
        filetype: FileType,
485✔
344
        blocksz: BlockSz,
485✔
345
        tz_offset: FixedOffset,
485✔
346
        filter_dt_after_opt: DateTimeLOpt,
485✔
347
        filter_dt_before_opt: DateTimeLOpt,
485✔
348
    ) -> Result<SyslogProcessor> {
485✔
349
        def1n!("({}, {:?}, {:?}, {:?}, {:?})", path_id, path, filetype, blocksz, tz_offset);
485✔
350
        if blocksz < SyslogProcessor::BLOCKSZ_MIN {
485✔
351
            return Result::Err(Error::new(
×
352
                ErrorKind::InvalidInput,
×
353
                format!(
×
354
                    "BlockSz {0} (0x{0:08X}) is too small, SyslogProcessor has BlockSz minimum {1} (0x{1:08X}) file {2:?}",
×
355
                    blocksz,
×
356
                    SyslogProcessor::BLOCKSZ_MIN,
×
357
                    &path,
×
UNCOV
358
                ),
×
UNCOV
359
            ));
×
360
        }
485✔
361
        let mut slr = match SyslineReader::new(path_id, path, filetype, blocksz, tz_offset) {
485✔
362
            Ok(val) => val,
483✔
363
            Err(err) => {
2✔
364
                def1x!();
2✔
365
                return Result::Err(err);
2✔
366
            }
367
        };
368

369
        if !SyslogProcessor::LRU_CACHE_ENABLE {
483✔
370
            slr.LRU_cache_disable();
×
371
            slr.linereader
×
372
                .LRU_cache_disable();
×
373
            slr.linereader
×
UNCOV
374
                .blockreader
×
UNCOV
375
                .LRU_cache_disable();
×
376
        }
483✔
377

378
        def1x!("return Ok(SyslogProcessor)");
483✔
379

380
        Result::Ok(SyslogProcessor {
483✔
381
            syslinereader: slr,
483✔
382
            processingstage: ProcessingStage::Stage0ValidFileCheck,
483✔
383
            tz_offset,
483✔
384
            filter_dt_after_opt,
483✔
385
            filter_dt_before_opt,
483✔
386
            blockzero_analysis_done: false,
483✔
387
            drop_block_last: 0,
483✔
388
            missing_year: None,
483✔
389
            error: None,
483✔
390
        })
483✔
391
    }
485✔
392

393
    /// `Count` of [`Line`s] processed.
394
    ///
395
    /// [`Line`s]: crate::data::line::Line
396
    #[inline(always)]
397
    #[allow(dead_code)]
398
    pub fn count_lines(&self) -> Count {
×
399
        self.syslinereader
×
400
            .linereader
×
UNCOV
401
            .count_lines_processed()
×
UNCOV
402
    }
×
403

404
    /// See [`Sysline::count_syslines_stored`].
405
    ///
406
    /// [`Sysline::count_syslines_stored`]: crate::data::sysline::Sysline::count_syslines_stored
407
    #[cfg(test)]
408
    pub fn count_syslines_stored(&self) -> Count {
4✔
409
        self.syslinereader.count_syslines_stored()
4✔
410
    }
4✔
411

412
    /// See [`BlockReader::blocksz`].
413
    ///
414
    /// [`BlockReader::blocksz`]: crate::readers::blockreader::BlockReader#method.blocksz
415
    #[inline(always)]
416
    pub const fn blocksz(&self) -> BlockSz {
873✔
417
        self.syslinereader.blocksz()
873✔
418
    }
873✔
419

420
    /// See [`BlockReader::path_id`].
421
    ///
422
    /// [`BlockReader::path_id`]: crate::readers::blockreader::BlockReader#method.path_id
423
    #[inline(always)]
UNCOV
424
    pub const fn path_id(&self) -> PathId {
×
UNCOV
425
        self.syslinereader.path_id()
×
UNCOV
426
    }
×
427

428
    /// See [`BlockReader::filesz`].
429
    ///
430
    /// [`BlockReader::filesz`]: crate::readers::blockreader::BlockReader#method.filesz
431
    #[inline(always)]
432
    pub const fn filesz(&self) -> FileSz {
476✔
433
        self.syslinereader.filesz()
476✔
434
    }
476✔
435

436
    /// See [`BlockReader::filetype`].
437
    ///
438
    /// [`BlockReader::filetype`]: crate::readers::blockreader::BlockReader#method.filetype
439
    #[inline(always)]
440
    pub const fn filetype(&self) -> FileType {
1,113✔
441
        self.syslinereader.filetype()
1,113✔
442
    }
1,113✔
443

444
    /// See [`BlockReader::path`].
445
    ///
446
    /// [`BlockReader::path`]: crate::readers::blockreader::BlockReader#method.path
447
    #[inline(always)]
448
    #[allow(dead_code)]
449
    pub const fn path(&self) -> &FPath {
1,113✔
450
        self.syslinereader.path()
1,113✔
451
    }
1,113✔
452

453
    /// See [`BlockReader::block_offset_at_file_offset`].
454
    ///
455
    /// [`BlockReader::block_offset_at_file_offset`]: crate::readers::blockreader::BlockReader#method.block_offset_at_file_offset
456
    #[allow(dead_code)]
UNCOV
457
    pub const fn block_offset_at_file_offset(
×
UNCOV
458
        &self,
×
459
        fileoffset: FileOffset,
×
460
    ) -> BlockOffset {
×
461
        self.syslinereader
×
462
            .block_offset_at_file_offset(fileoffset)
×
463
    }
×
464

465
    /// See [`BlockReader::file_offset_at_block_offset`].
466
    ///
467
    /// [`BlockReader::file_offset_at_block_offset`]: crate::readers::blockreader::BlockReader#method.file_offset_at_block_offset
468
    #[allow(dead_code)]
UNCOV
469
    pub const fn file_offset_at_block_offset(
×
UNCOV
470
        &self,
×
471
        blockoffset: BlockOffset,
×
472
    ) -> FileOffset {
×
473
        self.syslinereader
×
474
            .file_offset_at_block_offset(blockoffset)
×
475
    }
×
476

477
    /// See [`BlockReader::file_offset_at_block_offset_index`].
478
    ///
479
    /// [`BlockReader::file_offset_at_block_offset_index`]: crate::readers::blockreader::BlockReader#method.file_offset_at_block_offset_index
480
    #[allow(dead_code)]
UNCOV
481
    pub const fn file_offset_at_block_offset_index(
×
UNCOV
482
        &self,
×
UNCOV
483
        blockoffset: BlockOffset,
×
484
        blockindex: BlockIndex,
×
485
    ) -> FileOffset {
×
486
        self.syslinereader
×
487
            .file_offset_at_block_offset_index(blockoffset, blockindex)
×
488
    }
×
489

490
    /// See [`BlockReader::block_index_at_file_offset`].
491
    ///
492
    /// [`BlockReader::block_index_at_file_offset`]: crate::readers::blockreader::BlockReader#method.block_index_at_file_offset
493
    #[allow(dead_code)]
UNCOV
494
    pub const fn block_index_at_file_offset(
×
UNCOV
495
        &self,
×
496
        fileoffset: FileOffset,
×
497
    ) -> BlockIndex {
×
498
        self.syslinereader
×
499
            .block_index_at_file_offset(fileoffset)
×
UNCOV
500
    }
×
501

502
    /// See [`BlockReader::count_blocks`].
503
    ///
504
    /// [`BlockReader::count_blocks`]: crate::readers::blockreader::BlockReader#method.count_blocks
505
    #[allow(dead_code)]
506
    pub const fn count_blocks(&self) -> Count {
×
507
        self.syslinereader
×
508
            .count_blocks()
×
UNCOV
509
    }
×
510

511
    /// See [`BlockReader::blockoffset_last`].
512
    ///
513
    /// [`BlockReader::blockoffset_last`]: crate::readers::blockreader::BlockReader#method.blockoffset_last
514
    #[allow(dead_code)]
UNCOV
515
    pub const fn blockoffset_last(&self) -> BlockOffset {
×
UNCOV
516
        self.syslinereader
×
UNCOV
517
            .blockoffset_last()
×
UNCOV
518
    }
×
519

520
    /// See [`BlockReader::fileoffset_last`].
521
    ///
522
    /// [`BlockReader::fileoffset_last`]: crate::readers::blockreader::BlockReader#method.fileoffset_last
523
    pub const fn fileoffset_last(&self) -> FileOffset {
15✔
524
        self.syslinereader
15✔
525
            .fileoffset_last()
15✔
526
    }
15✔
527

528
    /// See [`LineReader::charsz`].
529
    ///
530
    /// [`LineReader::charsz`]: crate::readers::linereader::LineReader#method.charsz
531
    #[allow(dead_code)]
532
    pub const fn charsz(&self) -> CharSz {
15✔
533
        self.syslinereader.charsz()
15✔
534
    }
15✔
535

536
    /// See [`BlockReader::mtime`].
537
    ///
538
    /// [`BlockReader::mtime`]: crate::readers::blockreader::BlockReader#method.mtime
539
    pub fn mtime(&self) -> SystemTime {
414✔
540
        self.syslinereader.mtime()
414✔
541
    }
414✔
542

543
    /// Did this `SyslogProcessor` run `process_missing_year()` ?
544
    fn did_process_missing_year(&self) -> bool {
415✔
545
        self.missing_year.is_some()
415✔
546
    }
415✔
547

548
    /// Did this `SyslogProcessor` run `process_uptime()` ?
UNCOV
549
    fn did_process_uptime(&self) -> bool {
×
550
        self.systemtime_at_uptime_zero().is_some()
×
551
    }
×
552

553
    /// Return `drop_data` value.
554
    pub const fn is_drop_data(&self) -> bool {
3,218✔
555
        self.syslinereader.is_drop_data()
3,218✔
556
    }
3,218✔
557

558
    /// store an `Error` that occurred. For later printing during `--summary`.
559
    // XXX: duplicates `FixedStructReader.set_error`
UNCOV
560
    fn set_error(
×
UNCOV
561
        &mut self,
×
UNCOV
562
        error: &Error,
×
563
    ) {
×
564
        def1ñ!("{:?}", error);
×
565
        let mut error_string: String = error.kind().to_string();
×
566
        error_string.push_str(": ");
×
567
        error_string.push_str(error.kind().to_string().as_str());
×
568
        // print the error but avoid printing the same error more than once
569
        // XXX: This is somewhat a hack as it's possible the same error, with the
570
        //      the same error message, could occur more than once.
571
        //      Considered another way, this function `set_error` may get called
572
        //      too often. The responsibility for calling `set_error` is haphazard.
573
        match &self.error {
×
574
            Some(err_s) => {
×
575
                if err_s != &error_string {
×
576
                    e_err!("{}", error);
×
577
                }
×
578
            }
UNCOV
579
            None => {
×
UNCOV
580
                e_err!("{}", error);
×
UNCOV
581
            }
×
582
        }
UNCOV
583
        if let Some(ref _err) = self.error {
×
UNCOV
584
            de_wrn!("skip overwrite of previous Error {:?} with Error ({:?})", _err, error);
×
UNCOV
585
            return;
×
UNCOV
586
        }
×
UNCOV
587
        self.error = Some(error_string);
×
UNCOV
588
    }
×
589

590
    /// Syslog files wherein the datetime format that does not include a year
591
    /// must have special handling.
592
    ///
593
    /// The last [`Sysline`] in the file is presumed to share the same year as
594
    /// the `mtime` (stored by the underlying [`BlockReader`] instance).
595
    /// The entire file is read from end to beginning (in reverse) (unless
596
    /// a `filter_dt_after_opt` is passed that coincides with the found
597
    /// syslines). The year is tracked and updated for each sysline.
598
    /// If there is jump backwards in time, that is presumed to be a
599
    /// year changeover.
600
    ///
601
    /// For example, given syslog contents
602
    ///
603
    /// ```text
604
    /// Nov 1 12:00:00 hello
605
    /// Dec 1 12:00:00 good morning
606
    /// Jan 1 12:00:00 goodbye
607
    /// ```
608
    ///
609
    /// and file `mtime` that is datetime _January 1 12:00:00 2015_,
610
    /// then the last `Sysline` "Jan 1 12:00:00 goodbye" is presumed to be in
611
    /// year 2015.
612
    /// The preceding `Sysline` "Dec 1 12:00:00 goodbye" is then processed.
613
    /// An apparent backwards jump is seen _Jan 1_ to _Dec 1_.
614
    /// From this, it can be concluded the _Dec 1_ refers to a prior year, 2014.
615
    ///
616
    /// Typically, when a datetime filter is passed, a special binary search is
617
    /// done to find the desired syslog line, reducing resource usage. Whereas,
618
    /// files processed here must be read linearly and in their entirety
619
    /// Or, if `filter_dt_after_opt` is passed then the file is read to the
620
    /// first `sysline.dt()` (datetime) that is
621
    /// `Result_Filter_DateTime1::OccursBefore` the
622
    /// `filter_dt_after_opt`.
623
    ///
624
    /// [`Sysline`]: crate::data::sysline::Sysline
625
    /// [`BlockReader`]: crate::readers::blockreader::BlockReader
626
    /// [`DateTimeL`]: crate::data::datetime::DateTimeL
627
    // BUG: does not revise year guesstimation based on encountering leap date February 29
628
    //      See Issue #245
629
    pub fn process_missing_year(
15✔
630
        &mut self,
15✔
631
        mtime: SystemTime,
15✔
632
        filter_dt_after_opt: &DateTimeLOpt,
15✔
633
    ) -> FileProcessingResultBlockZero {
15✔
634
        defn!("({:?}, {:?})", mtime, filter_dt_after_opt);
15✔
635
        debug_assert!(!self.did_process_missing_year(), "process_missing_year() must only be called once");
15✔
636
        let dt_mtime: DateTimeL = systemtime_to_datetime(&self.tz_offset, &mtime);
15✔
637
        defo!("converted dt_mtime {:?}", dt_mtime);
15✔
638
        let year: Year = dt_mtime.date_naive().year() as Year;
15✔
639
        self.missing_year = Some(year);
15✔
640
        defo!("converted missing_year {:?}", self.missing_year);
15✔
641
        let mut year_opt: Option<Year> = Some(year);
15✔
642
        defo!("year_opt {:?}", year_opt);
15✔
643
        let charsz_fo: FileOffset = self.charsz() as FileOffset;
15✔
644

645
        // The previously stored `Sysline`s have a filler year that is most likely
646
        // incorrect. The underlying `Sysline` instance cannot be updated behind
647
        // an `Arc`. Those syslines must be dropped and the entire file
648
        // processed again. However, underlying `Line` and `Block` are still
649
        // valid; do not reprocess those.
650
        self.syslinereader
15✔
651
            .clear_syslines();
15✔
652

653
        // read all syslines in reverse
654
        let mut fo_prev: FileOffset = self.fileoffset_last();
15✔
655
        let mut syslinep_prev_opt: Option<SyslineP> = None;
15✔
656
        loop {
657
            let syslinep: SyslineP = match self
36✔
658
                .syslinereader
36✔
659
                .find_sysline_year(fo_prev, &year_opt)
36✔
660
            {
661
                ResultFindSysline::Found((_fo, syslinep)) => {
36✔
662
                    defo!(
36✔
663
                        "Found {} Sysline @[{}, {}] datetime: {:?})",
664
                        _fo,
665
                        (*syslinep).fileoffset_begin(),
36✔
666
                        (*syslinep).fileoffset_end(),
36✔
667
                        (*syslinep).dt()
36✔
668
                    );
669
                    syslinep
36✔
670
                }
UNCOV
671
                ResultFindSysline::Done => {
×
UNCOV
672
                    defo!("Done, break;");
×
UNCOV
673
                    break;
×
674
                }
UNCOV
675
                ResultFindSysline::Err(err) => {
×
UNCOV
676
                    self.set_error(&err);
×
UNCOV
677
                    defx!("return FileErrIo({:?})", err);
×
UNCOV
678
                    return FileProcessingResultBlockZero::FileErrIoPath(err);
×
679
                }
680
            };
681
            // TODO: [2022/07/27] add fn `syslinereader.find_sysline_year_rev` to hide these char offset
682
            //       details (put them into a struct that is meant to understand these details)
683
            let fo_prev_prev: FileOffset = fo_prev;
36✔
684
            fo_prev = (*syslinep).fileoffset_begin();
36✔
685
            // check if datetime has suddenly jumped backwards.
686
            // if date has jumped backwards, then remove sysline, update the year, and
687
            // process the file from that fileoffset again
688
            if let Some(syslinep_prev) = syslinep_prev_opt {
36✔
689
                // normally `dt_cur` should have a datetime *before or equal* to `dt_prev`
690
                // but if not, then there was probably a year rollover
691
                if (*syslinep).dt() > (*syslinep_prev).dt() {
21✔
UNCOV
692
                    let diff: Duration = *(*syslinep).dt() - *(*syslinep_prev).dt();
×
UNCOV
693
                    if diff > BACKWARDS_TIME_JUMP_MEANS_NEW_YEAR {
×
UNCOV
694
                        year_opt = Some(year_opt.unwrap() - 1);
×
UNCOV
695
                        defo!("year_opt updated {:?}", year_opt);
×
UNCOV
696
                        self.syslinereader
×
UNCOV
697
                            .remove_sysline(fo_prev);
×
UNCOV
698
                        fo_prev = fo_prev_prev;
×
UNCOV
699
                        syslinep_prev_opt = Some(SyslineP::clone(&syslinep_prev));
×
UNCOV
700
                        continue;
×
UNCOV
701
                    }
×
702
                }
21✔
703
            }
15✔
704
            if fo_prev < charsz_fo {
36✔
705
                defo!("fo_prev {} break;", fo_prev);
13✔
706
                // fileoffset is at the beginning of the file (or, cannot be moved back any more)
707
                break;
13✔
708
            }
23✔
709
            // if user-passed `--dt-after` and the sysline is prior to that filter then
710
            // stop processing
711
            match dt_after_or_before(syslinep.dt(), filter_dt_after_opt) {
23✔
712
                Result_Filter_DateTime1::OccursBefore => {
713
                    defo!("dt_after_or_before({:?},  {:?}) returned OccursBefore; break", syslinep.dt(), filter_dt_after_opt);
2✔
714
                    break;
2✔
715
                }
716
                Result_Filter_DateTime1::OccursAtOrAfter | Result_Filter_DateTime1::Pass => {},
21✔
717
            }
718
            // search for preceding sysline
719
            fo_prev -= charsz_fo;
21✔
720
            if fo_prev >= fo_prev_prev {
21✔
721
                // This will happen in case where the very first line of the file
722
                // holds a sysline with datetime pattern without a year, and that
723
                // sysline datetime pattern is different than all
724
                // proceeding syslines that have a year. (and it should only happen then)
725
                // Elicited by example in Issue #74
UNCOV
726
                de_err!("fo_prev {} ≥ {} fo_prev_prev, expected <; something is wrong", fo_prev, fo_prev_prev);
×
727
                // must break otherwise end up in an infinite loop
728
                break;
×
729
            }
21✔
730
            syslinep_prev_opt = Some(SyslineP::clone(&syslinep));
21✔
731
        } // end loop
732
        defx!("return FileOk");
15✔
733

734
        FileProcessingResultBlockZero::FileOk
15✔
735
    }
15✔
736

737
    fn systemtime_at_uptime_zero(&self) -> Option<SystemTime>{
×
738
        self.syslinereader.systemtime_at_uptime_zero
×
739
    }
×
740

741
    pub fn process_uptime(
×
UNCOV
742
        &mut self,
×
743
    ) -> FileProcessingResultBlockZero {
×
UNCOV
744
        defn!();
×
745
        debug_assert!(!self.did_process_uptime(), "did_process_uptime() must only be called once");
×
746

747
        let fo_last = self.fileoffset_last();
×
UNCOV
748
        defo!("find_sysline(fo_last={})", fo_last);
×
749
        let syslinep = match self.find_sysline(fo_last) {
×
750
            ResultFindSysline::Found((_fo, syslinep_)) => {
×
751
                defo!("found sysline at fo_last={} {:?}", fo_last, syslinep_);
×
752

UNCOV
753
                syslinep_
×
754
            }
755
            ResultFindSysline::Done => {
×
756
                defx!("No sysline found");
×
757
                return FileProcessingResultBlockZero::FileErrNoSyslinesFound;
×
758
            }
759
            ResultFindSysline::Err(err) => {
×
760
                defx!("error finding sysline: {:?}", err);
×
761
                return FileProcessingResultBlockZero::FileErrIo(err);
×
762
            }
763
        };
764
        let dt = syslinep.dt();
×
765
        let diff_ = datetime_minus_systemtime(&dt, &UPTIME_DEFAULT_OFFSET);
×
766
        defo!("diff_ from UPTIME_DEFAULT_OFFSET {:?}", diff_);
×
767
        let diff_secs = diff_.num_seconds();
×
768
        defo!("diff_secs {:?}", diff_secs);
×
UNCOV
769
        let mut diff_nanos = diff_.subsec_nanos();
×
770
        defo!("diff_nanos {:?}", diff_nanos);
×
771
        if diff_nanos < 0 {
×
772
            diff_nanos = 0;
×
773
        }
×
UNCOV
774
        let diffs: StdDuration = StdDuration::new(diff_secs as u64, diff_nanos as u32);
×
775
        defo!("diffs {:?}", diffs);
×
776
        defo!("mtime()");
×
777
        let mtime = self.mtime();
×
778
        defo!("mtime {:?} (as DateTime {:?})", mtime, systemtime_to_datetime(&self.tz_offset, &mtime));
×
779
        // std::time::Duration is unsigned whereas chrono::Duration is signed.
UNCOV
780
        let st_at_zero = if diff_secs > 0 {
×
UNCOV
781
            defo!("checked_sub({:?})", diffs);
×
782
            match mtime.checked_sub(diffs) {
×
783
                Some(st) => st,
×
784
                None => {
UNCOV
785
                    defx!("failed to calculate systemtime at uptime zero");
×
786
                    return FileProcessingResultBlockZero::FileErrIo(std::io::Error::other(
×
787
                        "failed to calculate systemtime at uptime zero",
×
788
                    ));
×
789
                }
790
            }
791
        } else {
UNCOV
792
            defo!("checked_add({:?})", diffs);
×
793
            match mtime.checked_add(diffs) {
×
794
                Some(st) => st,
×
795
                None => {
UNCOV
796
                    defx!("failed to calculate systemtime at uptime zero");
×
797
                    return FileProcessingResultBlockZero::FileErrIo(std::io::Error::other(
×
798
                        "failed to calculate systemtime at uptime zero",
×
799
                    ));
×
800
                }
801
            }
802
        };
UNCOV
803
        self.syslinereader.systemtime_at_uptime_zero = Some(st_at_zero);
×
UNCOV
804
        defo!("systemtime_at_uptime_zero is  {:?}", self.syslinereader.systemtime_at_uptime_zero);
×
805
        #[cfg(debug_assertions)]
806
        {
807
            let d = systemtime_to_datetime(
×
UNCOV
808
                &self.tz_offset,
×
UNCOV
809
                &st_at_zero,
×
810
            );
811
            defo!("systemtime_at_uptime_zero as DateTime {:?}", d);
×
812
        }
813

814
        // The systemtime at uptime zero has been discovered.
815
        // So clear the lines that previously used the stand-in value for
816
        // `systemtime_at_uptime_zero`.
UNCOV
817
        self.syslinereader.clear_syslines();
×
818
        // The syslines gathered after this point will use the
819
        // correct `systemtime_at_uptime_zero`.
820

UNCOV
821
        defx!("return FileOk");
×
822

UNCOV
823
        FileProcessingResultBlockZero::FileOk
×
UNCOV
824
    }
×
825

826
    /// See [`SyslineReader::is_sysline_last`].
827
    ///
828
    /// [`SyslineReader::is_sysline_last`]: crate::readers::syslinereader::SyslineReader#method.is_sysline_last
829
    pub fn is_sysline_last(
3,970✔
830
        &self,
3,970✔
831
        syslinep: &SyslineP,
3,970✔
832
    ) -> bool {
3,970✔
833
        self.syslinereader
3,970✔
834
            .is_sysline_last(syslinep)
3,970✔
835
    }
3,970✔
836

837
    /// Try to `drop` data associated with the [`Block`] at [`BlockOffset`].
838
    /// This includes dropping associated [`Sysline`]s and [`Line`]s.
839
    /// This calls [`SyslineReader::drop_data`].
840
    ///
841
    /// _The caller must know what they are doing!_
842
    ///
843
    /// [`BlockOffset`]: crate::readers::blockreader::BlockOffset
844
    /// [`Sysline`]: crate::data::sysline::Sysline
845
    /// [`Line`]: crate::data::line::Line
846
    /// [`Block`]: crate::readers::blockreader::Block
847
    pub fn drop_data(
14✔
848
        &mut self,
14✔
849
        blockoffset: BlockOffset,
14✔
850
    ) -> bool {
14✔
851
        def1n!("({})", blockoffset);
14✔
852
        self.assert_stage(ProcessingStage::Stage3StreamSyslines);
14✔
853

854
        if !self.is_drop_data() {
14✔
UNCOV
855
            def1x!("return false; is_drop_data() is false");
×
UNCOV
856
            return false;
×
857
        }
14✔
858

859
        // `syslinereader.drop_data` is an expensive function, skip if possible.
860
        if blockoffset == self.drop_block_last {
14✔
861
            def1x!("({}) skip block, return true", blockoffset);
5✔
862
            return false;
5✔
863
        }
9✔
864

865
        if self
9✔
866
            .syslinereader
9✔
867
            .drop_data(blockoffset)
9✔
868
        {
869
            self.drop_block_last = blockoffset;
6✔
870
            def1x!("({}) return true", blockoffset);
6✔
871
            return true;
6✔
872
        }
3✔
873

874
        def1x!("({}) return false", blockoffset);
3✔
875
        false
3✔
876
    }
14✔
877

878
    /// Call [`drop_data`] for the data assocaited with the [`Block`]
879
    /// *preceding* the first block of the passed [`Sysline`].
880
    ///
881
    /// _The caller must know what they are doing!_
882
    ///
883
    /// [`drop_data`]: Self#method.drop_data
884
    /// [`Block`]: crate::readers::blockreader::Block
885
    /// [`Sysline`]: crate::data::sysline::Sysline
886
    pub fn drop_data_try(
3,204✔
887
        &mut self,
3,204✔
888
        syslinep: &SyslineP,
3,204✔
889
    ) -> bool {
3,204✔
890
        if !SyslogProcessor::STREAM_STAGE_DROP {
3,204✔
UNCOV
891
            de_wrn!("drop_data_try() called but SyslogProcessor::STREAM_STAGE_DROP is false");
×
UNCOV
892
            return false;
×
893
        }
3,204✔
894
        if !self.is_drop_data() {
3,204✔
UNCOV
895
            def1ñ!("is_drop_data() is false; return false");
×
UNCOV
896
            return false;
×
897
        }
3,204✔
898

899
        let bo_first: BlockOffset = (*syslinep).blockoffset_first();
3,204✔
900
        if bo_first > 1 {
3,204✔
901
            def1ñ!();
14✔
902
            return self.drop_data(bo_first - 2);
14✔
903
        }
3,190✔
904

905
        false
3,190✔
906
    }
3,204✔
907

908
    /// Calls [`self.syslinereader.find_sysline(fileoffset)`],
909
    /// and in some cases calls private function `drop_block` to drop
910
    /// previously processed [`Sysline`], [`Line`], and [`Block`s].
911
    ///
912
    /// This is what implements the "streaming" in "[streaming stage]".
913
    ///
914
    /// [`self.syslinereader.find_sysline(fileoffset)`]: crate::readers::syslinereader::SyslineReader#method.find_sysline
915
    /// [`Block`s]: crate::readers::blockreader::Block
916
    /// [`Line`]: crate::data::line::Line
917
    /// [`Sysline`]: crate::data::sysline::Sysline
918
    /// [streaming stage]: crate::readers::syslogprocessor::ProcessingStage#variant.Stage3StreamSyslines
919
    pub fn find_sysline(
40✔
920
        &mut self,
40✔
921
        fileoffset: FileOffset,
40✔
922
    ) -> ResultFindSysline {
40✔
923
        defn!("({})", fileoffset);
40✔
924
        let result: ResultFindSysline = self
40✔
925
            .syslinereader
40✔
926
            .find_sysline(fileoffset);
40✔
927
        match result {
40✔
928
            ResultFindSysline::Found(_) => {}
35✔
929
            ResultFindSysline::Done => {}
5✔
UNCOV
930
            ResultFindSysline::Err(ref err) => {
×
UNCOV
931
                self.set_error(err);
×
UNCOV
932
            }
×
933
        }
934
        defx!();
40✔
935

936
        result
40✔
937
    }
40✔
938

939
    /// Wrapper function for [`SyslineReader::find_sysline_between_datetime_filters`].
940
    /// Keeps a custom copy of any returned `Error` at `self.error`.
941
    ///
942
    /// [`SyslineReader::find_sysline_between_datetime_filters`]: crate::readers::syslinereader::SyslineReader#method.find_sysline_between_datetime_filters
943
    //
944
    // TODO: [2022/06/20] the `find` functions need consistent naming,
945
    //       `find_next`, `find_between`, `find_…` . The current design has
946
    //       the public-facing `find_` functions falling back on potential file-wide binary-search
947
    //       The binary-search only needs to be done during the stage 2. During stage 3, a simpler
948
    //       linear sequential search is more suitable, and more intuitive.
949
    //       More refactoring is in order.
950
    //       Also, a linear search can better detect rollover (i.e. when sysline datetime is missing year).
951
    // TODO: [2023/03/06] add stats tracking in `find` functions for number of
952
    //       "jumps" or bounces or fileoffset changes to confirm big-O
953
    #[inline(always)]
954
    pub fn find_sysline_between_datetime_filters(
3,975✔
955
        &mut self,
3,975✔
956
        fileoffset: FileOffset,
3,975✔
957
    ) -> ResultFindSysline {
3,975✔
958
        defn!("({})", fileoffset);
3,975✔
959

960
        let result = match self
3,975✔
961
            .syslinereader
3,975✔
962
            .find_sysline_between_datetime_filters(
3,975✔
963
                fileoffset,
3,975✔
964
                &self.filter_dt_after_opt,
3,975✔
965
                &self.filter_dt_before_opt,
3,975✔
966
            ) {
3,975✔
UNCOV
967
            ResultFindSysline::Err(err) => {
×
UNCOV
968
                self.set_error(&err);
×
969

UNCOV
970
                ResultFindSysline::Err(err)
×
971
            }
972
            val => val,
3,975✔
973
        };
974

975
        defx!("({})", fileoffset);
3,975✔
976

977
        result
3,975✔
978
    }
3,975✔
979

980
    /// Wrapper function for a recurring sanity check.
981
    ///
982
    /// Good for checking functions `process_stage…` are called in
983
    /// the correct order.
984
    // XXX: is there a rust-ic way to enforce stage procession behavior
985
    //      at compile-time? It's a fairly simple enumerated type. Could a
986
    //      `match` tree (or something like that) be used?
987
    //      run-time checks of rust enum values seems hacky.
988
    #[inline(always)]
989
    fn assert_stage(
3,465✔
990
        &self,
3,465✔
991
        stage_expact: ProcessingStage,
3,465✔
992
    ) {
3,465✔
993
        debug_assert_eq!(
3,465✔
994
            self.processingstage, stage_expact,
995
            "Unexpected Processing Stage {:?}, expected Processing Stage {:?}",
996
            self.processingstage, stage_expact,
997
        );
998
    }
3,465✔
999

1000
    /// Stage 0 does some sanity checks on the file.
1001
    // TODO: this is redundant and has already been performed by functions in
1002
    //       `filepreprocessor` and `BlockReader::new`.
1003
    pub fn process_stage0_valid_file_check(&mut self) -> FileProcessingResultBlockZero {
476✔
1004
        defn!();
476✔
1005
        // sanity check calls are in correct order
1006
        self.assert_stage(ProcessingStage::Stage0ValidFileCheck);
476✔
1007
        self.processingstage = ProcessingStage::Stage0ValidFileCheck;
476✔
1008

1009
        if self.filesz() == 0 {
476✔
1010
            defx!("filesz 0; return {:?}", FileProcessingResultBlockZero::FileErrEmpty);
2✔
1011
            return FileProcessingResultBlockZero::FileErrEmpty;
2✔
1012
        }
474✔
1013
        defx!("return {:?}", FileProcessingResultBlockZero::FileOk);
474✔
1014

1015
        FileProcessingResultBlockZero::FileOk
474✔
1016
    }
476✔
1017

1018
    /// Stage 1: Can [`Line`s] and [`Sysline`s] be parsed from the first block
1019
    /// (block zero)?
1020
    ///
1021
    /// [`Sysline`s]: crate::data::sysline::Sysline
1022
    /// [`Line`s]: crate::data::line::Line
1023
    pub fn process_stage1_blockzero_analysis(&mut self) -> FileProcessingResultBlockZero {
473✔
1024
        defn!();
473✔
1025
        self.assert_stage(ProcessingStage::Stage0ValidFileCheck);
473✔
1026
        self.processingstage = ProcessingStage::Stage1BlockzeroAnalysis;
473✔
1027

1028
        let result: FileProcessingResultBlockZero = self.blockzero_analysis();
473✔
1029
        // stored syslines may be zero if a "partial" `Line` was examined
1030
        // e.g. an incomplete and temporary `Line` instance was examined.
1031
        defo!(
473✔
1032
            "blockzero_analysis() stored syslines {}",
1033
            self.syslinereader
473✔
1034
                .count_syslines_stored()
473✔
1035
        );
1036
        match result {
473✔
1037
            FileProcessingResult::FileOk => {}
345✔
1038
            // skip further processing if not `FileOk`
1039
            _ => {
1040
                defx!("return {:?}", result);
128✔
1041
                return result;
128✔
1042
            }
1043
        }
1044

1045
        defx!("return {:?}", result);
345✔
1046

1047
        result
345✔
1048
    }
473✔
1049

1050
    /// Stage 2: Given the an optional datetime filter (user-passed
1051
    /// `--dt-after`), can a log message with a datetime after that filter be
1052
    /// found?
1053
    pub fn process_stage2_find_dt(
318✔
1054
        &mut self,
318✔
1055
        filter_dt_after_opt: &DateTimeLOpt,
318✔
1056
    ) -> FileProcessingResultBlockZero {
318✔
1057
        defn!();
318✔
1058
        self.assert_stage(ProcessingStage::Stage1BlockzeroAnalysis);
318✔
1059
        self.processingstage = ProcessingStage::Stage2FindDt;
318✔
1060

1061
        // datetime formats without a year requires special handling
1062
        if !self.syslinereader.dt_pattern_has_year() &&
318✔
1063
            !self.syslinereader.dt_pattern_uptime()
14✔
1064
        {
1065
            defo!("!dt_pattern_has_year() && !dt_pattern_uptime()");
14✔
1066
            let mtime: SystemTime = self.mtime();
14✔
1067
            match self.process_missing_year(mtime, filter_dt_after_opt) {
14✔
1068
                FileProcessingResultBlockZero::FileOk => {}
14✔
1069
                result => {
×
1070
                    defx!("Bad result {:?}", result);
×
UNCOV
1071
                    return result;
×
1072
                }
1073
            }
1074
        } else if self.syslinereader.dt_pattern_uptime() {
304✔
UNCOV
1075
            defo!("dt_pattern_uptime()");
×
UNCOV
1076
            match self.process_uptime() {
×
UNCOV
1077
                FileProcessingResultBlockZero::FileOk => {}
×
UNCOV
1078
                result => {
×
UNCOV
1079
                    defx!("Bad result {:?}", result);
×
UNCOV
1080
                    return result;
×
1081
                }
1082
            }
1083
        }
304✔
1084

1085
        defx!();
318✔
1086

1087
        FileProcessingResultBlockZero::FileOk
318✔
1088
    }
318✔
1089

1090
    /// Stage 3: during "[streaming]", processed and printed data stored by
1091
    /// underlying "Readers" is proactively dropped
1092
    /// (removed from process memory).
1093
    ///
1094
    /// Also see [`find_sysline`].
1095
    ///
1096
    /// [streaming]: ProcessingStage#variant.Stage3StreamSyslines
1097
    /// [`find_sysline`]: self::SyslogProcessor#method.find_sysline
1098
    pub fn process_stage3_stream_syslines(&mut self) -> FileProcessingResultBlockZero {
314✔
1099
        defñ!();
314✔
1100
        self.assert_stage(ProcessingStage::Stage2FindDt);
314✔
1101
        self.processingstage = ProcessingStage::Stage3StreamSyslines;
314✔
1102
        self.syslinereader.disable_range_lookups();
314✔
1103

1104
        FileProcessingResultBlockZero::FileOk
314✔
1105
    }
314✔
1106

1107
    /// Stage 4: no more [`Sysline`s] to process. Create and return a
1108
    /// [`Summary`].
1109
    ///
1110
    /// [`Summary`]: crate::readers::summary::Summary
1111
    /// [`Sysline`s]: crate::data::sysline::Sysline
1112
    pub fn process_stage4_summary(&mut self) -> Summary {
311✔
1113
        defñ!();
311✔
1114
        // XXX: this can be called from various stages, no need to assert
1115
        self.processingstage = ProcessingStage::Stage4Summary;
311✔
1116

1117
        self.summary_complete()
311✔
1118
    }
311✔
1119

1120
    /// Review bytes in the first block ("zero block").
1121
    /// If enough `Line` found then return [`FileOk`]
1122
    /// else return [`FileErrNoLinesFound`].
1123
    ///
1124
    /// [`FileOk`]: self::FileProcessingResultBlockZero
1125
    /// [`FileErrNoLinesFound`]: self::FileProcessingResultBlockZero
1126
    pub(super) fn blockzero_analysis_bytes(&mut self) -> FileProcessingResultBlockZero {
473✔
1127
        defn!();
473✔
1128
        self.assert_stage(ProcessingStage::Stage1BlockzeroAnalysis);
473✔
1129

1130
        let blockp: BlockP = match self
473✔
1131
            .syslinereader
473✔
1132
            .linereader
473✔
1133
            .blockreader
473✔
1134
            .read_block(0)
473✔
1135
        {
1136
            ResultFindReadBlock::Found(blockp_) => blockp_,
473✔
UNCOV
1137
            ResultFindReadBlock::Done => {
×
UNCOV
1138
                defx!("return FileErrEmpty");
×
UNCOV
1139
                return FileProcessingResultBlockZero::FileErrEmpty;
×
1140
            }
UNCOV
1141
            ResultFindReadBlock::Err(err) => {
×
UNCOV
1142
                self.set_error(&err);
×
UNCOV
1143
                defx!("return FileErrIo({:?})", err);
×
UNCOV
1144
                return FileProcessingResultBlockZero::FileErrIoPath(err);
×
1145
            }
1146
        };
1147
        // if the first block is too small then not a valid log file
1148
        let blocksz0: BlockSz = (*blockp).len() as BlockSz;
473✔
1149
        let require_sz: BlockSz = std::cmp::min(Self::BLOCKZERO_ANALYSIS_BYTES_MIN, self.blocksz());
473✔
1150
        defo!("blocksz0 {} < {} require_sz", blocksz0, require_sz);
473✔
1151
        if blocksz0 < require_sz {
473✔
1152
            defx!("return FileErrTooSmall");
9✔
1153
            return FileProcessingResultBlockZero::FileErrTooSmall;
9✔
1154
        }
464✔
1155
        // if all 0x00 bytes then not valid log file
1156
        if (*blockp).iter().take(Self::BLOCKZERO_ANALYSIS_BYTES_NULL_MAX).all(|&b| b == 0) {
666✔
1157
            defx!("return FileErrNullBytes");
2✔
1158
            return FileProcessingResultBlockZero::FileErrNullBytes;
2✔
1159
        }
462✔
1160
        // if all 0xFF bytes then not valid log file
1161
        if (*blockp).iter().take(Self::BLOCKZERO_ANALYSIS_BYTES_FF_MAX).all(|&b| b == 0xFF) {
482✔
UNCOV
1162
            defx!("return FileErrFFBytes");
×
1163
            return FileProcessingResultBlockZero::FileErrFFBytes;
×
1164
        }
462✔
1165
        // determine file encoding
1166
        let block_bytes = blockp.as_slice();
462✔
1167
        let encoding_type: FileTypeTextEncoding = match detect_filetype_text_encoding(block_bytes) {
462✔
1168
            Some(enc) => {
462✔
1169
                defo!("detected encoding {:?}", enc);
462✔
1170
                enc
462✔
1171
            },
1172
            None => {
UNCOV
1173
                defo!("could not detect valid encoding; fallback to UTF-8/ASCII");
×
UNCOV
1174
                FileTypeTextEncoding::Utf8Ascii
×
1175
            }
1176
        };
1177

1178
        // TODO: here is where to detect magic bytes for
1179
        //       unsupported file types, e.g. databases, unsupported compressed, etc.
1180

1181
        self.syslinereader.filetype_text_encoding_update(encoding_type);
462✔
1182

1183
        defx!("return FileOk");
462✔
1184

1185
        FileProcessingResultBlockZero::FileOk
462✔
1186
    }
473✔
1187

1188
    /// Attempt to find a minimum number of [`Line`s] within the first block
1189
    /// (block zero).
1190
    /// If enough `Line` found then return [`FileOk`]
1191
    /// else return [`FileErrNoLinesFound`].
1192
    ///
1193
    /// [`Line`s]: crate::data::line::Line
1194
    /// [`FileOk`]: self::FileProcessingResultBlockZero
1195
    /// [`FileErrNoLinesFound`]: self::FileProcessingResultBlockZero
1196
    pub(super) fn blockzero_analysis_lines(&mut self) -> FileProcessingResultBlockZero {
462✔
1197
        defn!();
462✔
1198
        self.assert_stage(ProcessingStage::Stage1BlockzeroAnalysis);
462✔
1199

1200
        let blockp: BlockP = match self
462✔
1201
            .syslinereader
462✔
1202
            .linereader
462✔
1203
            .blockreader
462✔
1204
            .read_block(0)
462✔
1205
        {
1206
            ResultFindReadBlock::Found(blockp_) => blockp_,
462✔
UNCOV
1207
            ResultFindReadBlock::Done => {
×
UNCOV
1208
                defx!("return FileErrEmpty");
×
UNCOV
1209
                return FileProcessingResultBlockZero::FileErrEmpty;
×
1210
            }
UNCOV
1211
            ResultFindReadBlock::Err(err) => {
×
UNCOV
1212
                self.set_error(&err);
×
UNCOV
1213
                defx!("return FileErrIo({:?})", err);
×
UNCOV
1214
                return FileProcessingResultBlockZero::FileErrIoPath(err);
×
1215
            }
1216
        };
1217
        let blocksz0: BlockSz = (*blockp).len() as BlockSz;
462✔
1218
        let mut _partial_found = false;
462✔
1219
        let mut fo: FileOffset = 0;
462✔
1220
        // how many lines have been found?
1221
        let mut found: Count = 0;
462✔
1222
        // must find at least this many lines in block zero to be FileOk
1223
        let found_min: Count = *BLOCKZERO_ANALYSIS_LINE_COUNT_MIN_MAP
462✔
1224
            .get(&blocksz0)
462✔
1225
            .unwrap();
462✔
1226
        defx!("block zero blocksz {} found_min {}", blocksz0, found_min);
462✔
1227
        // find `found_min` Lines or whatever can be found within block 0
1228
        while found < found_min {
936✔
1229
            fo = match self
502✔
1230
                .syslinereader
502✔
1231
                .linereader
502✔
1232
                .find_line_in_block(fo)
502✔
1233
            {
1234
                (ResultFindLine::Found((fo_next, _linep)), _) => {
475✔
1235
                    found += 1;
475✔
1236

1237
                    fo_next
475✔
1238
                }
1239
                (ResultFindLine::Done, partial) => {
27✔
1240
                    if partial.is_some() {
27✔
1241
                        found += 1;
27✔
1242
                        _partial_found = true;
27✔
1243
                    }
27✔
1244
                    break;
27✔
1245
                }
UNCOV
1246
                (ResultFindLine::Err(err), _) => {
×
UNCOV
1247
                    self.set_error(&err);
×
UNCOV
1248
                    defx!("return FileErrIo({:?})", err);
×
UNCOV
1249
                    return FileProcessingResultBlockZero::FileErrIoPath(err);
×
1250
                }
1251
            };
1252
            if 0 != self
475✔
1253
                .syslinereader
475✔
1254
                .linereader
475✔
1255
                .block_offset_at_file_offset(fo)
475✔
1256
            {
1257
                break;
1✔
1258
            }
474✔
1259
        }
1260

1261
        let fpr: FileProcessingResultBlockZero = if found >= found_min {
462✔
1262
            FileProcessingResultBlockZero::FileOk
462✔
1263
        } else {
UNCOV
1264
            FileProcessingResultBlockZero::FileErrNoLinesFound
×
1265
        };
1266

1267
        defx!("found {} lines, partial_found {}, require {} lines, return {:?}", found, _partial_found, found_min, fpr);
462✔
1268

1269
        fpr
462✔
1270
    }
462✔
1271

1272
    /// Attempt to find a minimum number of [`Sysline`] within the first block.
1273
    /// If enough `Sysline` found then return [`FileOk`]
1274
    /// else return [`FileErrNoSyslinesFound`].
1275
    ///
1276
    /// [`Sysline`]: crate::data::sysline::Sysline
1277
    /// [`FileOk`]: self::FileProcessingResultBlockZero
1278
    /// [`FileErrNoSyslinesFound`]: self::FileProcessingResultBlockZero
1279
    pub(super) fn blockzero_analysis_syslines(&mut self) -> FileProcessingResultBlockZero {
462✔
1280
        defn!();
462✔
1281
        self.assert_stage(ProcessingStage::Stage1BlockzeroAnalysis);
462✔
1282

1283
        let blockp: BlockP = match self
462✔
1284
            .syslinereader
462✔
1285
            .linereader
462✔
1286
            .blockreader
462✔
1287
            .read_block(0)
462✔
1288
        {
1289
            ResultFindReadBlock::Found(blockp_) => blockp_,
462✔
UNCOV
1290
            ResultFindReadBlock::Done => {
×
UNCOV
1291
                defx!("return FileErrEmpty");
×
UNCOV
1292
                return FileProcessingResultBlockZero::FileErrEmpty;
×
1293
            }
UNCOV
1294
            ResultFindReadBlock::Err(err) => {
×
UNCOV
1295
                self.set_error(&err);
×
UNCOV
1296
                defx!("return FileErrIo({:?})", err);
×
UNCOV
1297
                return FileProcessingResultBlockZero::FileErrIoPath(err);
×
1298
            }
1299
        };
1300
        let blocksz0: BlockSz = (*blockp).len() as BlockSz;
462✔
1301
        let mut fo: FileOffset = 0;
462✔
1302
        // how many syslines have been found?
1303
        let mut found: Count = 0;
462✔
1304
        // must find at least this many syslines in block zero to be FileOk
1305
        let found_min: Count = *BLOCKZERO_ANALYSIS_SYSLINE_COUNT_MIN_MAP
462✔
1306
            .get(&blocksz0)
462✔
1307
            .unwrap();
462✔
1308
        defo!("block zero blocksz {} found_min {:?}", blocksz0, found_min);
462✔
1309

1310
        // find `at_max` Syslines within block zero
1311
        while found < found_min
817✔
1312
            && self.syslinereader.block_offset_at_file_offset(fo) == 0
482✔
1313
        {
1314
            fo = match self
482✔
1315
                .syslinereader
482✔
1316
                .find_sysline_in_block(fo)
482✔
1317
            {
1318
                (ResultFindSysline::Found((fo_next, _slinep)), _) => {
355✔
1319
                    found += 1;
355✔
1320
                    defo!("Found; found {} syslines, fo_next {}", found, fo_next);
355✔
1321

1322
                    fo_next
355✔
1323
                }
1324
                (ResultFindSysline::Done, partial_found) => {
127✔
1325
                    defo!("Done; found {} syslines, partial_found {}", found, partial_found);
127✔
1326
                    if partial_found
127✔
1327
                        && (self.syslinereader.count_lines_stored() > 0
10✔
UNCOV
1328
                            || self.syslinereader.charsz() > 1)
×
1329
                    {
10✔
1330
                        found += 1;
10✔
1331
                    }
117✔
1332
                    break;
127✔
1333
                }
UNCOV
1334
                (ResultFindSysline::Err(err), _) => {
×
UNCOV
1335
                    self.set_error(&err);
×
UNCOV
1336
                    defx!("return FileErrIo({:?})", err);
×
UNCOV
1337
                    return FileProcessingResultBlockZero::FileErrIoPath(err);
×
1338
                }
1339
            };
1340
        }
1341

1342
        if found == 0 {
462✔
1343
            defx!("found {} syslines, require {} syslines, return FileErrNoSyslinesFound", found, found_min);
117✔
1344
            return FileProcessingResultBlockZero::FileErrNoSyslinesFound;
117✔
1345
        }
345✔
1346

1347
        let patt_count_a = self.syslinereader.dt_patterns_counts_in_use();
345✔
1348
        defo!("dt_patterns_counts_in_use {}", patt_count_a);
345✔
1349

1350
        if patt_count_a == 0 && self.syslinereader.charsz() > 1 {
345✔
1351
            // For UTF-16/UTF-32 with small block sizes, block zero may only
1352
            // produce a partial line and no complete datetime match yet.
1353
            // Defer datetime pattern narrowing to later full-file processing.
UNCOV
1354
            defo!("multibyte block-zero partial detected; skip dt_patterns_analysis and continue");
×
UNCOV
1355
            return FileProcessingResultBlockZero::FileOk;
×
1356
        }
345✔
1357

1358
        if !self.syslinereader.dt_patterns_analysis() {
345✔
UNCOV
1359
            de_err!("dt_patterns_analysis() failed which is unexpected; return FileErrNoSyslinesFound");
×
UNCOV
1360
            return FileProcessingResultBlockZero::FileErrNoSyslinesFound;
×
1361
        }
345✔
1362

1363
        let _patt_count_b = self.syslinereader.dt_patterns_counts_in_use();
345✔
1364
        debug_assert_eq!(
345✔
1365
            _patt_count_b,
1366
            SyslogProcessor::DT_PATTERN_MAX,
1367
            "expected patterns to be reduced to {}, found {:?}",
1368
            SyslogProcessor::DT_PATTERN_MAX,
1369
            _patt_count_b,
1370
        );
1371

1372
        // if more than one `DateTimeParseInstr` was used then the syslines
1373
        // must be reparsed using the one chosen `DateTimeParseInstr`
1374
        if patt_count_a > 1 {
345✔
1375
            defo!("must reprocess all syslines using limited patterns (used {} DateTimeParseInstr; must only use {})!", patt_count_a, 1);
8✔
1376

1377
            self.syslinereader.clear_syslines();
8✔
1378
            // find `at_max` Syslines within block zero
1379
            found = 0;
8✔
1380
            fo = 0;
8✔
1381
            while found < found_min
11✔
1382
                && self.syslinereader.block_offset_at_file_offset(fo) == 0
8✔
1383
            {
1384
                fo = match self
8✔
1385
                    .syslinereader
8✔
1386
                    .find_sysline_in_block(fo)
8✔
1387
                {
1388
                    (ResultFindSysline::Found((fo_next, _slinep)), _) => {
3✔
1389
                        found += 1;
3✔
1390
                        defo!("Found; found {} syslines, fo_next {}", found, fo_next);
3✔
1391

1392
                        fo_next
3✔
1393
                    }
1394
                    (ResultFindSysline::Done, partial_found) => {
5✔
1395
                        defo!("Done; found {} syslines, partial_found {}", found, partial_found);
5✔
1396
                        if partial_found
5✔
1397
                            && (self.syslinereader.count_lines_stored() > 0
5✔
UNCOV
1398
                                || self.syslinereader.charsz() > 1)
×
1399
                        {
5✔
1400
                            found += 1;
5✔
1401
                        }
5✔
1402
                        break;
5✔
1403
                    }
UNCOV
1404
                    (ResultFindSysline::Err(err), _) => {
×
UNCOV
1405
                        self.set_error(&err);
×
UNCOV
1406
                        defx!("return FileErrIo({:?})", err);
×
UNCOV
1407
                        return FileProcessingResultBlockZero::FileErrIoPath(err);
×
1408
                    }
1409
                };
1410
            }
1411
            defo!("done reprocessing.");
8✔
1412
        } else {
1413
            defo!("no reprocess needed ({} DateTimeParseInstr)!", patt_count_a);
337✔
1414
        }
1415

1416
        let fpr: FileProcessingResultBlockZero = match found >= found_min {
345✔
1417
            true => FileProcessingResultBlockZero::FileOk,
345✔
UNCOV
1418
            false => FileProcessingResultBlockZero::FileErrNoSyslinesFound,
×
1419
        };
1420

1421
        // sanity check that only one `DateTimeParseInstr` is in use
1422
        if cfg!(debug_assertions) && self.syslinereader.dt_patterns_counts_in_use() != 1 {
345✔
1423
            de_wrn!(
×
UNCOV
1424
                "dt_patterns_counts_in_use() = {}, expected 1; for {:?}",
×
UNCOV
1425
                self.syslinereader.dt_patterns_counts_in_use(), self.path()
×
UNCOV
1426
            );
×
1427
        }
345✔
1428

1429
        if self.syslinereader.is_streamed_file()
345✔
1430
            && !self.syslinereader.dt_pattern_has_year()
50✔
1431
        {
UNCOV
1432
            self.syslinereader.linereader.blockreader.disable_drop_data();
×
UNCOV
1433
            debug_assert!(!self.is_drop_data(), "is_drop_data() should be false");
×
1434
        }
345✔
1435

1436
        defx!("found {} syslines, require {} syslines, return {:?}", found, found_min, fpr);
345✔
1437

1438
        fpr
345✔
1439
    }
462✔
1440

1441
    /// Call `self.blockzero_analysis_lines`.
1442
    /// If that passes then call `self.blockzero_analysis_syslines`.
1443
    pub(super) fn blockzero_analysis(&mut self) -> FileProcessingResultBlockZero {
473✔
1444
        defn!();
473✔
1445
        assert!(!self.blockzero_analysis_done, "blockzero_analysis_lines should only be completed once.");
473✔
1446
        self.blockzero_analysis_done = true;
473✔
1447
        self.assert_stage(ProcessingStage::Stage1BlockzeroAnalysis);
473✔
1448

1449
        if self.syslinereader.filesz() == 0 {
473✔
UNCOV
1450
            defx!("return FileErrEmpty");
×
UNCOV
1451
            return FileProcessingResultBlockZero::FileErrEmpty;
×
1452
        }
473✔
1453

1454
        let result: FileProcessingResultBlockZero = self.blockzero_analysis_bytes();
473✔
1455
        if !result.is_ok() {
473✔
1456
            defx!("syslinereader.blockzero_analysis_bytes() was !is_ok(), return {:?}", result);
11✔
1457
            return result;
11✔
1458
        };
462✔
1459

1460
        let result: FileProcessingResultBlockZero = self.blockzero_analysis_lines();
462✔
1461
        if !result.is_ok() {
462✔
UNCOV
1462
            defx!("syslinereader.blockzero_analysis() was !is_ok(), return {:?}", result);
×
UNCOV
1463
            return result;
×
1464
        };
462✔
1465

1466
        let result: FileProcessingResultBlockZero = self.blockzero_analysis_syslines();
462✔
1467
        defx!("return {:?}", result);
462✔
1468

1469
        result
462✔
1470
    }
473✔
1471

1472
    #[cfg(test)]
1473
    pub(crate) fn dropped_blocks(&self) -> SetDroppedBlocks {
3✔
1474
        self.syslinereader
3✔
1475
            .linereader
3✔
1476
            .blockreader
3✔
1477
            .dropped_blocks
3✔
1478
            .clone()
3✔
1479
    }
3✔
1480

1481
    #[cfg(test)]
1482
    pub(crate) fn dropped_lines(&self) -> SetDroppedLines {
3✔
1483
        self.syslinereader
3✔
1484
            .linereader
3✔
1485
            .dropped_lines
3✔
1486
            .clone()
3✔
1487
    }
3✔
1488

1489
    #[cfg(test)]
1490
    pub(crate) fn dropped_syslines(&self) -> SetDroppedSyslines {
3✔
1491
        self.syslinereader
3✔
1492
            .dropped_syslines
3✔
1493
            .clone()
3✔
1494
    }
3✔
1495

1496
    pub fn summary(&self) -> SummarySyslogProcessor {
715✔
1497
        let syslogprocessor_missing_year = self.missing_year;
715✔
1498

1499
        SummarySyslogProcessor {
715✔
1500
            syslogprocessor_missing_year,
715✔
1501
        }
715✔
1502
    }
715✔
1503

1504
    /// Return an up-to-date [`Summary`] instance for this `SyslogProcessor`.
1505
    ///
1506
    /// Probably not useful or interesting before
1507
    /// `ProcessingStage::Stage4Summary`.
1508
    ///
1509
    /// [`Summary`]: crate::readers::summary::Summary
1510
    pub fn summary_complete(&self) -> Summary {
713✔
1511
        let path = self.path().clone();
713✔
1512
        let path_ntf = None;
713✔
1513
        let filetype = self.filetype();
713✔
1514
        let logmessagetype = filetype.to_logmessagetype();
713✔
1515
        let summaryblockreader = self.syslinereader.linereader.blockreader.summary();
713✔
1516
        let summarylinereader = self.syslinereader.linereader.summary();
713✔
1517
        let summarysyslinereader = self.syslinereader.summary();
713✔
1518
        let summarysyslogprocessor = self.summary();
713✔
1519
        let error: Option<String> = self.error.clone();
713✔
1520

1521
        Summary::new(
713✔
1522
            path,
713✔
1523
            path_ntf,
713✔
1524
            filetype,
713✔
1525
            logmessagetype,
713✔
1526
            Some(summaryblockreader),
713✔
1527
            Some(summarylinereader),
713✔
1528
            Some(summarysyslinereader),
713✔
1529
            Some(summarysyslogprocessor),
713✔
1530
            None,
713✔
1531
            None,
713✔
1532
            None,
713✔
1533
            None,
713✔
1534
            error,
713✔
1535
        )
1536
    }
713✔
1537
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc