-
Notifications
You must be signed in to change notification settings - Fork 731
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Size Based Rotating File Appender #865
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,80 +1,200 @@ | ||
use std::io::{BufWriter, Write}; | ||
use std::io::BufWriter; | ||
use std::{fs, io}; | ||
|
||
use crate::rolling::Rotation; | ||
use crate::rolling::Rotation as Roll; | ||
use crate::rotating::Rotation; | ||
|
||
use chrono::prelude::*; | ||
use std::fmt::Debug; | ||
use std::fs::{File, OpenOptions}; | ||
use std::marker::PhantomData; | ||
use std::path::Path; | ||
|
||
pub(crate) trait InnerAppenderTrait<R>: io::Write | ||
where | ||
Self: Sized, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe specifying this will prevent making the trait into an object https://doc.rust-lang.org/std/marker/trait.Sized.html I may be wrong though. I've never used There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i need it for Result |
||
{ | ||
fn new(rotation: R, log_directory: &Path, log_filename_prefix: &Path) -> io::Result<Self>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tying the constructor to a trait method will make the code brittle. The constructor should only be implemented by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i have to have this function since |
||
|
||
fn refresh_writer(&mut self, size: &usize); | ||
} | ||
|
||
#[derive(Debug)] | ||
pub(crate) struct InnerAppenderWrapper<R, T: InnerAppenderTrait<R>> { | ||
appender: T, | ||
phantom: PhantomData<R>, | ||
} | ||
impl<R, T: InnerAppenderTrait<R>> InnerAppenderWrapper<R, T> { | ||
Comment on lines
+26
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit let's add a line break here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will fix |
||
pub(crate) fn new( | ||
rotation: R, | ||
log_directory: &Path, | ||
log_filename_prefix: &Path, | ||
) -> io::Result<Self> { | ||
let appender = T::new(rotation, log_directory, log_filename_prefix)?; | ||
Ok(Self { | ||
appender, | ||
phantom: PhantomData, | ||
}) | ||
} | ||
} | ||
|
||
impl<R, T: InnerAppenderTrait<R>> io::Write for InnerAppenderWrapper<R, T> { | ||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | ||
let buf_len = buf.len(); | ||
self.appender.refresh_writer(&buf_len); | ||
self.appender.write(buf) | ||
} | ||
|
||
fn flush(&mut self) -> io::Result<()> { | ||
self.appender.flush() | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
pub(crate) struct InnerAppender { | ||
pub(crate) struct InnerRollingAppender { | ||
log_directory: String, | ||
log_filename_prefix: String, | ||
writer: BufWriter<File>, | ||
next_date: DateTime<Utc>, | ||
rotation: Rotation, | ||
roll: Roll, | ||
} | ||
|
||
impl io::Write for InnerAppender { | ||
impl io::Write for InnerRollingAppender { | ||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | ||
let now = Utc::now(); | ||
self.write_timestamped(buf, now) | ||
let buf_len = buf.len(); | ||
self.writer.write_all(buf).map(|_| buf_len) | ||
} | ||
|
||
fn flush(&mut self) -> io::Result<()> { | ||
self.writer.flush() | ||
} | ||
} | ||
|
||
impl InnerAppender { | ||
pub(crate) fn new( | ||
log_directory: &Path, | ||
log_filename_prefix: &Path, | ||
rotation: Rotation, | ||
now: DateTime<Utc>, | ||
) -> io::Result<Self> { | ||
impl InnerAppenderTrait<Roll> for InnerRollingAppender { | ||
fn new(roll: Roll, log_directory: &Path, log_filename_prefix: &Path) -> io::Result<Self> { | ||
let now = Utc::now(); | ||
let log_directory = log_directory.to_str().unwrap(); | ||
let log_filename_prefix = log_filename_prefix.to_str().unwrap(); | ||
|
||
let filename = rotation.join_date(log_filename_prefix, &now); | ||
let next_date = rotation.next_date(&now); | ||
let filename = roll.join_date(log_filename_prefix, &now); | ||
let next_date = roll.next_date(&now); | ||
|
||
Ok(InnerAppender { | ||
Ok(InnerRollingAppender { | ||
log_directory: log_directory.to_string(), | ||
log_filename_prefix: log_filename_prefix.to_string(), | ||
writer: create_writer(log_directory, &filename)?, | ||
next_date, | ||
rotation, | ||
roll, | ||
}) | ||
} | ||
|
||
fn write_timestamped(&mut self, buf: &[u8], date: DateTime<Utc>) -> io::Result<usize> { | ||
// Even if refresh_writer fails, we still have the original writer. Ignore errors | ||
// and proceed with the write. | ||
let buf_len = buf.len(); | ||
self.refresh_writer(date); | ||
self.writer.write_all(buf).map(|_| buf_len) | ||
} | ||
fn refresh_writer(&mut self, _size: &usize) { | ||
let now = Utc::now(); | ||
|
||
fn refresh_writer(&mut self, now: DateTime<Utc>) { | ||
if self.should_rollover(now) { | ||
let filename = self.rotation.join_date(&self.log_filename_prefix, &now); | ||
let filename = self.roll.join_date(&self.log_filename_prefix, &now); | ||
|
||
self.next_date = self.rotation.next_date(&now); | ||
self.next_date = self.roll.next_date(&now); | ||
|
||
match create_writer(&self.log_directory, &filename) { | ||
Ok(writer) => self.writer = writer, | ||
Err(err) => eprintln!("Couldn't create writer for logs: {}", err), | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl InnerRollingAppender { | ||
fn should_rollover(&self, date: DateTime<Utc>) -> bool { | ||
date >= self.next_date | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
pub(crate) struct InnerRotatingAppender { | ||
log_directory: String, | ||
log_filename_prefix: String, | ||
writer: BufWriter<File>, | ||
last_backup: usize, | ||
current_size: usize, | ||
rotation: Rotation, | ||
} | ||
|
||
impl io::Write for InnerRotatingAppender { | ||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | ||
let buf_len = buf.len(); | ||
self.writer.write_all(buf).map(|_| buf_len) | ||
} | ||
|
||
fn flush(&mut self) -> io::Result<()> { | ||
self.writer.flush() | ||
} | ||
Comment on lines
+122
to
+130
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need to implement io::Write again for each appender impl. If you add a default method There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i will still have to create these methods for each appender, I can't create a default method that will access the appenders buffers. |
||
} | ||
|
||
impl InnerAppenderTrait<Rotation> for InnerRotatingAppender { | ||
fn new( | ||
rotation: Rotation, | ||
log_directory: &Path, | ||
log_filename_prefix: &Path, | ||
) -> io::Result<Self> { | ||
let log_directory = log_directory.to_str().unwrap(); | ||
let log_filename_prefix = log_filename_prefix.to_str().unwrap(); | ||
let current_size = get_file_size(log_directory, log_filename_prefix)?; | ||
let last_backup = Self::find_last_backup(&rotation, log_directory, log_filename_prefix); | ||
Ok(Self { | ||
writer: create_writer(log_directory, log_filename_prefix)?, | ||
log_directory: log_directory.to_string(), | ||
log_filename_prefix: log_filename_prefix.to_string(), | ||
last_backup, | ||
current_size, | ||
rotation, | ||
}) | ||
} | ||
|
||
fn refresh_writer(&mut self, size: &usize) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: A new line between methods could be added in a few places. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will fix this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
if self.rotation.should_rollover(self.current_size + size) { | ||
self.current_size = 0; | ||
if self.rotation.is_create_backup(self.last_backup) { | ||
self.last_backup += 1; | ||
} | ||
self.rotate_files(); | ||
match create_writer(&self.log_directory, &self.log_filename_prefix) { | ||
Ok(writer) => self.writer = writer, | ||
Err(err) => eprintln!("Couldn't create writer for logs: {}", err), | ||
} | ||
} | ||
self.current_size += size; | ||
} | ||
} | ||
|
||
impl InnerRotatingAppender { | ||
fn rotate_files(&self) { | ||
for x in (1..=self.last_backup).rev() { | ||
let from = self.rotation.join_backup(&self.log_filename_prefix, x - 1); | ||
let to = self.rotation.join_backup(&self.log_filename_prefix, x); | ||
if let Err(err) = rename_file(&self.log_directory, &from, &to) { | ||
eprintln!("Couldn't rename backup log file: {}", err); | ||
} | ||
} | ||
} | ||
|
||
fn find_last_backup( | ||
rotation: &Rotation, | ||
log_directory: &str, | ||
log_filename_prefix: &str, | ||
) -> usize { | ||
let mut last_backup = 0; | ||
while rotation.is_create_backup(last_backup) { | ||
let filename = rotation.join_backup(log_filename_prefix, last_backup + 1); | ||
if file_exist(log_directory, &filename) { | ||
last_backup += 1; | ||
} else { | ||
break; | ||
} | ||
} | ||
last_backup | ||
} | ||
} | ||
|
||
fn create_writer(directory: &str, filename: &str) -> io::Result<BufWriter<File>> { | ||
let file_path = Path::new(directory).join(filename); | ||
Ok(BufWriter::new(open_file_create_parent_dirs(&file_path)?)) | ||
|
@@ -94,3 +214,23 @@ fn open_file_create_parent_dirs(path: &Path) -> io::Result<File> { | |
|
||
new_file | ||
} | ||
|
||
fn get_file_size(directory: &str, filename: &str) -> io::Result<usize> { | ||
let file_path = Path::new(directory).join(filename); | ||
if file_path.exists() { | ||
Ok(std::fs::metadata(file_path)?.len() as usize) | ||
} else { | ||
Ok(0) | ||
} | ||
} | ||
|
||
fn file_exist(directory: &str, filename: &str) -> bool { | ||
let file_path = Path::new(directory).join(filename); | ||
file_path.as_path().exists() | ||
} | ||
|
||
fn rename_file(directory: &str, from: &str, to: &str) -> io::Result<()> { | ||
let from = Path::new(directory).join(from); | ||
let to = Path::new(directory).join(to); | ||
std::fs::rename(from, to) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Something better named would be nice as
pub(crate) trait
already signifies this is a trait. Something likeAppendInnder
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will rename