Skip to content

Commit 4d9fa2d

Browse files
dianpopadpopa
authored and
dpopa
committed
logger: adding crate for syslog-free logging subsystem
Simple logger that uses the external log crate to output log messages to either stdout, stderr or a file. Writing to the file is thread safe. Signed-off-by: Diana Popa <[email protected]>
1 parent a4d1e93 commit 4d9fa2d

File tree

4 files changed

+437
-0
lines changed

4 files changed

+437
-0
lines changed

logger/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "logger"
3+
version = "0.1.0"
4+
authors = ["Amazon firecracker team <[email protected]>"]
5+
6+
[dependencies]
7+
libc = ">=0.2.39"
8+
log = { version = "0.4", features = ["std"] }
9+

logger/src/error.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//! Errors returned by the logger.
2+
3+
use std;
4+
use std::error::Error;
5+
use std::fmt;
6+
7+
#[derive(Debug)]
8+
pub enum LoggerError {
9+
/// First attempt at initialization failed.
10+
NeverInitialized(String),
11+
/// Initialization has previously failed and can not be retried.
12+
Poisoned(String),
13+
/// Creating log file fails.
14+
CreateLogFile(std::io::Error),
15+
/// Writing to log file fails.
16+
FileLogWrite(std::io::Error),
17+
/// Flushing to disk fails.
18+
FileLogFlush(std::io::Error),
19+
/// Error obtaining lock on mutex.
20+
FileLogLock(String),
21+
}
22+
23+
impl fmt::Display for LoggerError {
24+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25+
let printable = match *self {
26+
LoggerError::NeverInitialized(ref e) => e,
27+
LoggerError::Poisoned(ref e) => e,
28+
LoggerError::CreateLogFile(ref e) => e.description(),
29+
LoggerError::FileLogWrite(ref e) => e.description(),
30+
LoggerError::FileLogFlush(ref e) => e.description(),
31+
LoggerError::FileLogLock(ref e) => e,
32+
};
33+
write!(f, "{}", printable)
34+
}
35+
}
36+

logger/src/lib.rs

Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
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

Comments
 (0)