### Resulting files after compression example Source: https://docs.rs/file-rotate/latest/file_rotate Shows the files created by the compression example. The two oldest files are compressed with a `.gz` suffix, while the two most recent are not. ```text log log.20220112T112415.gz log.20220112T112416.gz log.20220112T112417 log.20220112T112418 ``` -------------------------------- ### Compression example with FileRotate and Compression::OnRotate Source: https://docs.rs/file-rotate/latest/file_rotate/index.html Creates a rotating log file with compression enabled. The example writes six bytes with a one-second delay, producing the listed files and demonstrating how to reassemble the log data using gunzip and cat. ```rust use file_rotate::{compression::*, suffix::*, *}; use std::io::Write; let mut log = FileRotate::new( "./log", AppendTimestamp::default(FileLimit::MaxFiles(4)), ContentLimit::Bytes(1), Compression::OnRotate(2), None, ); for i in 0..6 { write!(log, "{}", i).unwrap(); std::thread::sleep(std::time::Duration::from_secs(1)); } ``` ```text log log.20220112T112415.gz log.20220112T112416.gz log.20220112T112417 log.20220112T112418 ``` ```bash $ gunzip -c log.20220112T112415.gz ; gunzip -c log.20220112T112416.gz ; cat log.20220112T112417 log.20220112T112418 log 12345 ``` -------------------------------- ### AppendTimestamp rotation example Source: https://docs.rs/file-rotate/latest/file_rotate Shows how to use AppendTimestamp with a maximum file count. When the limit is reached, the file is moved with a timestamp suffix. The example writes single characters and asserts the resulting file contents using log.log_paths(). ```rust use file_rotate::{FileRotate, ContentLimit, suffix::{AppendTimestamp, FileLimit}, compression::Compression}; use std::{fs, io::Write}; let log_path = directory.join("my-log-file"); let mut log = FileRotate::new( log_path.clone(), AppendTimestamp::default(FileLimit::MaxFiles(2)), ContentLimit::Bytes(1), Compression::None, None, ); write!(log, "A"); assert_eq!("A", fs::read_to_string(&log_path).unwrap()); write!(log, "B"); assert_eq!("A", fs::read_to_string(&log.log_paths()[0]).unwrap()); assert_eq!("B", fs::read_to_string(&log_path).unwrap()); write!(log, "C"); assert_eq!("A", fs::read_to_string(&log.log_paths()[0]).unwrap()); assert_eq!("B", fs::read_to_string(&log.log_paths()[1]).unwrap()); assert_eq!("C", fs::read_to_string(&log_path).unwrap()); write!(log, "D"); assert_eq!("B", fs::read_to_string(&log.log_paths()[0]).unwrap()); assert_eq!("C", fs::read_to_string(&log.log_paths()[1]).unwrap()); assert_eq!("D", fs::read_to_string(&log_path).unwrap()); ``` -------------------------------- ### SuffixInfo example with TimestampSuffix Source: https://docs.rs/file-rotate/latest/src/file_rotate/lib.rs.html Shows a SuffixInfo array with TimestampSuffix values and compressed flags, used to assemble log history. ```rust //! suffix: TimestampSuffix { //! timestamp: "20220112T112416", //! number: None, //! }, //! compressed: true, //! }, //! SuffixInfo { //! suffix: TimestampSuffix { //! timestamp: "20220112T112415", //! number: None, //! }, //! compressed: true, //! }, //! } ``` -------------------------------- ### AppendCount rotation example Source: https://docs.rs/file-rotate/latest/file_rotate Shows how to use AppendCount with a 1-byte content limit. When the limit is reached, the file is moved with suffix .1 and subsequent files cascade. The example writes single characters and asserts the resulting file contents. ```rust use file_rotate::{FileRotate, ContentLimit, suffix::AppendCount, compression::Compression}; use std::{fs, io::Write}; let log_path = directory.join("my-log-file"); let mut log = FileRotate::new( log_path.clone(), AppendCount::new(3), ContentLimit::Bytes(1), Compression::None, None, ); write!(log, "A"); assert_eq!("A", fs::read_to_string(&log_path).unwrap()); write!(log, "B"); assert_eq!("A", fs::read_to_string(directory.join("my-log-file.1")).unwrap()); assert_eq!("B", fs::read_to_string(&log_path).unwrap()); write!(log, "C"); assert_eq!("A", fs::read_to_string(directory.join("my-log-file.2")).unwrap()); assert_eq!("B", fs::read_to_string(directory.join("my-log-file.1")).unwrap()); assert_eq!("C", fs::read_to_string(&log_path).unwrap()); write!(log, "D"); assert_eq!("A", fs::read_to_string(directory.join("my-log-file.3")).unwrap()); assert_eq!("B", fs::read_to_string(directory.join("my-log-file.2")).unwrap()); assert_eq!("C", fs::read_to_string(directory.join("my-log-file.1")).unwrap()); assert_eq!("D", fs::read_to_string(&log_path).unwrap()); write!(log, "E"); assert_eq!("B", fs::read_to_string(directory.join("my-log-file.3")).unwrap()); assert_eq!("C", fs::read_to_string(directory.join("my-log-file.2")).unwrap()); assert_eq!("D", fs::read_to_string(directory.join("my-log-file.1")).unwrap()); assert_eq!("E", fs::read_to_string(&log_path).unwrap()); ``` -------------------------------- ### Rotating by Lines with ContentLimit::Lines Source: https://docs.rs/file-rotate/latest/file_rotate Creates a FileRotate writer that rotates after 3 lines, keeping up to 2 rotated files. The example writes 10 lines and asserts the contents of the current and rotated files. ```rust use file_rotate::{FileRotate, ContentLimit, suffix::AppendCount, compression::Compression}; use std::{fs, io::Write}; // Create a new log writer. The first argument is anything resembling a path. The // basename is used for naming the log files. // // Here we choose to limit logs by 10 lines, and have at most 2 rotated log files. This // makes the total amount of log files 3, since the original file is present as well. let log_path = directory.join("my-log-file"); let mut log = FileRotate::new( log_path.clone(), AppendCount::new(2), ContentLimit::Lines(3), Compression::None, None, ); // Write a bunch of lines writeln!(log, "Line 1: Hello World!"); for idx in 2..11 { writeln!(log, "Line {}", idx); } assert_eq!("Line 10\n", fs::read_to_string(&log_path).unwrap()); assert_eq!("Line 4\nLine 5\nLine 6\n", fs::read_to_string(&directory.join("my-log-file.2")).unwrap()); assert_eq!("Line 7\nLine 8\nLine 9\n", fs::read_to_string(&directory.join("my-log-file.1")).unwrap()); ``` -------------------------------- ### Rotating by Bytes with ContentLimit::Bytes Source: https://docs.rs/file-rotate/latest/file_rotate Creates a FileRotate writer that rotates after 5 bytes, keeping up to 2 rotated files. The example writes a short string and asserts the contents of the current and rotated files, then cleans up the directory. ```rust use file_rotate::{FileRotate, ContentLimit, suffix::AppendCount, compression::Compression}; use std::{fs, io::Write}; let log_path = directory.join("my-log-file"); let mut log = FileRotate::new( "target/my-log-directory-bytes/my-log-file", AppendCount::new(2), ContentLimit::Bytes(5), Compression::None, None, ); writeln!(log, "Test file"); assert_eq!("Test ", fs::read_to_string(&log.log_paths()[0]).unwrap()); assert_eq!("file\n", fs::read_to_string("target/my-log-directory-bytes/my-log-file").unwrap()); fs::remove_dir_all("target/my-log-directory-bytes"); ``` -------------------------------- ### Get structured list of log files with scan_suffixes Source: https://docs.rs/file-rotate/latest/file_rotate/index.html Scans the current directory for log files using SuffixScheme::scan_suffixes, which recognizes the extra .gz suffix and marks compressed files. The output shows the SuffixInfo list with timestamps and compression flags. ```rust println!( "{:#?}", AppendTimestamp::default(FileLimit::MaxFiles(4)).scan_suffixes(Path::new("./log")) ); ``` ```rust { SuffixInfo { suffix: TimestampSuffix { timestamp: "20220112T112418", number: None, }, compressed: false, }, SuffixInfo { suffix: TimestampSuffix { timestamp: "20220112T112417", number: None, }, compressed: false, }, SuffixInfo { suffix: TimestampSuffix { timestamp: "20220112T112416", number: None, }, compressed: true, }, SuffixInfo { suffix: TimestampSuffix { timestamp: "20220112T112415", number: None, }, compressed: true, }, } ``` -------------------------------- ### Scan log files with scan_suffixes Source: https://docs.rs/file-rotate/latest/file_rotate Uses AppendTimestamp::default(FileLimit::MaxFiles(4)).scan_suffixes(Path::new("./log")) to get a structured list of log files in the current directory. The method recognizes the `.gz` suffix and reports compression status. ```rust println!( "{:#?}", AppendTimestamp::default(FileLimit::MaxFiles(4)).scan_suffixes(Path::new("./log")) ); ``` -------------------------------- ### Create FileRotate with Compression::OnRotate(2) Source: https://docs.rs/file-rotate/latest/file_rotate Sets up a FileRotate that compresses rotated files using flate2. The Compression::OnRotate(2) keeps the two most recent files uncompressed and compresses older ones with a `.gz` suffix. Writes six single-byte entries with a one-second delay to trigger rotation. ```rust use file_rotate::{compression::*, suffix::*, *}; use std::io::Write; let mut log = FileRotate::new( "./log", AppendTimestamp::default(FileLimit::MaxFiles(4)), ContentLimit::Bytes(1), Compression::OnRotate(2), None, ); for i in 0..6 { write!(log, "{}", i).unwrap(); std::thread::sleep(std::time::Duration::from_secs(1)); } ``` -------------------------------- ### FileRotate::new constructor Source: https://docs.rs/file-rotate/latest/src/file_rotate/lib.rs.html Creates a new FileRotate. Panics if bytes or lines are zero. Requires open_options to have read, create, and append set. ```rust impl FileRotate { /// Create a new [FileRotate]. /// /// The basename of the `path` is used to create new log files by appending an extension of the /// form `.N`, where N is `0..=max_files`. /// /// `content_limit` specifies the limits for rotating a file. /// /// `open_options`: If provided, you must set `.read(true).create(true).append(true)`! /// /// # Panics /// /// Panics if `bytes == 0` or `lines == 0`. pub fn new>( path: P, suffix_scheme: S, content_limit: ContentLimit, compression: Compression, open_options: Option, ) -> Self { match content_limit { ContentLimit::Bytes(bytes) => { assert!(bytes > 0); } ContentLimit::Lines(lines) => { assert!(lines > 0); } ContentLimit::Time(_) => {} } } } ``` -------------------------------- ### Compression configuration and gzip implementation in file-rotate Source: https://docs.rs/file-rotate/latest/src/file_rotate/compression.rs.html Defines the Compression enum with None and OnRotate(usize) variants, and provides the compress function that gzips a file using flate2's GzEncoder. The function opens the source file, creates a .gz destination, copies data, and removes the original file. It asserts both source and destination exist before compression. ```rust 1//! Compression - configuration and implementation 2use flate2::write::GzEncoder; 3use std::{ 4 fs::{self, File, OpenOptions}, 5 io, 6 path::{Path, PathBuf}, 7}; 8 9/// Compression mode - when to compress files. 10#[derive(Debug, Clone)] 11pub enum Compression { 12 /// No compression 13 None, 14 /// Look for files to compress when rotating. 15 /// First argument: How many files to keep uncompressed (excluding the original file) 16 OnRotate(usize), 17} 18 19pub(crate) fn compress(path: &Path) -> io::Result<()> { 20 let dest_path = PathBuf::from(format!("{}.gz", path.display())); 21 22 let mut src_file = File::open(path)?; 23 let dest_file = OpenOptions::new() 24 .write(true) 25 .create(true) 26 .append(false) 27 .open(&dest_path)?; 28 29 assert!(path.exists()); 30 assert!(dest_path.exists()); 31 let mut encoder = GzEncoder::new(dest_file, flate2::Compression::default()); 32 io::copy(&mut src_file, &mut encoder)?; 33 34 fs::remove_file(path)?; 35 36 Ok(()) 37} ``` -------------------------------- ### FileRotate::new Source: https://docs.rs/file-rotate/latest/file_rotate/struct.FileRotate.html Creates a new FileRotate instance with the specified path, suffix scheme, content limit, compression, and optional open options. The basename of the path is used to create new log files by appending an extension of the form .N, where N is 0..=max_files. ```APIDOC ## FileRotate::new ### Description Creates a new FileRotate instance. The basename of the `path` is used to create new log files by appending an extension of the form `.N`, where N is `0..=max_files`. `content_limit` specifies the limits for rotating a file. `open_options`: If provided, you must set `.read(true).create(true).append(true)`! ### Method new ### Signature pub fn new>(path: P, suffix_scheme: S, content_limit: ContentLimit, compression: Compression, open_options: Option) -> Self ### Parameters - **path** (P) - Required - The base path for the log files. - **suffix_scheme** (S) - Required - The suffix scheme used for naming rotated files. - **content_limit** (ContentLimit) - Required - Specifies the limits for rotating a file. - **compression** (Compression) - Required - The compression method to use. - **open_options** (Option) - Optional - If provided, must have `.read(true).create(true).append(true)` set. ### Panics Panics if `bytes == 0` or `lines == 0`. ### Returns Self ``` -------------------------------- ### Handle old files: removal and compression Source: https://docs.rs/file-rotate/latest/src/file_rotate/lib.rs.html Internal method that removes suffixes that are too old based on the suffix scheme, and optionally compresses the oldest N files when Compression::OnRotate is set. Returns the result of file removal operations. ```rust fn handle_old_files(&mut self) -> io::Result<()> { // Find the youngest suffix that is too old, and then remove all suffixes that are older or // equally old: let mut youngest_old = None; // Start from oldest suffix, stop when we find a suffix that is not too old let mut result = Ok(()); for (i, suffix) in self.suffixes.iter().enumerate().rev() { if self.suffix_scheme.too_old(&suffix.suffix, i) { result = result.and(fs::remove_file(suffix.to_path(&self.basepath))); youngest_old = Some((*suffix).clone()); } else { break; } } if let Some(youngest_old) = youngest_old { // Removes all the too old let _ = self.suffixes.split_off(&youngest_old); } // Compression if let Compression::OnRotate(max_file_n) = self.compression { let n = (self.suffixes.len() as i32 - max_file_n as i32).max(0) as usize; // The oldest N files should be compressed let suffixes_to_compress = self .suffixes .iter() .rev() .take(n) .filter(|info| !info.compressed) .cloned() .collect::>(); for info in suffixes_to_compress { // Do the compression let path = info.suffix.to_path(&self.basepath); compress(&path)?; self.suffixes.replace(SuffixInfo { compressed: true, ..info }); } } result } ``` -------------------------------- ### AppendTimestamp with FileLimit::Age Source: https://docs.rs/file-rotate/latest/file_rotate Shows how to configure AppendTimestamp to delete files when they reach a certain age, using FileLimit::Age with a chrono::Duration. ```rust use file_rotate::suffix::{AppendTimestamp, FileLimit}; AppendTimestamp::default(FileLimit::Age(chrono::Duration::weeks(1))); ``` -------------------------------- ### Assemble log data with gunzip and cat Source: https://docs.rs/file-rotate/latest/file_rotate Command to decompress and concatenate the compressed and uncompressed log files to recover the original data. The output is `12345`. ```bash $ gunzip -c log.20220112T112415.gz ; gunzip -c log.20220112T112416.gz ; cat log.20220112T112417 log.20220112T112418 log 12345 ``` -------------------------------- ### ContentLimit::None handling Source: https://docs.rs/file-rotate/latest/src/file_rotate/lib.rs.html Simply writes the buffer to the file without any rotation checks. Used when no content limit is configured. ```rust ContentLimit::None => { if let Some(ref mut file) = self.file { file.write_all(buf)?; } } ``` -------------------------------- ### Manual rotation trigger Source: https://docs.rs/file-rotate/latest/src/file_rotate/lib.rs.html Public method to trigger log rotation manually, intended for use with ContentLimit::None but works with all content limits. Takes the current file, moves it with a new suffix, inserts the suffix, opens a new file, resets the count, and handles old files. ```rust /// Trigger a log rotation manually. This is mostly intended for use with `ContentLimit::None` /// but will work with all content limits. pub fn rotate(&mut self) -> io::Result<()> { self.ensure_log_directory_exists(); let _ = self.file.take(); // This function will always create a new file. Returns suffix of that file let new_suffix_info = self.move_file_with_suffix(None)?; self.suffixes.insert(new_suffix_info); self.open_file(); self.count = 0; self.handle_old_files()?; Ok(()) } ``` -------------------------------- ### pub fn to_path(&self, basepath: &Path) -> PathBuf Source: https://docs.rs/file-rotate/latest/file_rotate/struct.SuffixInfo.html Appends the suffix (and optional .gz) to a given base path, returning the resulting PathBuf. ```APIDOC ## pub fn to_path(&self, basepath: &Path) -> PathBuf ### Description Appends the suffix (and optional `.gz`) to a given base path, returning the resulting `PathBuf`. ### Method `to_path` ### Parameters - `basepath` (`&Path`) - The base path to which the suffix will be appended. ### Returns - `PathBuf` - The resulting path with the suffix appended. ### Example ```rust let info = SuffixInfo { suffix: "log", compressed: true }; let path = info.to_path(Path::new("/var/log/app")); assert_eq!(path, PathBuf::from("/var/log/app.log.gz")); ``` ``` -------------------------------- ### SuffixInfo struct and implementations Source: https://docs.rs/file-rotate/latest/src/file_rotate/lib.rs.html Defines SuffixInfo with suffix and compressed fields, including PartialEq, to_path, Ord, and PartialOrd implementations. ```rust /// Used mostly internally. Info about suffix + compressed state. #[derive(Clone, Debug, Eq)] pub struct SuffixInfo { /// Suffix pub suffix: Repr, /// Whether there is a `.gz` suffix after the suffix pub compressed: bool, } impl PartialEq for SuffixInfo { fn eq(&self, other: &Self) -> bool { self.suffix == other.suffix } } impl SuffixInfo { /// Append this suffix (and eventual `.gz`) to a path pub fn to_path(&self, basepath: &Path) -> PathBuf { let path = self.suffix.to_path(basepath); if self.compressed { PathBuf::from(format!("{}.gz", path.display())) } else { path } } } impl Ord for SuffixInfo { fn cmp(&self, other: &Self) -> Ordering { self.suffix.cmp(&other.suffix) } } impl PartialOrd for SuffixInfo { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } ``` -------------------------------- ### TimestampSuffix ordering tests Source: https://docs.rs/file-rotate/latest/src/file_rotate/suffix.rs.html Verifies the comparison logic for TimestampSuffix instances. ```rust @test fn timestamp_ordering() { assert!( TimestampSuffix { timestamp: "2021".to_string(), number: None } < TimestampSuffix { timestamp: "2020".to_string(), number: None } ); assert!( TimestampSuffix { timestamp: "2021".to_string(), number: Some(1) } < TimestampSuffix { timestamp: "2021".to_string(), number: None } ); } ``` -------------------------------- ### AppendCount Cascade with 1-Byte Limit Source: https://docs.rs/file-rotate/latest/src/file_rotate/lib.rs.html Demonstrates the AppendCount rotation cascade with a 1-byte limit and 3 rotated files. Each write triggers a rotation, moving the oldest content to a higher suffix and eventually discarding the oldest file. ```rust use file_rotate::{FileRotate, ContentLimit, suffix::AppendCount, compression::Compression}; use std::{fs, io::Write}; # let directory = tempfile::TempDir::new().unwrap(); # let directory = directory.path(); let log_path = directory.join("my-log-file"); let mut log = FileRotate::new( log_path.clone(), AppendCount::new(3), ContentLimit::Bytes(1), Compression::None, None, ); write!(log, "A"); assert_eq!("A", fs::read_to_string(&log_path).unwrap()); write!(log, "B"); assert_eq!("A", fs::read_to_string(directory.join("my-log-file.1")).unwrap()); assert_eq!("B", fs::read_to_string(&log_path).unwrap()); write!(log, "C"); assert_eq!("A", fs::read_to_string(directory.join("my-log-file.2")).unwrap()); assert_eq!("B", fs::read_to_string(directory.join("my-log-file.1")).unwrap()); assert_eq!("C", fs::read_to_string(&log_path).unwrap()); write!(log, "D"); assert_eq!("A", fs::read_to_string(directory.join("my-log-file.3")).unwrap()); assert_eq!("B", fs::read_to_string(directory.join("my-log-file.2")).unwrap()); assert_eq!("C", fs::read_to_string(directory.join("my-log-file.1")).unwrap()); assert_eq!("D", fs::read_to_string(&log_path).unwrap()); write!(log, "E"); assert_eq!("B", fs::read_to_string(directory.join("my-log-file.3")).unwrap()); assert_eq!("C", fs::read_to_string(directory.join("my-log-file.2")).unwrap()); assert_eq!("D", fs::read_to_string(directory.join("my-log-file.1")).unwrap()); ``` -------------------------------- ### Output of scan_suffixes Source: https://docs.rs/file-rotate/latest/file_rotate Shows the structured output of scan_suffixes, listing each file's suffix and whether it is compressed. This information can be used to assemble log history. ```text { SuffixInfo { suffix: TimestampSuffix { timestamp: "20220112T112418", number: None, }, compressed: false, }, SuffixInfo { suffix: TimestampSuffix { timestamp: "20220112T112417", number: None, }, compressed: false, }, SuffixInfo { suffix: TimestampSuffix { timestamp: "20220112T112416", number: None, }, compressed: true, }, SuffixInfo { suffix: TimestampSuffix { timestamp: "20220112T112415", number: None, }, compressed: true, }, } ``` -------------------------------- ### ContentLimit::Lines handling with line count rotation Source: https://docs.rs/file-rotate/latest/src/file_rotate/lib.rs.html Processes the buffer line by line, writing each line to the file, incrementing the line count, and rotating when the count reaches the configured limit. Handles partial lines at the end. ```rust ContentLimit::Lines(lines) => { while let Some((idx, _)) = buf.iter().enumerate().find(|(_, byte)| *byte == &b'\n') { if let Some(ref mut file) = self.file { file.write_all(&buf[..idx + 1])?; } self.count += 1; buf = &buf[idx + 1..]; if self.count >= lines { self.rotate()?; } } if let Some(ref mut file) = self.file { file.write_all(buf)?; } } ``` -------------------------------- ### Move file with suffix and handle conflicts Source: https://docs.rs/file-rotate/latest/src/file_rotate/lib.rs.html Internal method that moves a file to a new path with a suffix, handling conflicts by recursing to move the conflicting file. Uses fs::rename and asserts that the old path exists and the new path does not. ```rust // was replaced. Due to possible `.gz`, a "conflicting" file doesn't mean that paths // are equal. self.suffixes.replace(new_suffix_info); // Recurse to move conflicting file. self.move_file_with_suffix(Some(existing_suffix_info))? } else { new_suffix_info }; let old_path = match old_suffix_info { Some(suffix) => suffix.to_path(&self.basepath), None => self.basepath.clone(), }; // Do the move assert!(old_path.exists()); assert!(!new_path.exists()); fs::rename(old_path, new_path)?; Ok(newly_created_suffix) } ``` -------------------------------- ### now function for non-test and test builds Source: https://docs.rs/file-rotate/latest/src/file_rotate/lib.rs.html Returns the current local time. In non-test builds, uses Local::now(); in test builds, uses the mocked time from the mock_time module. ```rust #[cfg(not(test))] fn now() -> DateTime { Local::now() } ``` ```rust #[cfg(test)] pub mod mock_time { use super::*; use std::cell::RefCell; thread_local! { static MOCK_TIME: RefCell>> = RefCell::new(None); } /// Get current _mocked_ time pub fn now() -> DateTime { MOCK_TIME.with(|cell| cell.borrow().as_ref().cloned().unwrap_or_else(Local::now)) } /// Set mocked time pub fn set_mock_time(time: DateTime) { MOCK_TIME.with(|cell| *cell.borrow_mut() = Some(time)); } } #[cfg(test)] pub use mock_time::now; ``` -------------------------------- ### Suffix Scanning and Validation Loop Source: https://docs.rs/file-rotate/latest/src/file_rotate/suffix.rs.html Iterates through test cases, creates temporary files with various suffixes, and asserts that the scan_suffixes method correctly identifies the expected suffixes. ```rust for (i, case) in cases.iter().enumerate() { println!("Case {}", i); let tmp_dir = TempDir::new().unwrap(); let dir = tmp_dir.path(); let log_path = dir.join("file"); for suffix in case.suffixes.iter().chain(case.incorrect_suffixes) { File::create(dir.join(format!("file.{}", suffix))).unwrap(); } let scheme = AppendTimestamp::with_format( case.format, FileLimit::MaxFiles(1), DateFrom::DateYesterday, ); // Scan for suffixes let suffixes_set = scheme.scan_suffixes(&log_path); // Collect these suffixes, and the expected suffixes, into Vec, and sort let mut suffixes = suffixes_set .into_iter() .map(|x| x.suffix.to_string()) .collect::>(); suffixes.sort_unstable(); let mut expected_suffixes = case.suffixes.to_vec(); expected_suffixes.sort_unstable(); assert_eq!(suffixes, case.suffixes); println!("Passed\n"); } ``` -------------------------------- ### Write trait implementation with content limits Source: https://docs.rs/file-rotate/latest/src/file_rotate/lib.rs.html Implements the Write trait for FileRotate, handling ContentLimit::Bytes by rotating when the byte count exceeds the limit, and ContentLimit::Time by rotating based on time frequency (hourly, daily). Uses the now() function and DateTime. ```rust impl Write for FileRotate { fn write(&mut self, mut buf: &[ u8]) -> io::Result { let written = buf.len(); match self.content_limit { ContentLimit::Bytes(bytes) => { while self.count + buf.len() > bytes { let bytes_left = bytes.saturating_sub(self.count); if let Some(ref mut file) = self.file { file.write_all(&buf[..bytes_left])?; } self.rotate()?; buf = &buf[bytes_left..]; } self.count += buf.len(); if let Some(ref mut file) = self.file { file.write_all(buf)?; } } ContentLimit::Time(time) => { let local: DateTime = now(); if let Some(modified) = self.modified { match time { TimeFrequency::Hourly => { if local.hour() != modified.hour() || local.day() != modified.day() || local.month() != modified.month() || local.year() != modified.year() { self.rotate()?; } } TimeFrequency::Daily => { if local.date() > modified.date() { self.rotate()?; } } } } } } Ok(written) } } ``` -------------------------------- ### AppendTimestamp::with_format Source: https://docs.rs/file-rotate/latest/file_rotate/suffix/struct.AppendTimestamp.html Creates a new AppendTimestamp suffix scheme with a custom format, file limit, and date source. ```APIDOC ## AppendTimestamp::with_format ### Description Creates a new AppendTimestamp suffix scheme with a custom format, file limit, and date source. ### Method `pub fn with_format(format: &'static str, file_limit: FileLimit, date_from: DateFrom) -> Self` ### Parameters - **format** (&'static str) - Required - The format of the timestamp suffix. - **file_limit** (FileLimit) - Required - The file limit, e.g. when to delete an old file - by age (given by suffix) or by number of files. - **date_from** (DateFrom) - Required - Add timestamp from DateFrom. ### Returns - **Self** - A new AppendTimestamp instance. ``` -------------------------------- ### ContentLimit Enum Source: https://docs.rs/file-rotate/latest/file_rotate/enum.ContentLimit.html Defines the variants for log rotation conditions. Users can construct a ContentLimit value to configure file rotation behavior. ```APIDOC ## ContentLimit Enum ### Description Enum representing the condition on which a file is rotated. ### Variants - **Bytes(usize)** - Cut the log at the exact size in bytes. - **Lines(usize)** - Cut the log file at line breaks. - **Time(TimeFrequency)** - Cut the log at time interval. - **BytesSurpassed(usize)** - Cut the log file after surpassing size in bytes (but having written a complete buffer from a write call). - **None** - Don't do any rotation automatically. ### Usage Construct a `ContentLimit` value to specify the rotation condition when configuring file rotation. ### Example ```rust use file_rotate::ContentLimit; let limit = ContentLimit::Bytes(1024); ``` ``` -------------------------------- ### Enum FileLimit Source: https://docs.rs/file-rotate/latest/file_rotate/suffix/enum.FileLimit.html Defines the FileLimit enum with variants for limiting files by count, age, or unlimited. ```APIDOC ## Enum FileLimit ### Description How to determine whether a file should be deleted, in the case of AppendTimestamp. ### Variants - **MaxFiles(usize)** - Delete the oldest files if number of files is too high. - **Age(Duration)** - Delete files whose age exceeds the `Duration` - age is determined by the suffix of the file. - **Unlimited** - Never delete files. ### Usage This enum is used to configure file rotation behavior in the `file_rotate` crate. ``` -------------------------------- ### Suffix Parsing Test Cases Source: https://docs.rs/file-rotate/latest/src/file_rotate/suffix.rs.html Defines test cases with specific date formats, expected valid suffixes, and incorrect suffixes to validate the scanning logic. ```rust let cases = [ TestCase { format: "%Y%m%dT%H%M%S", suffixes: &["20220201T101010", "20220202T101010"], incorrect_suffixes: &["20220201T1010", "20220201T999999", "2022-02-02"], }, TestCase { format: "%Y-%m-%d", suffixes: &["2022-02-01", "2022-02-02"], incorrect_suffixes: &[ "abc", "2022-99-99", "2022-05", "2022", "20220202", "2022-02-02T112233", ], }, ]; ``` -------------------------------- ### Representation::to_path Source: https://docs.rs/file-rotate/latest/file_rotate/suffix/trait.Representation.html Provided method to create a path from a base path using the suffix representation. ```APIDOC ## Representation::to_path ### Description Creates a path from a base path using the suffix representation. ### Method fn to_path(&self, basepath: &Path) -> PathBuf ### Parameters - **basepath** (&Path) - The base path to which the suffix is appended. ### Returns - **PathBuf** - The resulting path. ### Example ```rust use std::path::Path; use file_rotate::suffix::Representation; let suffix = TimestampSuffix::default(); let path = suffix.to_path(Path::new("/var/log/app.log")); ``` ``` -------------------------------- ### FileRotate::rotate Source: https://docs.rs/file-rotate/latest/file_rotate/struct.FileRotate.html Triggers a log rotation manually. This is mostly intended for use with ContentLimit::None but will work with all content limits. ```APIDOC ## FileRotate::rotate ### Description Trigger a log rotation manually. This is mostly intended for use with `ContentLimit::None` but will work with all content limits. ### Method rotate ### Signature pub fn rotate(&mut self) -> Result<()> ### Parameters None ### Returns Result<()> - Ok if rotation succeeded, otherwise an error. ``` -------------------------------- ### TimestampSuffix parsing logic Source: https://docs.rs/file-rotate/latest/src/file_rotate/suffix.rs.html The parse method extracts timestamp and optional numeric components from a file suffix string. ```rust fn parse(&self, suffix: &str) -> Option { let (timestamp_str, n) = if let Some(dot) = suffix.find('.') { if let Ok(n) = suffix[(dot + 1)..].parse::() { (&suffix[..dot], Some(n)) } else { return None; } } else { (suffix, None) }; let success = match NaiveDateTime::parse_from_str(timestamp_str, self.format) { Ok(_) => true, Err(e) => e.kind() == ParseErrorKind::NotEnough, }; if success { Some(TimestampSuffix { timestamp: timestamp_str.to_string(), number: n, }) } else { None } } ``` -------------------------------- ### TimestampSuffix::to_path Source: https://docs.rs/file-rotate/latest/file_rotate/suffix/struct.TimestampSuffix.html Creates a path from the given base path and the timestamp suffix. This method is part of the Representation trait implementation. ```APIDOC ## TimestampSuffix::to_path ### Description Creates a path from the given base path and the timestamp suffix. This method is part of the Representation trait implementation. ### Method fn to_path(&self, basepath: &Path) -> PathBuf ### Parameters - **basepath** (&Path) - Required - The base path to which the suffix will be appended. ### Returns - **PathBuf** - The resulting path with the timestamp suffix appended. ``` -------------------------------- ### Compression Enum Source: https://docs.rs/file-rotate/latest/file_rotate/compression/enum.Compression.html Defines the compression modes for file rotation. Use None for no compression or OnRotate(usize) to compress on rotation, keeping a specified number of files uncompressed. ```APIDOC ## Compression Enum ### Description Defines the compression modes for file rotation. Use None for no compression or OnRotate(usize) to compress on rotation, keeping a specified number of files uncompressed. ### Definition ```rust pub enum Compression { None, OnRotate(usize), } ``` ### Variants - **None** - No compression. - **OnRotate(usize)** - Look for files to compress when rotating. First argument: How many files to keep uncompressed (excluding the original file). ### Trait Implementations - **Clone** - Provides clone and clone_from methods. - **Debug** - Provides fmt method for formatting. ### Auto Trait Implementations - Freeze - RefUnwindSafe - Send - Sync - Unpin - UnsafeUnpin - UnwindSafe ### Blanket Implementations - Any, Borrow, BorrowMut, CloneToUninit, From, Into, ToOwned, TryFrom, TryInto ``` -------------------------------- ### Time-based rotation logic for Weekly, Monthly, Yearly frequencies Source: https://docs.rs/file-rotate/latest/src/file_rotate/lib.rs.html Checks if the current local time has crossed a frequency boundary (week, month, or year) compared to the last modified time, and triggers rotation if so. Used within the write method. ```rust TimeFrequency::Weekly => { if local.iso_week().week() != modified.iso_week().week() || local.year() > modified.year() { self.rotate()?; } } TimeFrequency::Monthly => { if local.month() != modified.month() || local.year() != modified.year() { self.rotate()?; } } TimeFrequency::Yearly => { if local.year() > modified.year() { self.rotate()?; } } ``` -------------------------------- ### Rust crate attributes and imports Source: https://docs.rs/file-rotate/latest/src/file_rotate/lib.rs.html The crate-level deny attributes and imports used by the file_rotate library. ```rust #![deny( missing_docs, trivial_casts, trivial_numeric_casts, unsafe_code, unused_import_braces, unused_qualifications )] use chrono::prelude::*; use compression::*; use std::io::{BufRead, BufReader}; use std::{ cmp::Ordering, collections::BTreeSet, fs::{self, File, OpenOptions}, io::{self, Write}, path::{Path, PathBuf}, }; use suffix::*; pub mod compression; pub mod suffix; #[cfg(test)] mod tests; ``` -------------------------------- ### mtime function for non-test and test builds Source: https://docs.rs/file-rotate/latest/src/file_rotate/lib.rs.html Provides the modification time of a file. In non-test builds, reads from metadata; in test builds, returns the mocked current time. ```rust #[cfg(not(test))] fn mtime(file: &File) -> Option> { if let Ok(time) = file.metadata().and_then(|metadata| metadata.modified()) { return Some(time.into()); } None } ``` ```rust #[cfg(test)] fn mtime(_: &File) -> Option> { Some(now()) } ``` -------------------------------- ### Flush implementation for RotatingFileLogger Source: https://docs.rs/file-rotate/latest/src/file_rotate/lib.rs.html Flushes the underlying file if present, otherwise returns Ok. Implements the io::Write trait's flush method. ```rust fn flush(&mut self) -> io::Result<()> { self.file .as_mut() .map(|file| file.flush()) .unwrap_or(Ok(())) } ```