-
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
Open
AliceLanniste
wants to merge
35
commits into
Fullstop000:master
Choose a base branch
from
AliceLanniste:rotation
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Add log Rotation #62
Changes from all commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
7a331c4
test_generate_filename supports windows
AliceLanniste b4e360f
te2 tests's functions supports windows
AliceLanniste 343eb96
cargo fmt
Fullstop000 f15cc46
add some set_*** testcase in version_edit
AliceLanniste e9e41f8
cargo fmt
AliceLanniste d0b24ea
cargo fmt --check
AliceLanniste 3489f0a
cargo fmt --check before rustc updated
AliceLanniste 5634f41
parenthesis added
AliceLanniste d3fdc3b
cargo fmt
AliceLanniste 946dcfe
add testcases for Version::find_file
AliceLanniste fb70318
My own comments deleted
AliceLanniste 38564a1
Fix FileMetaDatas.geneerator() error
AliceLanniste 0a262c2
re-arrange tests
Fullstop000 6696691
assoicated type in iterator.rs
AliceLanniste 07a7a2c
completed assoicated type implementation
AliceLanniste d68806e
completed assoicated type implemenation
AliceLanniste 5c14c16
fix conflict files
AliceLanniste e35c113
fix conflict changes
AliceLanniste 2298102
remove unnecessary information
AliceLanniste 3f036a7
added associated type for db
AliceLanniste 731255f
make sure InternalKey live longer than Slice
AliceLanniste 1f8cdd8
cargo fmt
AliceLanniste 85fb6be
cargo fmt again
AliceLanniste f3420a7
add Rotator trait
AliceLanniste 5a2ae75
add add_rotator to FileBaseDrain
AliceLanniste f631794
initially complete `RotatedFileBySize`
AliceLanniste 42c0a2d
update upstream
AliceLanniste 36c3fb7
add simple log rotation
AliceLanniste c2aa6d0
merge
AliceLanniste 746d23b
cargo fmt
AliceLanniste 1d82110
update
AliceLanniste daf1437
add test for rotate
AliceLanniste 7110a01
complete rotate test
AliceLanniste f37634e
using memStorage instead of FileStorage
AliceLanniste 5d27d2c
delete unncessary import
AliceLanniste File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
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.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,17 +12,27 @@ | |
| // 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::atomic::{AtomicU64, Ordering}; | ||
| 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 | ||
| /// | ||
|
|
||
| pub fn create_file<S: Storage>(storage: &S, db_path: &str, timestamp: i64) -> Result<S::F> { | ||
| let log_path = generate_filename(db_path, FileType::Log, timestamp as u64); | ||
| storage.create(log_path) | ||
| } | ||
|
|
||
| pub struct Logger { | ||
| inner: slog::Logger, | ||
| level: LevelFilter, | ||
|
|
@@ -35,11 +45,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: &'static str, | ||
| ) -> Self { | ||
| let inner = match inner { | ||
| Some(l) => l, | ||
|
|
@@ -51,13 +61,13 @@ impl Logger { | |
| slog::Logger::root(drain, o!()) | ||
| } 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, 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, file_fn) | ||
| .add_rotator(RotatedFileBySize::new(1)); | ||
| let drain = slog_async::Async::new(drain).build().fuse(); | ||
| slog::Logger::root(drain, o!()) | ||
| } | ||
| } | ||
|
|
@@ -104,7 +114,6 @@ impl Log for Logger { | |
| } | ||
| } | ||
| } | ||
|
|
||
| fn flush(&self) {} | ||
| } | ||
|
|
||
|
|
@@ -120,16 +129,53 @@ fn log_to_slog_level(level: log::Level) -> Level { | |
|
|
||
| struct FileBasedDrain<F: File> { | ||
| inner: Mutex<F>, | ||
| rotators: Vec<Box<dyn Rotator>>, | ||
| db_path: String, | ||
| 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: &str, new_file: H) -> Self | ||
| where | ||
| H: 'static + Send + Fn(String) -> Result<F>, | ||
| { | ||
| FileBasedDrain { | ||
| db_path: path.to_string(), | ||
| 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() { | ||
| rotator.prepare(&*self.inner.lock().unwrap()).unwrap(); | ||
| } | ||
| self | ||
| } | ||
|
|
||
| fn flush(&self) -> Result<()> { | ||
| for rotator in self.rotators.iter() { | ||
| if rotator.should_rotate() { | ||
| let new_file = (self.new_file)(self.db_path.clone()).unwrap(); | ||
|
|
||
| let mut old_file = self.inner.lock().unwrap(); | ||
| *old_file = new_file; | ||
|
|
||
| for rotator in self.rotators.iter() { | ||
| rotator.on_rotate(); | ||
| } | ||
|
|
||
| return Ok(()); | ||
| } | ||
| } | ||
|
|
||
| self.inner.lock().unwrap().flush() | ||
| } | ||
| } | ||
| impl<F: File> Drain for FileBasedDrain<F> { | ||
| type Ok = (); | ||
| type Err = slog::Never; | ||
|
|
@@ -138,40 +184,146 @@ 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() { | ||
| rotator.on_write(by.as_bytes()).unwrap(); | ||
| } | ||
|
|
||
| self.flush().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; | ||
|
|
||
| /// Call by operator, update rotators' state while the operator try to write some data. | ||
| fn on_write(&self, buf: &[u8]) -> Result<()>; | ||
|
Owner
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); | ||
| } | ||
|
|
||
| struct RotatedFileBySize { | ||
| rotation_size: u64, | ||
| file_size: AtomicU64, | ||
| } | ||
|
|
||
| impl RotatedFileBySize { | ||
| fn new(rotation_size: u64) -> Self { | ||
| RotatedFileBySize { | ||
| rotation_size, | ||
| file_size: AtomicU64::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.store(file.len().unwrap(), Ordering::Relaxed); | ||
| Ok(()) | ||
| } | ||
|
|
||
| fn should_rotate(&self) -> bool { | ||
| self.file_size.load(Ordering::Relaxed) > self.rotation_size | ||
| } | ||
| fn on_write(&self, buf: &[u8]) -> Result<()> { | ||
| let size = self.file_size.load(Ordering::Relaxed) + buf.len() as u64; | ||
| self.file_size.store(size, Ordering::Relaxed); | ||
| Ok(()) | ||
| } | ||
|
|
||
| fn on_rotate(&self) { | ||
| self.file_size.store(0, Ordering::Relaxed) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
|
|
||
| use super::*; | ||
| use crate::storage::mem::MemStorage; | ||
|
|
||
| use std::thread; | ||
| use std::time::Duration; | ||
|
|
||
| #[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); | ||
| // Ignore the error if the logger have been set | ||
| let _ = log::set_logger(Box::leak(Box::new(logger))); | ||
| log::set_max_level(LevelFilter::Debug); | ||
| info!("Hello World"); | ||
| log::info!("Hello World"); | ||
| // Wait for the async logger print the result | ||
| thread::sleep(Duration::from_millis(100)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_rotate_by_size() { | ||
| let db_path = "log"; | ||
|
|
||
| let storage = MemStorage::default(); | ||
| let stor2 = storage.clone(); | ||
| let _ = storage.mkdir_all(db_path); | ||
| let file = create_file(&storage, db_path, 0).unwrap(); | ||
| let new_path = generate_filename(db_path, FileType::Log, 1); | ||
|
|
||
| { | ||
| let file_fn = move |path: String| create_file(&storage, path.as_str(), 1); | ||
|
|
||
| let drain = | ||
| FileBasedDrain::new(file, db_path, file_fn).add_rotator(RotatedFileBySize::new(1)); | ||
| let drain = slog_async::Async::new(drain).build().fuse(); | ||
| let _log = slog::Logger::root(drain, o!()); | ||
| slog::info!(_log, "Test log file rotated by size"); | ||
| } | ||
| assert_eq!(true, stor2.exists(new_path)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_not_rotate_by_size() { | ||
| let db_path = "norotate"; | ||
|
|
||
| let storage = MemStorage::default(); | ||
| let stor2 = storage.clone(); | ||
| let _ = storage.mkdir_all(db_path); | ||
| let file = create_file(&storage, db_path, 0).unwrap(); | ||
| let new_path = generate_filename(db_path, FileType::Log, 1); | ||
|
|
||
| { | ||
| let file_fn = move |path: String| create_file(&storage, path.as_str(), 1); | ||
|
|
||
| let drain = FileBasedDrain::new(file, db_path, file_fn) | ||
| .add_rotator(RotatedFileBySize::new(100)); | ||
| let drain = slog_async::Async::new(drain).build().fuse(); | ||
| let _log = slog::Logger::root(drain, o!()); | ||
| slog::info!(_log, "Test log file rotated by size"); | ||
| } | ||
| assert_eq!(true, stor2.exists("norotate/000000.log")); | ||
| assert_eq!(false, stor2.exists(new_path)); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
It seems
pubis not required. If usingpub, pls add rustdoc for it :)