-
Notifications
You must be signed in to change notification settings - Fork 432
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
Add log feature #246
Add log feature #246
Conversation
src/lib.rs
Outdated
@@ -1080,6 +1091,14 @@ pub fn sample<T, I, R>(rng: &mut R, iterable: I, amount: usize) -> Vec<T> | |||
.unwrap_or_else(|e| e) | |||
} | |||
|
|||
#[cfg(feature="std")] | |||
fn log_cause(mut e: &::std::error::Error) { |
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 know enough about the error types to be able to format them using a Debug
impl including the chain of causes instead of multiple separate logging statements?
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
is for dumping, Display
for pretty-printing, as I understand. This code attempts to do the latter, which requires multiple lines.
And yes, mostly we're using the Error
type from the rand::error
module.
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.
Ah gotcha. It might still be worth combining the multiple log statements into 1, otherwise you might end up with your chain of causes interleaved with other log messages.
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'm struggling to find a good middle-ground. Compare:
ERROR 2018-01-30T12:57:19Z: rand: JitterRng failed: RNG error [permanent failure or unavailable]: timer jitter failed basic quality tests
WARN 2018-01-30T12:57:19Z: rand: cause: no timer available
and
ERROR 2018-01-30T12:59:17Z: rand: JitterRng failed: RNG error [permanent failure or unavailable]: timer jitter failed basic quality tests; full error: Error { kind: Unavailable, msg: "timer jitter failed basic quality tests", cause: Some(NoTimer) }
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.
Personally, I prefer the second one even though it doesn't look as nice in that example because the log message holds all the details. In the same samples with other log messages interleaved the second is easier to follow:
ERROR 2018-01-30T12:57:19Z: rand: JitterRng failed: RNG error [permanent failure or unavailable]: timer jitter failed basic quality tests
INFO 2018-01-30T12:57:19Z: some_crate::some_mod: request to `/`
DEBUG 2018-01-30T12:57:19Z: some_crate::some_mod::some_other_mod: getting a connection to the database
DEBUG 2018-01-30T12:57:19Z: some_crate::some_mod::some_other_mod: got a connection to the database
TRACE 2018-01-30T12:57:19Z: rand: Seeding new RNG
WARN 2018-01-30T12:57:19Z: rand: cause: no timer available
INFO 2018-01-30T12:57:19Z: some_crate::some_mod: processing request `/` with handler `Root`
ERROR 2018-01-30T12:57:19Z: rand: JitterRng failed: RNG error [permanent failure or unavailable]: timer jitter failed basic quality tests; full error: Error { kind: Unavailable, msg: "timer jitter failed basic quality tests", cause: Some(NoTimer) }
INFO 2018-01-30T12:57:19Z: some_crate::some_mod: request to `/`
DEBUG 2018-01-30T12:57:19Z: some_crate::some_mod::some_other_mod: getting a connection to the database
DEBUG 2018-01-30T12:57:19Z: some_crate::some_mod::some_other_mod: got a connection to the database
TRACE 2018-01-30T12:57:19Z: rand: Seeding new RNG
INFO 2018-01-30T12:57:19Z: some_crate::some_mod: processing request `/` with handler `Root`
I added some logging. Example using rand-num:
Note that this will change a bit when I merge #239. |
src/jitter.rs
Outdated
@@ -422,6 +423,8 @@ impl JitterRng { | |||
} | |||
|
|||
fn gen_entropy(&mut self) -> u64 { | |||
trace!("JitterRng: generating entropy"); |
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 gets called a lot when seeding StdRng
— 256 times. @pitdicker and I already noted that initialising ISAAC via JitterRng
can be slow; should we fix? Not sure whether it's better to adjust the seed_from_rng
implementation or to try to make a special case for ISAAC seeded from JitterRng.
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.
collecting entropy
sounds better to me than generating entropy
.
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 added a few comments, but I think this looks very good!
I think you found a good balance for the different log levels, and which things to log.
Does it make sense to use Warning
as the highest log level, and let the application decide if it is important enough to log something as an error?
@@ -845,9 +854,13 @@ impl<R: SeedableRng> NewRng for R { | |||
T::from_rng(&mut r) | |||
} | |||
|
|||
trace!("Seeding new RNG"); |
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.
Is it possible to include the type of the RNG 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.
I don't know a way of doing that. I believe only macros could do so; something similar to stringify
but that one will not substitute R
for the underlying type.
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.
O, sorry. I kind of expected it to be possible, but it is only possible using intrinsics on nightly. Not worth the effort if it is not easy in my opinion.
src/os.rs
Outdated
if log_err == 0 { | ||
warn!("OsRng failed: {}", e); | ||
log_cause(&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.
Would it help to rewrite this function to match over all possible errors? It is becoming a little complex now...
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 lot of the complexity comes from having limited retry and logging in "smart" ways, i.e. I don't think significantly simpler code can achieve the same behaviour. Whether all this complexity is warranted is another question (e.g. we could remove log_err
and do logging each time, but that could create a huge number of messages).
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.
What do you think of this try?
fn fill_bytes(&mut self, dest: &mut [u8]) {
// We cannot return Err(..), so we try to handle before panicking.
const MAX_RETRY_PERIOD: u32 = 10; // max 10s
const WAIT_DUR_MS: u32 = 100; // retry every 100ms
const RETRY_LIMIT: u32 = (MAX_RETRY_PERIOD * 1000) / WAIT_DUR_MS;
const TRANSIENT_STEP: u32 = RETRY_LIMIT / 8;
let mut err_count = 0;
let mut error_logged = false;
loop {
if let Err(e) = self.try_fill_bytes(dest) {
if err_count >= RETRY_LIMIT {
error!("OsRng failed too many times, last error: {}", e);
panic!("OsRng failed too many times, last error: {}",
e.msg());
}
match e.kind() {
ErrorKind::Transient => {
if !error_logged {
warn!("OsRng failed: {}. retrying {} times ...",
e, TRANSIENT_STEP);
error_logged = true;
}
err_count += TRANSIENT_STEP;
continue;
}
ErrorKind::NotReady => {
if !error_logged {
warn!("OsRng failed: {}. waiting and retrying for {} ms ...",
e, MAX_RETRY_PERIOD);
error_logged = true;
}
err_count += 1;
let dur = ::std::time::Duration::from_millis(WAIT_DUR_MS as u64);
::std::thread::sleep(dur);
continue;
}
_ => {
error!("OsRng failed: {}", e);
panic!("OsRng fatal error: {}", e.msg());
}
}
}
break;
}
}
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 played some more with it to log only the error cause, and added a cause for a few operating systems where it was missing. Take from it what you can use. https://github.com/pitdicker/rand/commits/log-feature
src/lib.rs
Outdated
warn!("OsRng failed: {}; falling back to JitterRng", e1); | ||
log_cause(&e1); | ||
new_jitter().map_err(|e2| { | ||
error!("JitterRng failed: {}", e2); |
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 think it is better to make this warn!
also, and let the user code decide if it is important enough to also log as an error!
.
src/jitter.rs
Outdated
@@ -422,6 +423,8 @@ impl JitterRng { | |||
} | |||
|
|||
fn gen_entropy(&mut self) -> u64 { | |||
trace!("JitterRng: generating entropy"); |
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.
collecting entropy
sounds better to me than generating entropy
.
src/reseeding.rs
Outdated
@@ -47,6 +47,7 @@ impl<R: Rng, Rsdr: Reseeder<R>> ReseedingRng<R, Rsdr> { | |||
/// generated exceed the threshold. | |||
pub fn reseed_if_necessary(&mut self) { | |||
if self.bytes_generated >= self.generation_threshold { | |||
trace!("Reseeding RNG"); |
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 change this to something like "{} generated {} MiB, attempting to reseed"
?
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 could change it to "Reseeding RNG after {} bytes", but I don't think I can do much better than that. Not sure it's really useful changing units?
If I change
Code: trace!("Seeding new RNG");
new_os().or_else(|e1| {
warn!("OsRng failed: {}; falling back to JitterRng", e1.cause().unwrap());
new_jitter().map_err(|e2| {
warn!("JitterRng failed: {}", e2.cause().unwrap());
// TODO: can we somehow return both error sources?
Error::with_cause(
ErrorKind::Unavailable,
"seeding a new RNG failed: both OS and Jitter entropy sources failed",
e1)
})
}) (Unwrapping is safe because we know |
Safe with current code, you mean, but I think we should probably merge this and |
Good point. Probably should be a function that falls back to
😄 Yes, that replaces this part anyway. |
There's still room for tweaking messages, but response seems mostly positive, so I'll address the biggest concerns then merge; PRs to improve the details are welcome. |
Yet another optional feature (effectively 2×2×2×3 = 24 combinations, since nightly = i128_support and alloc only does anything without std).
The main motivation is to allow reporting of errors which are handled internally (via retry or fallback), and also to explain why
OsRng
delays (whenNotReady
).This has the disadvantage of complicating the feature matrix and documentation, but probably on the whole is worth adding?