timber/timber.rs
1// Copyright 2016-2017 The Perceptia Project Developers
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
4// and associated documentation files (the "Software"), to deal in the Software without
5// restriction, including without limitation the rights to use, copy, modify, merge, publish,
6// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
7// Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
13// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
14// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
15// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
18//! `Timber` is simple logger facility. It provides means to write logs to given file in concurrent
19//! applications.
20//!
21//! `timber!` macro takes as argument level number and writes log only if it is greater than zero.
22//! If user defines log levels as constants compiler will be able to ignore strings passed to unused
23//! logs and make application smaller. This way user can keep set of debugging logs, but compile
24//! them out for release.
25//!
26//! By default `timber` writes logs to `stdout`. To write to a file one have to pass file path with
27//! `timber::init(path)`.
28//!
29//! Example wrapper for `timber` could look like:
30//!
31//! ```
32//! #[macro_use(timber)]
33//! use timber;
34//!
35//! #[cfg(debug)]
36//! pub mod level {
37//! pub const ERR: i32 = 1;
38//! pub const DEB: i32 = 2;
39//! pub const INF: i32 = 7;
40//! }
41//!
42//! #[cfg(not(debug))]
43//! pub mod level {
44//! pub const ERR: i32 = 1;
45//! pub const DEB: i32 = 0;
46//! pub const INF: i32 = 3;
47//! }
48//!
49//! macro_rules! log_err{($($arg:tt)*) => {timber!($crate::level::ERR, "ERR", $($arg)*)}}
50//! macro_rules! log_deb{($($arg:tt)*) => {timber!($crate::level::DEB, "DEB", $($arg)*)}}
51//! macro_rules! log_inf{($($arg:tt)*) => {timber!($crate::level::INF, "INF", $($arg)*)}}
52//!
53//! //log_err!("This is error! I'm visible!");
54//! //log_deb!("I'm debug. I'm visible only in debug mode.");
55//! ```
56
57// -------------------------------------------------------------------------------------------------
58
59extern crate time;
60
61use std::sync::{Arc, Mutex, MutexGuard, PoisonError, Once, ONCE_INIT};
62use std::io::Write;
63
64// -------------------------------------------------------------------------------------------------
65
66/// Prints timber (processed log). Timber prints time (with microseconds), name of current thread,
67/// line number and module name + log text.
68#[macro_export]
69macro_rules! timber {
70 ($lnum:expr, $lname:expr, $($arg:tt)*) => {
71 if $lnum > 0 {
72 $crate::timber($lname, module_path!(), line!(), format_args!($($arg)*))
73 }
74 };
75}
76
77// -------------------------------------------------------------------------------------------------
78
79/// Timber struct - used as singleton.
80pub struct Timber {
81 log_file: Option<std::fs::File>,
82}
83
84// -------------------------------------------------------------------------------------------------
85
86/// Wrapper for `Timber` struct ensuring thread safety.
87struct Wrapper {
88 inner: Arc<Mutex<Timber>>,
89}
90
91// -------------------------------------------------------------------------------------------------
92
93impl Timber {
94 /// Print not formated log.
95 pub fn log(&mut self, args: std::fmt::Arguments) {
96 match self.log_file {
97 Some(ref mut log_file) => {
98 log_file.write(format!("{}", args).as_bytes()).expect("Failed to log!");
99 }
100 None => {
101 print!("{}", args);
102 }
103 }
104 }
105
106 /// Print formated log.
107 pub fn timber(&mut self, level: &str, module: &str, line: u32, args: std::fmt::Arguments) {
108 // Get local time
109 let tm = time::now().to_local();
110
111 // Get current thread name
112 let current_thread = std::thread::current();
113 let thread = current_thread.name().unwrap_or("<unknown>");
114
115 // Format log entry
116 let entry = format!("{:02}:{:02}:{:02}.{:06} | {} | {:16} | {:4} | {:40} | {}",
117 tm.tm_hour,
118 tm.tm_min,
119 tm.tm_sec,
120 tm.tm_nsec / 1000,
121 level,
122 thread,
123 line,
124 module,
125 args);
126
127 // Write log entry
128 match self.log_file {
129 Some(ref mut log_file) => {
130 log_file.write(entry.as_bytes()).expect("Failed to timber!");
131 log_file.write("\n".as_bytes()).expect("Failed to timber!");
132 }
133 None => {
134 println!("{}", entry);
135 }
136 }
137 }
138
139 /// Initialize logger by providing output log file. Before call to this method logs will be
140 /// printed to standard output.
141 pub fn init(&mut self, path: &std::path::Path) -> Result<(), std::io::Error> {
142 self.log_file = Some(std::fs::File::create(path)?);
143 Ok(())
144 }
145}
146
147// -------------------------------------------------------------------------------------------------
148
149/// Get instance of logger singleton.
150fn get_instance() -> &'static Wrapper {
151 static mut LOGGER: *const Wrapper = 0 as *const Wrapper;
152 static ONCE: Once = ONCE_INIT;
153
154 unsafe {
155 ONCE.call_once(|| {
156 let logger = Wrapper { inner: Arc::new(Mutex::new(Timber { log_file: None })) };
157
158 LOGGER = std::mem::transmute(Box::new(logger));
159 });
160
161 &(*LOGGER)
162 }
163}
164
165// -------------------------------------------------------------------------------------------------
166
167/// Get locked instance of `Timber` for guarded loging.
168pub fn lock<'a>() -> Result<MutexGuard<'a, Timber>, PoisonError<MutexGuard<'a, Timber>>> {
169 get_instance().inner.lock()
170}
171
172// -------------------------------------------------------------------------------------------------
173
174/// Print formated log.
175pub fn timber(level: &str, module: &str, line: u32, args: std::fmt::Arguments) {
176 let mut timber = get_instance().inner.lock().unwrap();
177 timber.timber(level, module, line, args);
178}
179
180// -------------------------------------------------------------------------------------------------
181
182/// Initialize logger by providing output log file. Before call to this method logs will be printed
183/// to standard output.
184pub fn init(path: &std::path::Path) -> Result<(), std::io::Error> {
185 let mut timber = get_instance().inner.lock().unwrap();
186 timber.init(path)
187}
188
189// -------------------------------------------------------------------------------------------------