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::{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
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"), LevelFilter::Warn);
72 builder.filter(Some("hyper"), LevelFilter::Warn);
73 builder.filter(Some("rustls"), LevelFilter::Error);
74 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 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 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 .or_else(|err| match ROTATING_LOGGER.lock().upgrade() {
140 Some(l) => Ok(l),
141 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}