1
2use std::{cmp, process};
3use std::io::Write;
4use std::path::{Path, PathBuf};
5
6struct SplitLogger {
8 log1: env_logger::Logger,
9 log2: env_logger::Logger,
10}
11
12impl SplitLogger {
13 fn init(log1: env_logger::Logger, log2: env_logger::Logger) {
14 let max_level = cmp::max(log1.filter(), log2.filter());
15 log::set_boxed_logger(Box::new(SplitLogger {
16 log1: log1,
17 log2: log2,
18 })).expect("error initializing split logger");
19 log::set_max_level(max_level);
20 }
21}
22
23impl log::Log for SplitLogger {
24 fn enabled(&self, m: &log::Metadata) -> bool {
25 self.log1.enabled(m) || self.log2.enabled(m)
26 }
27
28 fn flush(&self) {
29 self.log1.flush();
30 self.log2.flush();
31 }
32
33 fn log(&self, rec: &log::Record) {
34 self.log1.log(rec);
35 self.log2.log(rec);
36 }
37}
38
39pub fn init_logging(
44 verbose: bool,
45 quiet: bool,
46 datadir: &Path,
47 logfile: Option<PathBuf>,
48 no_logfile: bool,
49) {
50 if verbose && quiet {
51 println!("Can't set both --verbose and --quiet");
52 process::exit(1);
53 }
54
55 fn base(verbose: bool) -> env_logger::Builder {
58 let mut builder = env_logger::Builder::new();
59 builder
60 .filter_module("rusqlite", log::LevelFilter::Warn)
61 .filter_module("bitcoind_async_client", log::LevelFilter::Debug)
65 .filter_module("bitcoincore_rpc", log::LevelFilter::Debug)
66 .filter_module("rustls", log::LevelFilter::Warn)
67 .filter_module("reqwest", log::LevelFilter::Warn)
68 .filter_module("ureq", log::LevelFilter::Warn)
69 .filter_module("ureq_proto", log::LevelFilter::Warn)
70 .filter_module("tracing::span", log::LevelFilter::Off)
75 .filter_module("hyper", log::LevelFilter::Warn)
76 .filter_module("h2", log::LevelFilter::Warn)
77 .filter_module("tower", log::LevelFilter::Warn)
78 .filter_module("tonic", log::LevelFilter::Info)
79 .filter_module("tokio_tungstenite", log::LevelFilter::Warn)
81 .filter_module("tungstenite", log::LevelFilter::Warn);
82
83 builder.filter_level(if verbose {
86 log::LevelFilter::Trace
87 } else {
88 log::LevelFilter::Info
89 });
90 builder.parse_env(env_logger::Env::new().filter("BARK_LOG"));
91
92 builder
93 }
94
95 let terminal = if !quiet {
96 let mut logger = base(verbose);
97
98 logger
99 .format(move |out, rec| {
100 let now = chrono::Local::now();
101 let ts = now.format("%Y-%m-%d %H:%M:%S.%3f %:z");
102 let lvl = rec.level();
103 let msg = rec.args();
104 if verbose {
105 let module = rec.module_path().expect("no module");
106 if module.starts_with("bark") {
107 let file = rec.file().expect("our macro provides file");
108 let file = file.split("bark/src/").last().unwrap();
109 let line = rec.line().expect("our macro provides line");
110 writeln!(out, "[{ts} {lvl: >5} {module} {file}:{line}] {msg}")
111 } else {
112 writeln!(out, "[{ts} {lvl: >5} {module}] {msg}")
113 }
114 } else {
115 writeln!(out, "[{ts} {lvl: >5}] {msg}")
116 }
117 })
118 .target(env_logger::Target::Stderr);
119 Some(logger)
120 } else {
121 None
122 };
123
124 let logfile_path = if no_logfile {
127 None
128 } else if let Some(path) = logfile {
129 Some(path)
130 } else if datadir.exists() {
131 Some(datadir.join("debug.log"))
132 } else {
133 None
134 };
135
136 let logfile = logfile_path.and_then(|path| {
137 let mut opts = std::fs::File::options();
138 opts.create(true).append(true);
139 #[cfg(unix)]
143 {
144 use std::os::unix::fs::OpenOptionsExt;
145 opts.mode(0o600);
146 }
147 match opts.open(&path) {
148 Ok(mut file) => {
149 let _ = file.write_all("\n\n".as_bytes());
151 let mut logger = base(verbose);
152 logger
153 .format_timestamp_millis()
154 .format_module_path(true)
155 .format_file(true)
156 .format_line_number(true)
157 .target(env_logger::Target::Pipe(Box::new(file)));
158 Some(logger)
159 },
160 Err(e) => {
161 eprintln!("Failed to open debug log file {}: {:#}", path.display(), e);
162 None
163 },
164 }
165 });
166
167 match (terminal, logfile) {
168 (Some(mut l1), Some(mut l2)) => SplitLogger::init(l1.build(), l2.build()),
169 (Some(mut l), None) => l.init(),
170 (None, Some(mut l)) => l.init(),
171 (None, None) => {},
172 }
173}