|
| 1 | +//! Sends log messages to either stdout, stderr or a file provided as argument to the init. |
| 2 | +//! |
| 3 | +//! Every function exported by this module is thread-safe. |
| 4 | +//! Each function will silently fail until |
| 5 | +//! `log::init()` is called and returns `Ok`. |
| 6 | +//! |
| 7 | +//! # Examples |
| 8 | +//! |
| 9 | +//! ``` |
| 10 | +//! #[macro_use] |
| 11 | +//! extern crate log; |
| 12 | +//! extern crate logger; |
| 13 | +//! use logger::Logger; |
| 14 | +//! |
| 15 | +//! fn main() { |
| 16 | +//! if let Err(e) = Logger::new().init(None) { |
| 17 | +//! println!("Could not initialize the log subsystem: {:?}", e); |
| 18 | +//! return; |
| 19 | +//! } |
| 20 | +//! warn!("this is a warning"); |
| 21 | +//! error!("this is a error"); |
| 22 | +//! } |
| 23 | +//! ``` |
| 24 | +// workaround to marco_reexport |
| 25 | +extern crate log; |
| 26 | +pub use log::*; |
| 27 | + |
| 28 | +mod error; |
| 29 | +mod writers; |
| 30 | + |
| 31 | +use error::LoggerError; |
| 32 | +use log::{set_boxed_logger, set_max_level, Level, Log, Metadata, Record}; |
| 33 | +use std::result; |
| 34 | +use std::sync::{Arc, Once, ONCE_INIT}; |
| 35 | +use writers::*; |
| 36 | + |
| 37 | +/// Output sources for the log subsystem. |
| 38 | +/// |
| 39 | +#[derive(Debug, PartialEq, Clone, Copy)] |
| 40 | +enum Destination { |
| 41 | + Stderr, |
| 42 | + Stdout, |
| 43 | + File, |
| 44 | +} |
| 45 | + |
| 46 | +/// Each log level also has a code and a destination output associated with it. |
| 47 | +/// |
| 48 | +#[derive(Debug)] |
| 49 | +pub struct LevelInfo { |
| 50 | + code: Level, |
| 51 | + writer: Destination, |
| 52 | +} |
| 53 | + |
| 54 | +/// Types used by the Logger. |
| 55 | +/// |
| 56 | +pub type Result<T> = result::Result<T, LoggerError>; |
| 57 | + |
| 58 | +/// Values used by the Logger. |
| 59 | +/// |
| 60 | +pub const IN_PREFIX_SEPARATOR: &str = ":"; |
| 61 | +pub const MSG_SEPARATOR: &str = " "; |
| 62 | +pub const DEFAULT_LEVEL: Level = Level::Warn; |
| 63 | + |
| 64 | +/// Synchronization primitives used to run a one-time global initialization. |
| 65 | +/// |
| 66 | +static INIT: Once = ONCE_INIT; |
| 67 | +static mut INIT_RES: Result<()> = Ok(()); |
| 68 | + |
| 69 | +/// Logger representing the logging subsystem. |
| 70 | +/// |
| 71 | +#[derive(Debug)] |
| 72 | +pub struct Logger { |
| 73 | + show_level: bool, |
| 74 | + show_file_path: bool, |
| 75 | + show_line_numbers: bool, |
| 76 | + level_info: LevelInfo, |
| 77 | + // used in case we want to log to a file |
| 78 | + file: Option<Arc<FileLogWriter>>, |
| 79 | +} |
| 80 | + |
| 81 | +/// Auxiliary function to get the default destination for some code level. |
| 82 | +/// |
| 83 | +fn get_default_destination(level: Level) -> Destination { |
| 84 | + match level { |
| 85 | + Level::Error => Destination::Stderr, |
| 86 | + Level::Warn => Destination::Stderr, |
| 87 | + Level::Info => Destination::Stdout, |
| 88 | + Level::Debug => Destination::Stdout, |
| 89 | + Level::Trace => Destination::Stdout, |
| 90 | + } |
| 91 | +} |
| 92 | + |
| 93 | +impl Logger { |
| 94 | + /// Creates a new instance of the current logger. |
| 95 | + /// |
| 96 | + /// The default level is Warning. |
| 97 | + /// The default separator between the tag and the log message is " ". |
| 98 | + /// The default separator inside the tag is ":". |
| 99 | + /// The tag of the log message is the text to the left of the separator. |
| 100 | + /// |
| 101 | + pub fn new() -> Logger { |
| 102 | + Logger { |
| 103 | + show_level: true, |
| 104 | + show_line_numbers: true, |
| 105 | + show_file_path: true, |
| 106 | + level_info: LevelInfo { |
| 107 | + // DEFAULT_LEVEL is warn so the destination output is stderr |
| 108 | + code: DEFAULT_LEVEL, |
| 109 | + writer: Destination::Stderr, |
| 110 | + }, |
| 111 | + file: None, |
| 112 | + } |
| 113 | + } |
| 114 | + |
| 115 | + /// Enables or disables including the level in the log message's tag portion. |
| 116 | + /// |
| 117 | + /// # Example |
| 118 | + /// |
| 119 | + /// ```rust |
| 120 | + /// #[macro_use] |
| 121 | + /// extern crate log; |
| 122 | + /// extern crate logger; |
| 123 | + /// use logger::Logger; |
| 124 | + /// |
| 125 | + /// fn main() { |
| 126 | + /// Logger::new() |
| 127 | + /// .set_include_level(true) |
| 128 | + /// .init(None) |
| 129 | + /// .unwrap(); |
| 130 | + /// |
| 131 | + /// warn!("This will print 'WARN' surrounded by square brackets followed by log message"); |
| 132 | + /// } |
| 133 | + /// ``` |
| 134 | + pub fn set_include_level(mut self, option: bool) -> Self { |
| 135 | + self.show_level = option; |
| 136 | + self |
| 137 | + } |
| 138 | + |
| 139 | + /// Enables or disables including the file path and the line numbers in the tag of the log message. |
| 140 | + /// |
| 141 | + /// # Example |
| 142 | + /// |
| 143 | + /// ```rust |
| 144 | + /// #[macro_use] |
| 145 | + /// extern crate log; |
| 146 | + /// extern crate logger; |
| 147 | + /// use logger::Logger; |
| 148 | + /// |
| 149 | + /// fn main() { |
| 150 | + /// Logger::new() |
| 151 | + /// .set_include_origin(true, true) |
| 152 | + /// .init(None) |
| 153 | + /// .unwrap(); |
| 154 | + /// |
| 155 | + /// warn!("This will print '[WARN:file_path.rs:155]' followed by log message"); |
| 156 | + /// } |
| 157 | + /// ``` |
| 158 | + pub fn set_include_origin(mut self, file_path: bool, line_numbers: bool) -> Self { |
| 159 | + self.show_file_path = file_path; |
| 160 | + self.show_line_numbers = line_numbers; |
| 161 | + |
| 162 | + //buut if the file path is not shown, do not show line numbers either |
| 163 | + if !self.show_file_path { |
| 164 | + self.show_line_numbers = false; |
| 165 | + } |
| 166 | + |
| 167 | + self |
| 168 | + } |
| 169 | + |
| 170 | + /// Explicitly sets the log level for the Logger. |
| 171 | + /// User needs to say the level code(error, warn...) and the output destination will be updated if and only if the |
| 172 | + /// logger was not initialized to log to a file. |
| 173 | + /// The default level is WARN. So, ERROR and WARN statements will be shown (basically, all that is bigger |
| 174 | + /// than the level code). |
| 175 | + /// If level is decreased at INFO, ERROR, WARN and INFO statements will be outputted, etc. |
| 176 | + /// |
| 177 | + /// # Example |
| 178 | + /// |
| 179 | + /// ```rust |
| 180 | + /// #[macro_use] |
| 181 | + /// extern crate log; |
| 182 | + /// extern crate logger; |
| 183 | + /// use logger::Logger; |
| 184 | + /// |
| 185 | + /// fn main() { |
| 186 | + /// Logger::new() |
| 187 | + /// .set_level(log::Level::Info) |
| 188 | + /// .init(None) |
| 189 | + /// .unwrap(); |
| 190 | + /// } |
| 191 | + /// ``` |
| 192 | + pub fn set_level(mut self, level: Level) -> Self { |
| 193 | + self.level_info.code = level; |
| 194 | + if self.level_info.writer != Destination::File { |
| 195 | + self.level_info.writer = get_default_destination(level); |
| 196 | + } |
| 197 | + self |
| 198 | + } |
| 199 | + |
| 200 | + /// Creates the first portion (to the left of the separator) |
| 201 | + /// of the log statement based on the logger settings. |
| 202 | + /// |
| 203 | + fn create_prefix(&self, record: &Record) -> String { |
| 204 | + let level_str = if self.show_level { |
| 205 | + record.level().to_string() |
| 206 | + } else { |
| 207 | + String::new() |
| 208 | + }; |
| 209 | + |
| 210 | + let file_path_str = if self.show_file_path { |
| 211 | + let pth = record.file().unwrap_or("unknown"); |
| 212 | + if self.show_level { |
| 213 | + format!("{}{}", IN_PREFIX_SEPARATOR, pth) |
| 214 | + } else { |
| 215 | + pth.into() |
| 216 | + } |
| 217 | + } else { |
| 218 | + String::new() |
| 219 | + }; |
| 220 | + |
| 221 | + let line_str = if self.show_line_numbers { |
| 222 | + if let Some(l) = record.line() { |
| 223 | + format!("{}{}", IN_PREFIX_SEPARATOR, l) |
| 224 | + } else { |
| 225 | + String::new() |
| 226 | + } |
| 227 | + } else { |
| 228 | + String::new() |
| 229 | + }; |
| 230 | + |
| 231 | + format!("[{}{}{}]", level_str, file_path_str, line_str) |
| 232 | + } |
| 233 | + |
| 234 | + /// Initialize log subsystem (once and only once). |
| 235 | + /// Every call made after the first will have no effect besides return `Ok` or `Err` |
| 236 | + /// appropriately (read description of error's enum items). |
| 237 | + /// |
| 238 | + /// # Example |
| 239 | + /// |
| 240 | + /// ```rust |
| 241 | + /// extern crate logger; |
| 242 | + /// use logger::Logger; |
| 243 | + /// |
| 244 | + /// fn main() { |
| 245 | + /// Logger::new() |
| 246 | + /// .init(None) |
| 247 | + /// .unwrap(); |
| 248 | + /// } |
| 249 | + /// ``` |
| 250 | + pub fn init(mut self, log_path: Option<String>) -> Result<()> { |
| 251 | + unsafe { |
| 252 | + if INIT_RES.is_err() { |
| 253 | + INIT_RES = Err(LoggerError::Poisoned(format!( |
| 254 | + "{}", |
| 255 | + INIT_RES.as_ref().err().unwrap() |
| 256 | + ))); |
| 257 | + return Err(LoggerError::Poisoned(format!( |
| 258 | + "{}", |
| 259 | + INIT_RES.as_ref().err().unwrap() |
| 260 | + ))); |
| 261 | + } |
| 262 | + INIT.call_once(|| { |
| 263 | + if let Some(path) = log_path.as_ref() { |
| 264 | + match FileLogWriter::new(path) { |
| 265 | + Ok(t) => { |
| 266 | + self.file = Some(Arc::new(t)); |
| 267 | + self.level_info.writer = Destination::File; |
| 268 | + } |
| 269 | + Err(ref e) => { |
| 270 | + INIT_RES = Err(LoggerError::NeverInitialized(format!("{}", e))); |
| 271 | + } |
| 272 | + }; |
| 273 | + } |
| 274 | + set_max_level(self.level_info.code.to_level_filter()); |
| 275 | + |
| 276 | + if let Err(e) = set_boxed_logger(Box::new(self)) { |
| 277 | + INIT_RES = Err(LoggerError::NeverInitialized(format!("{}", e))) |
| 278 | + } |
| 279 | + }); |
| 280 | + if INIT_RES.is_err() { |
| 281 | + return Err(LoggerError::NeverInitialized(format!( |
| 282 | + "{}", |
| 283 | + INIT_RES.as_ref().err().unwrap() |
| 284 | + ))); |
| 285 | + } |
| 286 | + } |
| 287 | + Ok(()) |
| 288 | + } |
| 289 | +} |
| 290 | + |
| 291 | +/// Implements trait log from the externally used log crate |
| 292 | +/// |
| 293 | +impl Log for Logger { |
| 294 | + // test whether a log level is enabled for the current module |
| 295 | + fn enabled(&self, metadata: &Metadata) -> bool { |
| 296 | + metadata.level() <= self.level_info.code |
| 297 | + } |
| 298 | + |
| 299 | + fn log(&self, record: &Record) { |
| 300 | + if self.enabled(record.metadata()) { |
| 301 | + let mut msg = format!( |
| 302 | + "{}{}{}", |
| 303 | + self.create_prefix(&record), |
| 304 | + MSG_SEPARATOR, |
| 305 | + record.args() |
| 306 | + ); |
| 307 | + |
| 308 | + match self.level_info.writer { |
| 309 | + Destination::Stderr => { |
| 310 | + eprintln!("{}", msg); |
| 311 | + } |
| 312 | + Destination::Stdout => { |
| 313 | + println!("{}", msg); |
| 314 | + } |
| 315 | + Destination::File => { |
| 316 | + if let Some(fw) = self.file.as_ref() { |
| 317 | + msg = format!("{}\n", msg); |
| 318 | + if let Err(e) = fw.write(&msg) { |
| 319 | + eprintln!("logger: Could not write to log file {}", e); |
| 320 | + } |
| 321 | + self.flush(); |
| 322 | + } else { |
| 323 | + // if destination of log is a file but no file writer was found, |
| 324 | + // should print error |
| 325 | + eprintln!("logger: Could not find a file to write to"); |
| 326 | + } |
| 327 | + } |
| 328 | + } |
| 329 | + } |
| 330 | + } |
| 331 | + |
| 332 | + fn flush(&self) { |
| 333 | + if let Some(fw) = self.file.as_ref() { |
| 334 | + if let Err(e) = fw.flush() { |
| 335 | + eprintln!("logger: Could not flush log content to disk {}", e); |
| 336 | + } |
| 337 | + } |
| 338 | + // everything else flushes by itself |
| 339 | + } |
| 340 | +} |
0 commit comments