1extern 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
64pub 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 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 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 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 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 .or_else(|err| match ROTATING_LOGGER.lock().upgrade() {
141 Some(l) => Ok(l),
142 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}