vapcore_logger/
lib.rs

1// Copyright 2015-2020 Parity Technologies (UK) Ltd.
2// This file is part of Tetsy Vapory.
3
4// Tetsy Vapory is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Tetsy Vapory is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Tetsy Vapory.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Logger for tetsy executables
18
19extern crate ansi_term;
20extern crate arrayvec;
21extern crate atty;
22extern crate env_logger;
23extern crate log as rlog;
24extern crate parking_lot;
25extern crate regex;
26extern crate time;
27
28#[macro_use]
29extern crate lazy_static;
30
31mod rotating;
32
33use std::{env, thread, fs};
34use std::sync::{Weak, Arc};
35use std::io::Write;
36use env_logger::{Builder as LogBuilder, Formatter};
37use regex::Regex;
38use ansi_term::Colour;
39use parking_lot::Mutex;
40
41pub use rotating::{RotatingLogger, init_log};
42
43#[derive(Debug, PartialEq, Clone)]
44pub struct Config {
45	pub mode: Option<String>,
46	pub color: bool,
47	pub file: Option<String>,
48}
49
50impl Default for Config {
51	fn default() -> Self {
52		Config {
53			mode: None,
54			color: !cfg!(windows),
55			file: None,
56		}
57	}
58}
59
60lazy_static! {
61	static ref ROTATING_LOGGER : Mutex<Weak<RotatingLogger>> = Mutex::new(Default::default());
62}
63
64/// Sets up the logger
65pub fn setup_log(config: &Config) -> Result<Arc<RotatingLogger>, String> {
66	use rlog::*;
67
68	let mut levels = String::new();
69	let mut builder = LogBuilder::new();
70	// Disable info logging by default for some modules:
71	builder.filter(Some("ws"), LevelFilter::Warn);
72	builder.filter(Some("hyper"), LevelFilter::Warn);
73	builder.filter(Some("rustls"), LevelFilter::Error);
74	// Enable info for others.
75	builder.filter(None, LevelFilter::Info);
76
77	if let Ok(lvl) = env::var("RUST_LOG") {
78		levels.push_str(&lvl);
79		levels.push_str(",");
80		builder.parse(&lvl);
81	}
82
83	if let Some(ref s) = config.mode {
84		levels.push_str(s);
85		builder.parse(s);
86	}
87
88	let isatty = atty::is(atty::Stream::Stderr);
89	let enable_color = config.color && isatty;
90	let logs = Arc::new(RotatingLogger::new(levels));
91	let logger = logs.clone();
92	let mut open_options = fs::OpenOptions::new();
93
94	let maybe_file = match config.file.as_ref() {
95		Some(f) => Some(open_options
96			.append(true).create(true).open(f)
97			.map_err(|e| format!("Cannot write to log file given: {}, {}", f, e))?),
98		None => None,
99	};
100
101	let format = move |buf: &mut Formatter, record: &Record| {
102		let timestamp = time::strftime("%Y-%m-%d %H:%M:%S %Z", &time::now()).unwrap();
103
104		let with_color = if max_level() <= LevelFilter::Info {
105			format!("{} {}", Colour::Black.bold().paint(timestamp), record.args())
106		} else {
107			let name = thread::current().name().map_or_else(Default::default, |x| format!("{}", Colour::Blue.bold().paint(x)));
108			format!("{} {} {} {}  {}", Colour::Black.bold().paint(timestamp), name, record.level(), record.target(), record.args())
109		};
110
111		let removed_color = kill_color(with_color.as_ref());
112
113		let ret = match enable_color {
114			true => with_color,
115			false => removed_color.clone(),
116		};
117
118		if let Some(mut file) = maybe_file.as_ref() {
119			// ignore errors - there's nothing we can do
120			let _ = file.write_all(removed_color.as_bytes());
121			let _ = file.write_all(b"\n");
122		}
123		logger.append(removed_color);
124		if !isatty && record.level() <= Level::Info && atty::is(atty::Stream::Stdout) {
125			// duplicate INFO/WARN output to console
126			println!("{}", ret);
127		}
128
129		writeln!(buf, "{}", ret)
130    };
131
132	builder.format(format);
133	builder.try_init()
134		.and_then(|_| {
135			*ROTATING_LOGGER.lock() = Arc::downgrade(&logs);
136			Ok(logs)
137		})
138		// couldn't create new logger - try to fall back on previous logger.
139		.or_else(|err| match ROTATING_LOGGER.lock().upgrade() {
140			Some(l) => Ok(l),
141			// no previous logger. fatal.
142			None => Err(format!("{:?}", err)),
143		})
144}
145
146fn kill_color(s: &str) -> String {
147	lazy_static! {
148		static ref RE: Regex = Regex::new("\x1b\\[[^m]+m").unwrap();
149	}
150	RE.replace_all(s, "").to_string()
151}
152
153#[test]
154fn should_remove_colour() {
155	let before = "test";
156	let after = kill_color(&Colour::Red.bold().paint(before));
157	assert_eq!(after, "test");
158}
159
160#[test]
161fn should_remove_multiple_colour() {
162	let t = format!("{} {}", Colour::Red.bold().paint("test"), Colour::White.normal().paint("again"));
163	let after = kill_color(&t);
164	assert_eq!(after, "test again");
165}