202 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			202 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
/*! Logging library.
 | 
						|
 * 
 | 
						|
 * This is probably the only part of squeekboard
 | 
						|
 * that should be doing any direct printing.
 | 
						|
 * 
 | 
						|
 * There are several approaches to logging,
 | 
						|
 * in the order of increasing flexibility and/or purity:
 | 
						|
 * 
 | 
						|
 * 1. `println!` directly
 | 
						|
 * 
 | 
						|
 *   It can't be easily replaced by a different solution
 | 
						|
 * 
 | 
						|
 * 2. simple `log!` macro
 | 
						|
 * 
 | 
						|
 *   Replacing the destination at runtime other than globally would be awkward,
 | 
						|
 *   so no easy way to suppress errors for things that don't matter,
 | 
						|
 *   but formatting is still easy.
 | 
						|
 * 
 | 
						|
 * 3. logging to a mutable destination type
 | 
						|
 * 
 | 
						|
 *   Can be easily replaced, but logging `Result` types,
 | 
						|
 *   which should be done by calling a method on the result,
 | 
						|
 *   can't be formatted directly.
 | 
						|
 *   Cannot be parallelized.
 | 
						|
 * 
 | 
						|
 * 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::fmt::Display;
 | 
						|
 | 
						|
/// Levels are not in order.
 | 
						|
pub enum Level {
 | 
						|
    // Levels for reporting violated constraints
 | 
						|
    /// The program violated a self-imposed constraint,
 | 
						|
    /// ended up in an inconsistent state, and cannot recover.
 | 
						|
    /// Handlers must not actually panic. (should they?)
 | 
						|
    Panic,
 | 
						|
    /// The program violated a self-imposed constraint,
 | 
						|
    /// ended up in an inconsistent state, but some state can be recovered.
 | 
						|
    Bug,
 | 
						|
    /// Invalid data given by an external source,
 | 
						|
    /// some state of the program must be dropped.
 | 
						|
    Error,
 | 
						|
    // Still violated constraints, but harmless
 | 
						|
    /// Invalid data given by an external source, parts of data are ignored.
 | 
						|
    /// No previous program state needs to be dropped.
 | 
						|
    Warning,
 | 
						|
    /// External source not in an expected state,
 | 
						|
    /// but not violating any protocols (including no relevant protocol).
 | 
						|
    Surprise,
 | 
						|
    // Informational
 | 
						|
    /// A change in internal state that results in a change of behaviour
 | 
						|
    /// that a user can observe, and a tinkerer might find useful.
 | 
						|
    /// E.g. selection of external sources, like loading user's UI files,
 | 
						|
    /// language switch, overrides.
 | 
						|
    Info,
 | 
						|
    /// Information useful for application developer only.
 | 
						|
    /// Should be limited to information gotten from external sources,
 | 
						|
    /// and more tricky parts of internal state.
 | 
						|
    Debug,
 | 
						|
}
 | 
						|
 | 
						|
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 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 approach 2
 | 
						|
// TODO: avoid, deprecate.
 | 
						|
// Handler instances should be long lived, not one per call.
 | 
						|
macro_rules! log_print {
 | 
						|
    ($level:expr, $($arg:tt)*) => (::logging::print($level, &format!($($arg)*)))
 | 
						|
}
 | 
						|
 | 
						|
/// Approach 2
 | 
						|
pub fn print(level: Level, message: &str) {
 | 
						|
    Print{}.handle(level, message)
 | 
						|
}
 | 
						|
 | 
						|
/// 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<H: Handler>(
 | 
						|
        self,
 | 
						|
        handler: &mut H,
 | 
						|
        level: Problem,
 | 
						|
        message: &str,
 | 
						|
    ) -> Option<T> {
 | 
						|
        self.map_err(|e| {
 | 
						|
            handler.handle(level.into(), &format!("{}: {}", message, e));
 | 
						|
            e
 | 
						|
        }).ok()
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
impl<T> Warn for Option<T> {
 | 
						|
    type Value = T;
 | 
						|
    fn or_warn<H: Handler>(
 | 
						|
        self,
 | 
						|
        handler: &mut H,
 | 
						|
        level: Problem,
 | 
						|
        message: &str,
 | 
						|
    ) -> Option<T> {
 | 
						|
        self.or_else(|| {
 | 
						|
            handler.handle(level.into(), message);
 | 
						|
            None
 | 
						|
        })
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/// A mutable handler for text warnings.
 | 
						|
/// Approach 3.
 | 
						|
pub trait Handler {
 | 
						|
    /// Handle a log message
 | 
						|
    fn handle(&mut self, level: Level, message: &str);
 | 
						|
}
 | 
						|
 | 
						|
/// Prints info to stdout, everything else to stderr
 | 
						|
pub struct Print;
 | 
						|
 | 
						|
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, error, surprise, bug, or panic.
 | 
						|
/// Don't use except in tests
 | 
						|
pub struct ProblemPanic;
 | 
						|
 | 
						|
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),
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |