Mercurial > hg
view rust/hg-core/src/logging.rs @ 52307:22d24f6d6411
rust-lib: remove exports for not too common pattern-related types
This only muddies the lib and makes the imports more confusing.
author | Raphaël Gomès <rgomes@octobus.net> |
---|---|
date | Mon, 04 Nov 2024 11:26:41 +0100 |
parents | 7be39c5110c9 |
children |
line wrap: on
line source
use crate::errors::{HgError, HgResultExt, IoErrorContext, IoResultExt}; use crate::vfs::{Vfs, VfsImpl}; use std::io::Write; use std::path::Path; /// An utility to append to a log file with the given name, and optionally /// rotate it after it reaches a certain maximum size. /// /// Rotation works by renaming "example.log" to "example.log.1", after renaming /// "example.log.1" to "example.log.2" etc up to the given maximum number of /// files. pub struct LogFile<'a> { vfs: VfsImpl, name: &'a str, max_size: Option<u64>, max_files: u32, } impl<'a> LogFile<'a> { pub fn new(vfs: VfsImpl, name: &'a str) -> Self { Self { vfs, name, max_size: None, max_files: 0, } } /// Rotate before writing to a log file that was already larger than the /// given size, in bytes. `None` disables rotation. pub fn max_size(mut self, value: Option<u64>) -> Self { self.max_size = value; self } /// Keep this many rotated files `{name}.1` up to `{name}.{max}`, in /// addition to the original `{name}` file. pub fn max_files(mut self, value: u32) -> Self { self.max_files = value; self } /// Append the given `bytes` as-is to the log file, after rotating if /// needed. /// /// No trailing newline is added. Make sure to include one in `bytes` if /// desired. pub fn write(&self, bytes: &[u8]) -> Result<(), HgError> { let path = self.vfs.join(self.name); let context = || IoErrorContext::WritingFile(path.clone()); let open = || { std::fs::OpenOptions::new() .create(true) .append(true) .open(&path) .with_context(context) }; let mut file = open()?; if let Some(max_size) = self.max_size { if file.metadata().with_context(context)?.len() >= max_size { // For example with `max_files == 5`, the first iteration of // this loop has `i == 4` and renames `{name}.4` to `{name}.5`. // The last iteration renames `{name}.1` to // `{name}.2` for i in (1..self.max_files).rev() { self.vfs .rename( Path::new(&format!("{}.{}", self.name, i)), Path::new(&format!("{}.{}", self.name, i + 1)), false, ) .io_not_found_as_none()?; } // Then rename `{name}` to `{name}.1`. This is the // previously-opened `file`. self.vfs .rename( Path::new(&self.name), Path::new(&format!("{}.1", self.name)), false, ) .io_not_found_as_none()?; // Finally, create a new `{name}` file and replace our `file` // handle. file = open()?; } } file.write_all(bytes).with_context(context)?; file.sync_all().with_context(context) } } #[test] fn test_rotation() { let temp = tempfile::tempdir().unwrap(); let vfs = VfsImpl::new(temp.path().to_owned(), false); let logger = LogFile::new(vfs.clone(), "log") .max_size(Some(3)) .max_files(2); logger.write(b"one\n").unwrap(); logger.write(b"two\n").unwrap(); logger.write(b"3\n").unwrap(); logger.write(b"four\n").unwrap(); logger.write(b"five\n").unwrap(); assert_eq!(vfs.read("log").unwrap(), b"five\n"); assert_eq!(vfs.read("log.1").unwrap(), b"3\nfour\n"); assert_eq!(vfs.read("log.2").unwrap(), b"two\n"); assert!(vfs.read("log.3").io_not_found_as_none().unwrap().is_none()); }