ethcore_logger/
lib.rs

1// Copyright 2015-2017 Parity Technologies (UK) Ltd.
2// This file is part of Parity.
3
4// Parity 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// Parity 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 Parity.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Logger for parity 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::LogBuilder;
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"), LogLevelFilter::Warn);
72	builder.filter(Some("reqwest"), LogLevelFilter::Warn);
73	builder.filter(Some("hyper"), LogLevelFilter::Warn);
74	builder.filter(Some("rustls"), LogLevelFilter::Warn);
75	// Enable info for others.
76	builder.filter(None, LogLevelFilter::Info);
77
78	if let Ok(lvl) = env::var("RUST_LOG") {
79		levels.push_str(&lvl);
80		levels.push_str(",");
81		builder.parse(&lvl);
82	}
83
84	if let Some(ref s) = config.mode {
85		levels.push_str(s);
86		builder.parse(s);
87	}
88
89	let isatty = atty::is(atty::Stream::Stderr);
90	let enable_color = config.color && isatty;
91	let logs = Arc::new(RotatingLogger::new(levels));
92	let logger = logs.clone();
93	let mut open_options = fs::OpenOptions::new();
94
95	let maybe_file = match config.file.as_ref() {
96		Some(f) => Some(open_options
97			.append(true).create(true).open(f)
98			.map_err(|_| format!("Cannot write to log file given: {}", f))?),
99		None => None,
100	};
101
102	let format = move |record: &LogRecord| {
103		let timestamp = time::strftime("%Y-%m-%d %H:%M:%S %Z", &time::now()).unwrap();
104
105		let with_color = if max_log_level() <= LogLevelFilter::Info {
106			format!("{} {}", Colour::Black.bold().paint(timestamp), record.args())
107		} else {
108			let name = thread::current().name().map_or_else(Default::default, |x| format!("{}", Colour::Blue.bold().paint(x)));
109			format!("{} {} {} {}  {}", Colour::Black.bold().paint(timestamp), name, record.level(), record.target(), record.args())
110		};
111
112		let removed_color = kill_color(with_color.as_ref());
113
114		let ret = match enable_color {
115			true => with_color,
116			false => removed_color.clone(),
117		};
118
119		if let Some(mut file) = maybe_file.as_ref() {
120			// ignore errors - there's nothing we can do
121			let _ = file.write_all(removed_color.as_bytes());
122			let _ = file.write_all(b"\n");
123		}
124		logger.append(removed_color);
125		if !isatty && record.level() <= LogLevel::Info && atty::is(atty::Stream::Stdout) {
126			// duplicate INFO/WARN output to console
127			println!("{}", ret);
128		}
129
130		ret
131    };
132
133	builder.format(format);
134	builder.init()
135		.and_then(|_| {
136			*ROTATING_LOGGER.lock() = Arc::downgrade(&logs);
137			Ok(logs)
138		})
139		// couldn't create new logger - try to fall back on previous logger.
140		.or_else(|err| match ROTATING_LOGGER.lock().upgrade() {
141			Some(l) => Ok(l),
142			// no previous logger. fatal.
143			None => Err(format!("{:?}", err)),
144		})
145}
146
147fn kill_color(s: &str) -> String {
148	lazy_static! {
149		static ref RE: Regex = Regex::new("\x1b\\[[^m]+m").unwrap();
150	}
151	RE.replace_all(s, "").to_string()
152}
153
154#[test]
155fn should_remove_colour() {
156	let before = "test";
157	let after = kill_color(&Colour::Red.bold().paint(before));
158	assert_eq!(after, "test");
159}
160
161#[test]
162fn should_remove_multiple_colour() {
163	let t = format!("{} {}", Colour::Red.bold().paint("test"), Colour::White.normal().paint("again"));
164	let after = kill_color(&t);
165	assert_eq!(after, "test again");
166}