1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
/*! 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)*) => (crate::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),
}
}
}