-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Switch from syslog to some hostfile-backed mechanism #214
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
Conversation
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.
Review Part 1: will do some more tomorrow. As overall feedback, I would not return self for the configuration methods. I would go for the "classical" class and clearly specify that init will move ownership of Self.
logger/Cargo.toml
Outdated
[package] | ||
name = "logger" | ||
version = "0.1.0" | ||
authors = ["The Chromium OS Authors"] |
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.
Shouldn't this be Firecracker?
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.
yes it should, I changed it.
logger/Cargo.toml
Outdated
log = { version = "0.4", features = ["std"] } | ||
|
||
[build-dependencies] | ||
gcc = "=0.3.54" |
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.
Add new line.
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.
added.
logger/src/lib.rs
Outdated
mod writers; | ||
mod error; | ||
|
||
use std::result; |
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: import stuff in alphabetical order.
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.
Did it.
logger/src/lib.rs
Outdated
/// | ||
pub fn new() -> Logger { | ||
Logger { | ||
show_level: SHOW_LEVEL, |
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: Why don't you use directly true rather than having another constant in the file? I understand why it makes sense for the separators and the default level, but for boolean types it just adds unnecessary code.
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.
Ok, I used true and false and deleted the conts.
logger/src/lib.rs
Outdated
/// warn!("This will print '[WARN:file_path.rs]' followed by log message"); | ||
/// } | ||
/// ``` | ||
pub fn show_file_path(mut self, option: bool) -> Self { |
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.
The name of the method doesn't do justice to what it actually does. It also sets the line numbers configuration.
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.
Ok, now I only have a function called show_log_origin that receives two parameters, one for the file path and one for the line numbers. It sets the showing of the line numbers to false if the showing of the file path is also set to false.
logger/src/lib.rs
Outdated
/// warn!("This will print '[WARN:file_path.rs:179]' followed by log message"); | ||
/// } | ||
/// ``` | ||
pub fn show_line_numbers(mut self, option: bool) -> Self { |
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.
Doesn't this function sort of do the same thing as show_file_path? We can probably have a single function with 2 parameters: line number and file path. You can then avoid invalid states by checking that you can't set the line number (=true) if the path != true.
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.
See answer from above.
logger/src/writers.rs
Outdated
pub fn new(log_path: &String) -> Result<FileLogWriter> { | ||
let s_file: String = log_path.to_string(); | ||
let p_file = Path::new(&s_file); | ||
match File::create(&p_file) { |
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.
This currently overrides the log file if one exists, is this the desired functionality?
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.
As per discussion with @filipposironi , if the log file already exists it means we are running in a chroot that was not cleaned up. What I should do is add a warning that advises against the file being overwritten before going ahead with execution.
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.
Added the code that does the checking, throws an error and then moves on with the logging.
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.
Note that this needs to be handled differently on live-update though.
We either carry around the file descriptor of the log file and re-initialize the logger accordingly or we'll need to be able to tell the logger that the file needs to be opened in append mode.
/// Logger representing the logging subsystem. | ||
/// | ||
#[derive(Debug)] | ||
pub struct Logger { |
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.
Some kind of timestamp would be very useful here.
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.
This is true, but I prefer to add it in a separate commit. Created an issue for that: #228.
devices/src/lib.rs
Outdated
@@ -7,10 +7,11 @@ | |||
extern crate byteorder; | |||
extern crate epoll; | |||
extern crate libc; | |||
#[macro_use] |
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.
Can we get rid of the macro_use by publicly exporting the log macros in logger.rs?
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.
After some investigation, what we could do in order to reuse the macros in the external log crate, would be to use macro_reexport. But, as per rust issue, this is currently labeled as unstable and using it in your code triggers build failure. What I did was use the workaround that some user suggested there: `Since 1.15.0, macro_rules! macro can be re-exported with:
extern crate somelib;
pub use somelib::*;
'
and that is what I did.
@@ -79,10 +78,8 @@ impl Kvm { | |||
/// Gets the recommended maximum number of VCPUs per VM. | |||
pub fn get_nr_vcpus(&self) -> usize { | |||
match self.check_extension_int(Cap::NrVcpus) { | |||
0 => 4, // according to api.txt |
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.
Why did this line also go away?
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.
So, the logic before:
- In case of 0, return 4
- In case of a positive number return the number
- In case of anything else, warning and return 4
The logic now:
- In case of positive number, return the number
- Anything else, return 4 ( no warning is necessary in case kvm return negative, see api.txt and qemu code); this is why the line with 0 went away
logger/Cargo.toml
Outdated
log = { version = "0.4", features = ["std"] } | ||
|
||
[build-dependencies] | ||
gcc = "=0.3.54" |
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: Can you please add a newline at the end of this file for us OCD ppl? :(
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.
OCD trigger resolved.
logger/src/writers.rs
Outdated
/// Structure `FileLogWriter` used for writing to a file in a thread-safe way. | ||
#[derive(Debug)] | ||
pub struct FileLogWriter { | ||
line_writer: Mutex<RefCell<LineWriter<File>>>, |
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.
Do we still need the RefCell if the Writer is already wrapped inside a Mutex?
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.
I will add a comment explaining this.
For the moment, we do not need this, you are right.
The RefCell is used for its internal mutability and the Mutex for ensuring thread safety. If at some point we may want our structure to hold things that may be changing while the logger runs (rotating logs when the size increases above a certain size, using more files to log to when the size increases above a certain limit), the contents of line_writer will change and so I added the RefCell.
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.
A Mutex already provides interior mutability via the MutexGuard you get when you call mutex.lock().map_err(..)?; The MutexGuard implements both Defer and DerefMut, so you can mutate whatever is held inside the Mutex.
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.
Ok, seems like Mutex already guards against internal mutability, I will remove the RefCell.
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.
I removed the RefCell.
logger/src/writers.rs
Outdated
|
||
impl FileLogWriter { | ||
pub fn new(log_path: &String) -> Result<FileLogWriter> { | ||
let s_file: String = log_path.to_string(); |
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.
Why is this String needed?
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.
Removed it.
logger/src/writers.rs
Outdated
Ok(t) => Ok(FileLogWriter { | ||
line_writer: Mutex::new(RefCell::new(LineWriter::new(t))), | ||
}), | ||
Err(e) => return Err(LoggerError::BadDirectory(e)), |
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.
Who do all errors here get turned into BadDirectory? Is that the only possible error condition which may occur here?
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.
The name does not fit the purpose indeed. Changed the name of it to a more intuitive one.
logger/src/lib.rs
Outdated
|
||
/// Values used by the Logger. | ||
/// | ||
pub const SHOW_LEVEL: bool = true; |
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.
These constants look a bit awkward to me :-s Wouldn't it make more sense if they were parameters to the logger, and chosen appropriately when each Logger instance is created, instead of being defined at the top of this particular file? :-s
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.
They are default values. I prefer to do it this way instead of having a constructor with multiple parameters. These default values can be changed by calling the associated set functions.
logger/src/lib.rs
Outdated
/// warn!("This will print 'WARN' surrounded by square brackets followed by log message"); | ||
/// } | ||
/// ``` | ||
pub fn show_level(mut self, option: bool) -> Self { |
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.
This function appears to consume self, and then return it again. I don't know if it's inefficient or not, but it does look a bit weird; why does the function work that way?
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.
So,the init function calls set_boxed_logger which needs to receive self. All the setting up of the logger needs to happen before that. That function consumes self, so that I can do the setting up from one call (see src/main.rs):
Logger::new().set_bla().set_bla2().init();
If I were to implement the function with reference to mut self I would need to do it:
Logger::new()
l.set_bla()
l.set_bla2()
If the logger could be modified during its functioning, maybe there would be a reason in doing it by reference, but given that init needs to be called last, I basically enforced the setting up of the logger to take place from one call only.
logger/src/writers.rs
Outdated
let fw = FileLogWriter::new(&file).unwrap(); | ||
let msg = String::from("some message"); | ||
assert!(&fw.write(&msg).is_ok()); | ||
remove_file(file).unwrap(); |
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.
If the write fails for whatever reasons, the file doesn't get cleaned up. You could keep track of the result for write, clean up and assert after that.
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.
You are right. The new changes should remove the file whether or not the assert fails.
logger/src/writers.rs
Outdated
let file: String = "tmp_writers_flush.log".to_string(); | ||
let fw = FileLogWriter::new(&file).unwrap(); | ||
assert!(&fw.flush().is_ok()); | ||
remove_file(file).unwrap(); |
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.
Same as above
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.
See answer from above.
logger/src/error.rs
Outdated
FileLogLock(String), | ||
} | ||
|
||
impl fmt::Display for LoggerError { |
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.
Just curios, why do we implement Display for LoggerError?
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.
Are you asking why Debug would not be enough?
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.
Yes, I guess that would have been the more straightforward question.
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.
Debug seems to be for programmer facing output while the fmt is for user facing output and since the errors the logger throws are like the mother of errors, I though it would make sense to also implement fmt.
logger/src/writers.rs
Outdated
/// Structure `FileLogWriter` used for writing to a file in a thread-safe way. | ||
#[derive(Debug)] | ||
pub struct FileLogWriter { | ||
line_writer: Mutex<RefCell<LineWriter<File>>>, |
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.
A Mutex already provides interior mutability via the MutexGuard you get when you call mutex.lock().map_err(..)?; The MutexGuard implements both Defer and DerefMut, so you can mutate whatever is held inside the Mutex.
logger/src/writers.rs
Outdated
} | ||
|
||
pub fn write(&self, msg: &String) -> Result<()> { | ||
let guard = self.line_writer |
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.
Maybe it would make sense to implement a helper function which provides guard, so we don't have to repeat that relatively long code sequence twice (here, and once more for flush) :-s
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.
You are right, take a look now.
logger/src/lib.rs
Outdated
/// warn!("This will print 'WARN' surrounded by square brackets followed by log message"); | ||
/// } | ||
/// ``` | ||
pub fn show_level(mut self, option: bool) -> Self { |
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.
Also, the name of this function is a bit misleading; maybe you could rename it to something like set_show_level or something :-s
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.
Renamed it to set_include_level.
logger/src/lib.rs
Outdated
/// warn!("This will print '[WARN:file_path.rs:155]' followed by log message"); | ||
/// } | ||
/// ``` | ||
pub fn show_log_origin(mut self, file_path: bool, line_numbers: bool) -> Self { |
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.
Maybe rename this to something more suggestive towards the fact that it actually sets something :-s
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.
Done, renamed it to set_include_origin
/// } | ||
/// ``` | ||
pub fn init(mut self, log_path: Option<String>) -> Result<()> { | ||
unsafe { |
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.
Why do we need the unsafe block? If it's just for accessing the mut static, maybe it would make sense to refactor the code a bit, such that the actual unsafe block can be a lot smaller than it is now. It's a bit scary how large it is right now :-s
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 is indeed for the INIT_RES variable which requires an unsafe block which is basically used each two lines approximately and with different purposes...if you have a suggestion of how to refactor and keep functionality, I am open to it ;)
pub fn init(mut self, log_path: Option<String>) -> Result<()> { | ||
unsafe { | ||
if INIT_RES.is_err() { | ||
INIT_RES = Err(LoggerError::Poisoned(format!( |
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.
This duplication seems kinda ugly :-s Can't we create the error once, and then clone it or something? :-s
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 would mean to implement clone for Result<(), LoggerError> which is a type containing a generic inside, not sure how to do that... and since I cannot return INIT_RES, I think this is the quickest way to achieve it (even it it is kind of ugly).
self.level_info.writer = Destination::File; | ||
} | ||
Err(ref e) => { | ||
INIT_RES = Err(LoggerError::NeverInitialized(format!("{}", e))); |
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.
Isn't it a bit strange to use the NeverInitialized error here, since that should mean the initialization was never attempted?
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.
My initial purpose was to use NeverInitialized for when the first attempt to initialize fails. I wanted to use Poisoned when the user tries to intiailize again a logger that had previously failed initialization. Is it ok if I change the comments from the enum error to better express my intent in using those values?
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.
Sure.
set_max_level(self.level_info.code.to_level_filter()); | ||
|
||
if let Err(e) = set_boxed_logger(Box::new(self)) { | ||
INIT_RES = Err(LoggerError::NeverInitialized(format!("{}", e))) |
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.
Again, NeverInitialized seems a bit out of place :-s
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.
See answer from above.
} | ||
}); | ||
if INIT_RES.is_err() { | ||
return Err(LoggerError::NeverInitialized(format!( |
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.
Ditto.
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.
ditto answer
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.
Just a drive by review.
Looks good to me.
2fc0a66
Not necessary to throw a warning when the ioctl return error (see qemu). Signed-off-by: Diana Popa <[email protected]>
Simple logger that uses the external log crate to output log messages to either stdout, stderr or a file. Writing to the file is thread safe. Signed-off-by: Diana Popa <[email protected]>
Signed-off-by: Diana Popa <[email protected]>
For now, the functionality is not available through API commands. User can specify a file on disk (--log-file) for logging messages. Otherwise, all messages will be flushed to either stdout or stderr. Signed-off-by: Diana Popa <[email protected]>
...and all related stuff Signed-off-by: Diana Popa <[email protected]>
Changes
Testing
# put some logs in each vcpu thread containing the thread id and see that the tmp.log contains all of them target/debug/firecracker vmm-no-api -k vmlinux.bin -r ami-fea26484.ext4 --log-file tmp.log --vcpu-count 2
Dependencies