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// -------------------------------------------------------------------------------------------------