-
Notifications
You must be signed in to change notification settings - Fork 70
Add log Rotation #62
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
base: master
Are you sure you want to change the base?
Add log Rotation #62
Changes from 31 commits
7a331c4
b4e360f
343eb96
f15cc46
e9e41f8
d0b24ea
3489f0a
5634f41
d3fdc3b
946dcfe
fb70318
38564a1
0a262c2
6696691
07a7a2c
d68806e
5c14c16
e35c113
2298102
3f036a7
731255f
1f8cdd8
85fb6be
f3420a7
5a2ae75
f631794
42c0a2d
36c3fb7
c2aa6d0
746d23b
1d82110
daf1437
7110a01
f37634e
5d27d2c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,17 +12,26 @@ | |
// limitations under the License. | ||
|
||
use crate::db::filename::{generate_filename, FileType}; | ||
use crate::error::Result; | ||
use crate::storage::{File, Storage}; | ||
|
||
use log::{LevelFilter, Log, Metadata, Record}; | ||
use slog::{o, Drain, Level}; | ||
|
||
use chrono::prelude::*; | ||
use std::sync::Mutex; | ||
|
||
/// A `slog` based logger which can be used with `log` crate | ||
/// | ||
/// See `slog` at https://github.com/slog-rs/slog | ||
/// See `log` at https://github.com/rust-lang/log | ||
/// | ||
|
||
fn create_file<S: Storage>(storage: &S, dp_path: &str, timestamp: i64) -> Result<S::F> { | ||
let new_path = generate_filename(dp_path, FileType::OldInfoLog, timestamp as u64); | ||
storage.rename(dp_path, new_path.as_str())?; | ||
storage.create(dp_path) | ||
} | ||
|
||
pub struct Logger { | ||
inner: slog::Logger, | ||
level: LevelFilter, | ||
|
@@ -35,11 +44,11 @@ impl Logger { | |
/// If `inner` is `None` | ||
/// - In dev mode, use a std output | ||
/// - In release mode, use a storage specific file with name `LOG` | ||
pub fn new<S: Storage>( | ||
pub fn new<S: Storage + Clone + 'static>( | ||
inner: Option<slog::Logger>, | ||
level: LevelFilter, | ||
storage: &S, | ||
db_path: &str, | ||
storage: S, | ||
db_path: String, | ||
) -> Self { | ||
let inner = match inner { | ||
Some(l) => l, | ||
|
@@ -52,12 +61,14 @@ impl Logger { | |
} else { | ||
// Use a file `Log` to record all logs | ||
// TODO: add file rotation | ||
let file = storage | ||
.create(generate_filename(db_path, FileType::InfoLog, 0).as_str()) | ||
.unwrap(); | ||
let drain = slog_async::Async::new(FileBasedDrain::new(file)) | ||
.build() | ||
.fuse(); | ||
let file = | ||
create_file(&storage, db_path.as_str(), Local::now().timestamp()).unwrap(); | ||
let file_fn = move |path: String| { | ||
create_file(&storage, path.as_str(), Local::now().timestamp()) | ||
}; | ||
let drain = FileBasedDrain::new(file, db_path.clone(), file_fn) | ||
.add_rotator(RotatedFileBySize::new(0)); | ||
let drain = slog_async::Async::new(drain).build().fuse(); | ||
slog::Logger::root(drain, o!()) | ||
} | ||
} | ||
|
@@ -104,7 +115,6 @@ impl Log for Logger { | |
} | ||
} | ||
} | ||
|
||
fn flush(&self) {} | ||
} | ||
|
||
|
@@ -120,13 +130,44 @@ fn log_to_slog_level(level: log::Level) -> Level { | |
|
||
struct FileBasedDrain<F: File> { | ||
inner: Mutex<F>, | ||
rotators: Vec<Box<dyn Rotator>>, | ||
dp_path: String, | ||
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. Is 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. YES,you ar right |
||
new_file: Box<dyn Send + Fn(String) -> Result<F>>, | ||
} | ||
|
||
impl<F: File> FileBasedDrain<F> { | ||
fn new(f: F) -> Self { | ||
fn new<H>(f: F, path: String, new_file: H) -> Self | ||
where | ||
H: 'static + Send + Fn(String) -> Result<F>, | ||
{ | ||
FileBasedDrain { | ||
dp_path: path.clone(), | ||
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. ditto |
||
inner: Mutex::new(f), | ||
rotators: vec![], | ||
new_file: Box::new(new_file), | ||
} | ||
} | ||
|
||
fn add_rotator<R: 'static + Rotator>(mut self, rotator: R) -> Self { | ||
if rotator.is_enabled() { | ||
self.rotators.push(Box::new(rotator)); | ||
} | ||
for rotator in (&self).rotators.iter() { | ||
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. use 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. NO, THIS CANNOT COMPILER |
||
rotator.prepare(&*self.inner.lock().unwrap()).unwrap(); | ||
} | ||
self | ||
} | ||
|
||
fn flush(&self) -> Result<()> { | ||
self.inner.lock().unwrap().flush()?; | ||
let new_file = (self.new_file)(self.dp_path.clone()).unwrap(); | ||
|
||
let mut old_file = self.inner.lock().unwrap(); | ||
std::mem::replace(&mut *old_file, new_file); | ||
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. It could be *old_file = new_file |
||
for rotator in self.rotators.iter() { | ||
rotator.on_rotate()?; | ||
} | ||
return Ok(()); | ||
} | ||
} | ||
|
||
|
@@ -138,17 +179,79 @@ impl<F: File> Drain for FileBasedDrain<F> { | |
&self, | ||
record: &slog::Record, | ||
values: &slog::OwnedKVList, | ||
) -> Result<Self::Ok, Self::Err> { | ||
// Ignore errors here | ||
let _ = self.inner.lock().unwrap().write( | ||
format!( | ||
"[{}] : {:?} \n {:?} \n", | ||
record.level(), | ||
record.msg(), | ||
values | ||
) | ||
.as_bytes(), | ||
) -> std::result::Result<Self::Ok, Self::Err> { | ||
let by = format!( | ||
"[{}] : {:?} \n {:?} \n", | ||
record.level(), | ||
record.msg(), | ||
values | ||
); | ||
for rotator in self.rotators.iter() { | ||
if rotator.should_rotate() { | ||
self.flush().unwrap(); | ||
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.
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. what kind of error should throw, |
||
return Ok(()); | ||
} | ||
} | ||
|
||
for rotator in self.rotators.iter() { | ||
rotator.on_write(by.as_bytes()).unwrap(); | ||
} | ||
// Ignore errors here | ||
let _ = self.inner.lock().unwrap().write(by.as_bytes()); | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
trait Rotator: Send { | ||
/// Check if the option is enabled in configuration. | ||
/// Return true if the `rotator` is valid. | ||
fn is_enabled(&self) -> bool; | ||
|
||
/// Call by operator, initializes the states of rotators. | ||
fn prepare(&self, file: &dyn File) -> Result<()>; | ||
|
||
/// Return if the file need to be rotated. | ||
fn should_rotate(&self) -> bool; | ||
|
||
fn on_write(&self, buf: &[u8]) -> Result<()>; | ||
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. Pls add rustdoc |
||
// Call by operator, update rotators' state while the operator execute a rotation. | ||
fn on_rotate(&self) -> Result<()>; | ||
} | ||
|
||
struct RotatedFileBySize { | ||
rotation_size: u64, | ||
file_size: Mutex<u64>, | ||
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. Use |
||
} | ||
|
||
impl RotatedFileBySize { | ||
fn new(rotation_size: u64) -> Self { | ||
RotatedFileBySize { | ||
rotation_size, | ||
file_size: Mutex::new(0), | ||
} | ||
} | ||
} | ||
|
||
impl Rotator for RotatedFileBySize { | ||
fn is_enabled(&self) -> bool { | ||
self.rotation_size != 0 | ||
} | ||
fn prepare(&self, file: &dyn File) -> Result<()> { | ||
*self.file_size.lock().unwrap() = file.len().unwrap(); | ||
Ok(()) | ||
} | ||
|
||
fn should_rotate(&self) -> bool { | ||
*self.file_size.lock().unwrap() > self.rotation_size | ||
} | ||
fn on_write(&self, buf: &[u8]) -> Result<()> { | ||
*self.file_size.lock().unwrap() += buf.len() as u64; | ||
Ok(()) | ||
} | ||
|
||
fn on_rotate(&self) -> Result<()> { | ||
*self.file_size.lock().unwrap() = 0; | ||
Ok(()) | ||
} | ||
} | ||
|
@@ -165,8 +268,9 @@ mod tests { | |
#[test] | ||
fn test_default_logger() { | ||
let s = MemStorage::default(); | ||
// let s = &'static s; | ||
let db_path = "test"; | ||
let logger = Logger::new(None, LevelFilter::Debug, &s, db_path); | ||
let logger = Logger::new(None, LevelFilter::Debug, s, db_path.to_string()); | ||
// Ignore the error if the logger have been set | ||
let _ = log::set_logger(Box::leak(Box::new(logger))); | ||
log::set_max_level(LevelFilter::Debug); | ||
|
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.
db_path
could be a&'static str
I thinkThere 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.
I will try