1 /*! Logging library.
2 *
3 * This is probably the only part of squeekboard
4 * that should be doing any direct printing.
5 *
6 * There are several approaches to logging,
7 * in the order of increasing flexibility and/or purity:
8 *
9 * 1. `println!` directly
10 *
11 * It can't be easily replaced by a different solution
12 *
13 * 2. simple `log!` macro
14 *
15 * Replacing the destination at runtime other than globally would be awkward,
16 * so no easy way to suppress errors for things that don't matter,
17 * but formatting is still easy.
18 *
19 * 3. logging to a mutable destination type
20 *
21 * Can be easily replaced, but logging `Result` types,
22 * which should be done by calling a method on the result,
23 * can't be formatted directly.
24 * Cannot be parallelized.
25 *
26 * 4. logging to an immutable destination type
27 *
28 * Same as above, except it can be parallelized.
29 * Logs being outputs, they get returned
30 * instead of being misleadingly passed back through arguments.
31 * It seems more difficult to pass the logger around,
32 * but this may be a solved problem from the area of functional programming.
33 *
34 * This library generally aims at the approach in 3.
35 * */
36
37 use std::fmt::Display;
38
39 /// Levels are not in order.
40 pub enum Level {
41 // Levels for reporting violated constraints
42 /// The program violated a self-imposed constraint,
43 /// ended up in an inconsistent state, and cannot recover.
44 /// Handlers must not actually panic. (should they?)
45 Panic,
46 /// The program violated a self-imposed constraint,
47 /// ended up in an inconsistent state, but some state can be recovered.
48 Bug,
49 /// Invalid data given by an external source,
50 /// some state of the program must be dropped.
51 Error,
52 // Still violated constraints, but harmless
53 /// Invalid data given by an external source, parts of data are ignored.
54 /// No previous program state needs to be dropped.
55 Warning,
56 /// External source not in an expected state,
57 /// but not violating any protocols (including no relevant protocol).
58 Surprise,
59 // Informational
60 /// A change in internal state that results in a change of behaviour
61 /// that a user can observe, and a tinkerer might find useful.
62 /// E.g. selection of external sources, like loading user's UI files,
63 /// language switch, overrides.
64 Info,
65 /// Information useful for application developer only.
66 /// Should be limited to information gotten from external sources,
67 /// and more tricky parts of internal state.
68 Debug,
69 }
70
71 impl Level {
as_str(&self) -> &'static str72 fn as_str(&self) -> &'static str {
73 match self {
74 Level::Panic => "Panic",
75 Level::Bug => "Bug",
76 Level::Error => "Error",
77 Level::Warning => "Warning",
78 Level::Surprise => "Surprise",
79 Level::Info => "Info",
80 Level::Debug => "Debug",
81 }
82 }
83 }
84
85 impl From<Problem> for Level {
from(problem: Problem) -> Level86 fn from(problem: Problem) -> Level {
87 use self::Level::*;
88 match problem {
89 Problem::Panic => Panic,
90 Problem::Bug => Bug,
91 Problem::Error => Error,
92 Problem::Warning => Warning,
93 Problem::Surprise => Surprise,
94 }
95 }
96 }
97
98 /// Only levels which indicate problems
99 /// To use with `Result::Err` handlers,
100 /// which are needed only when something went off the optimal path.
101 /// A separate type ensures that `Err`
102 /// can't end up misclassified as a benign event like `Info`.
103 pub enum Problem {
104 Panic,
105 Bug,
106 Error,
107 Warning,
108 Surprise,
109 }
110
111 /// Sugar for approach 2
112 // TODO: avoid, deprecate.
113 // Handler instances should be long lived, not one per call.
114 macro_rules! log_print {
115 ($level:expr, $($arg:tt)*) => (::logging::print($level, &format!($($arg)*)))
116 }
117
118 /// Approach 2
print(level: Level, message: &str)119 pub fn print(level: Level, message: &str) {
120 Print{}.handle(level, message)
121 }
122
123 /// Sugar for logging errors in results.
124 pub trait Warn where Self: Sized {
125 type Value;
126 /// Approach 2.
or_print(self, level: Problem, message: &str) -> Option<Self::Value>127 fn or_print(self, level: Problem, message: &str) -> Option<Self::Value> {
128 self.or_warn(&mut Print {}, level.into(), message)
129 }
130 /// Approach 3.
or_warn<H: Handler>( self, handler: &mut H, level: Problem, message: &str, ) -> Option<Self::Value>131 fn or_warn<H: Handler>(
132 self,
133 handler: &mut H,
134 level: Problem,
135 message: &str,
136 ) -> Option<Self::Value>;
137 }
138
139 impl<T, E: Display> Warn for Result<T, E> {
140 type Value = T;
or_warn<H: Handler>( self, handler: &mut H, level: Problem, message: &str, ) -> Option<T>141 fn or_warn<H: Handler>(
142 self,
143 handler: &mut H,
144 level: Problem,
145 message: &str,
146 ) -> Option<T> {
147 self.map_err(|e| {
148 handler.handle(level.into(), &format!("{}: {}", message, e));
149 e
150 }).ok()
151 }
152 }
153
154 impl<T> Warn for Option<T> {
155 type Value = T;
or_warn<H: Handler>( self, handler: &mut H, level: Problem, message: &str, ) -> Option<T>156 fn or_warn<H: Handler>(
157 self,
158 handler: &mut H,
159 level: Problem,
160 message: &str,
161 ) -> Option<T> {
162 self.or_else(|| {
163 handler.handle(level.into(), message);
164 None
165 })
166 }
167 }
168
169 /// A mutable handler for text warnings.
170 /// Approach 3.
171 pub trait Handler {
172 /// Handle a log message
handle(&mut self, level: Level, message: &str)173 fn handle(&mut self, level: Level, message: &str);
174 }
175
176 /// Prints info to stdout, everything else to stderr
177 pub struct Print;
178
179 impl Handler for Print {
handle(&mut self, level: Level, message: &str)180 fn handle(&mut self, level: Level, message: &str) {
181 match level {
182 Level::Info => println!("Info: {}", message),
183 l => eprintln!("{}: {}", l.as_str(), message),
184 }
185 }
186 }
187
188 /// Warning handler that will panic
189 /// at any warning, error, surprise, bug, or panic.
190 /// Don't use except in tests
191 pub struct ProblemPanic;
192
193 impl Handler for ProblemPanic {
handle(&mut self, level: Level, message: &str)194 fn handle(&mut self, level: Level, message: &str) {
195 use self::Level::*;
196 match level {
197 Panic | Bug | Error | Warning | Surprise => panic!("{}", message),
198 l => Print{}.handle(l, message),
199 }
200 }
201 }
202