logging: Try to improve common operations

This adds sugar for logging `Result`s with a handler, makes names evoke something closer to "logging" than "warning", tries to remove any redundant `Logging` where the module name will do, and introduces a type strictly for bad things happening.
This commit is contained in:
Dorota Czaplejewicz
2020-01-16 15:57:46 +00:00
parent f3d852f552
commit ea84f4f031
5 changed files with 168 additions and 89 deletions

View File

@ -26,13 +26,15 @@
* 4. logging to an immutable destination type
*
* Same as above, except it can be parallelized.
* Logs being outputs, they get returned
* instead of being misleadingly passed back through arguments.
* It seems more difficult to pass the logger around,
* but this may be a solved problem from the area of functional programming.
*
* This library generally aims at the approach in 3.
* */
use std::error::Error;
use std::fmt::Display;
/// Levels are not in order.
pub enum Level {
@ -66,18 +68,72 @@ pub enum Level {
Debug,
}
/// Sugar for logging errors in results.
/// Approach 2.
pub trait Warn {
type Value;
fn or_warn(self, msg: &str) -> Option<Self::Value>;
impl Level {
fn as_str(&self) -> &'static str {
match self {
Level::Panic => "Panic",
Level::Bug => "Bug",
Level::Error => "Error",
Level::Warning => "Warning",
Level::Surprise => "Surprise",
Level::Info => "Info",
Level::Debug => "Debug",
}
}
}
impl<T, E: Error> Warn for Result<T, E> {
impl From<Problem> for Level {
fn from(problem: Problem) -> Level {
use self::Level::*;
match problem {
Problem::Panic => Panic,
Problem::Bug => Bug,
Problem::Error => Error,
Problem::Warning => Warning,
Problem::Surprise => Surprise,
}
}
}
/// Only levels which indicate problems
/// To use with `Result::Err` handlers,
/// which are needed only when something went off the optimal path.
/// A separate type ensures that `Err`
/// can't end up misclassified as a benign event like `Info`.
pub enum Problem {
Panic,
Bug,
Error,
Warning,
Surprise,
}
/// Sugar for logging errors in results.
pub trait Warn where Self: Sized{
type Value;
/// Approach 2.
fn or_print(self, level: Problem, message: &str) -> Option<Self::Value> {
self.or_warn(&mut Print {}, level.into(), message)
}
/// Approach 3.
fn or_warn<H: Handler>(
self,
handler: &mut H,
level: Problem,
message: &str,
) -> Option<Self::Value>;
}
impl<T, E: Display> Warn for Result<T, E> {
type Value = T;
fn or_warn(self, msg: &str) -> Option<T> {
fn or_warn<H: Handler>(
self,
handler: &mut H,
level: Problem,
message: &str,
) -> Option<T> {
self.map_err(|e| {
eprintln!("{}: {}", msg, e);
handler.handle(level.into(), &format!("{}: {}", message, e));
e
}).ok()
}
@ -85,9 +141,14 @@ impl<T, E: Error> Warn for Result<T, E> {
impl<T> Warn for Option<T> {
type Value = T;
fn or_warn(self, msg: &str) -> Option<T> {
fn or_warn<H: Handler>(
self,
handler: &mut H,
level: Problem,
message: &str,
) -> Option<T> {
self.or_else(|| {
eprintln!("{}", msg);
handler.handle(level.into(), message);
None
})
}
@ -95,26 +156,34 @@ impl<T> Warn for Option<T> {
/// A mutable handler for text warnings.
/// Approach 3.
pub trait WarningHandler {
/// Handle a warning
fn handle(&mut self, warning: &str);
pub trait Handler {
/// Handle a log message
fn handle(&mut self, level: Level, message: &str);
}
/// Prints warnings to stderr
pub struct PrintWarnings;
/// Prints info to stdout, everything else to stderr
pub struct Print;
impl WarningHandler for PrintWarnings {
fn handle(&mut self, warning: &str) {
eprintln!("{}", warning);
impl Handler for Print {
fn handle(&mut self, level: Level, message: &str) {
match level {
Level::Info => println!("Info: {}", message),
l => eprintln!("{}: {}", l.as_str(), message),
}
}
}
/// Warning handler that will panic at any warning.
/// Warning handler that will panic
/// at any warning, error, surprise, bug, or panic.
/// Don't use except in tests
pub struct PanicWarn;
pub struct ProblemPanic;
impl WarningHandler for PanicWarn {
fn handle(&mut self, warning: &str) {
panic!("{}", warning);
impl Handler for ProblemPanic {
fn handle(&mut self, level: Level, message: &str) {
use self::Level::*;
match level {
Panic | Bug | Error | Warning | Surprise => panic!("{}", message),
l => Print{}.handle(l, message),
}
}
}