1use std::{
2 env, fmt, fs,
3 io::Write,
4 path::{Path, PathBuf},
5 str::FromStr
6};
7
8use time::macros::format_description;
9
10use tracing_subscriber::{fmt::time::UtcTime, EnvFilter};
11
12#[cfg(feature = "clap")]
13use clap::ValueEnum;
14
15use crate::err::Error;
16
17
18#[derive(Default)]
19enum LogOut {
20 #[default]
21 Console,
22
23 #[cfg(windows)]
24 WinEvtLog { svcname: String }
25}
26
27pub struct LumberJack {
29 init: bool,
30 log_out: LogOut,
31 log_level: LogLevel,
32 trace_filter: Option<String>,
33 trace_file: Option<PathBuf>
35}
36
37impl Default for LumberJack {
38 fn default() -> Self {
50 let log_level =
51 std::env::var("LOG_LEVEL").map_or(LogLevel::Warn, |level| {
52 level
53 .parse::<LogLevel>()
54 .map_or(LogLevel::Warn, |level| level)
55 });
56
57 let trace_file =
58 std::env::var("TRACE_FILE").map_or(None, |v| Some(PathBuf::from(v)));
59 let trace_filter = env::var("TRACE_FILTER").ok();
60
61 Self {
62 init: true,
63 log_out: LogOut::default(),
64 log_level,
65 trace_filter,
66 trace_file
68 }
69 }
70}
71
72impl LumberJack {
73 #[must_use]
75 pub fn new() -> Self {
76 Self::default()
77 }
78
79 #[must_use]
83 pub fn noinit() -> Self {
84 Self {
85 init: false,
86 ..Default::default()
87 }
88 }
89
90 #[must_use]
91 pub const fn set_init(mut self, flag: bool) -> Self {
92 self.init = flag;
93 self
94 }
95
96 #[cfg(windows)]
101 pub fn from_winsvc(svcname: &str) -> Result<Self, Error> {
102 let params = crate::rt::winsvc::get_service_param(svcname)?;
103 let loglevel = params
104 .get_value::<String, &str>("LogLevel")
105 .unwrap_or_else(|_| String::from("warn"))
106 .parse::<LogLevel>()
107 .unwrap_or(LogLevel::Warn);
108 let tracefilter = params.get_value::<String, &str>("TraceFilter");
109 let tracefile = params.get_value::<String, &str>("TraceFile");
110
111 let mut this = Self::new().log_level(loglevel);
112 this.log_out = LogOut::WinEvtLog {
113 svcname: svcname.to_string()
114 };
115 let this =
116 if let (Ok(tracefilter), Ok(tracefile)) = (tracefilter, tracefile) {
117 this.trace_filter(tracefilter).trace_file(tracefile)
118 } else {
119 this
120 };
121
122 Ok(this)
123 }
124
125 #[must_use]
127 pub const fn log_level(mut self, level: LogLevel) -> Self {
128 self.log_level = level;
129 self
130 }
131
132 #[must_use]
134 #[allow(clippy::needless_pass_by_value)]
135 pub fn trace_filter(mut self, filter: impl ToString) -> Self {
136 self.trace_filter = Some(filter.to_string());
137 self
138 }
139
140 #[must_use]
143 pub fn trace_file<P>(mut self, fname: P) -> Self
144 where
145 P: AsRef<Path>
146 {
147 self.trace_file = Some(fname.as_ref().to_path_buf());
148 self
149 }
150
151 pub fn init(self) -> Result<(), Error> {
156 if self.init {
157 match self.log_out {
158 LogOut::Console => {
159 init_console_logging()?;
160 }
161 #[cfg(windows)]
162 LogOut::WinEvtLog { svcname } => {
163 eventlog::init(&svcname, log::Level::Trace)?;
164 log::set_max_level(self.log_level.into());
165 }
166 }
167
168 if let Some(fname) = self.trace_file {
169 init_file_tracing(fname, self.trace_filter.as_deref());
170 } else {
171 init_console_tracing(self.trace_filter.as_deref());
172 }
173 }
174 Ok(())
175 }
176}
177
178
179#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
180#[cfg_attr(feature = "clap", derive(ValueEnum))]
181pub enum LogLevel {
182 #[cfg_attr(feature = "clap", clap(name = "off"))]
184 Off,
185
186 #[cfg_attr(feature = "clap", clap(name = "error"))]
188 Error,
189
190 #[cfg_attr(feature = "clap", clap(name = "warn"))]
192 #[default]
193 Warn,
194
195 #[cfg_attr(feature = "clap", clap(name = "info"))]
197 Info,
198
199 #[cfg_attr(feature = "clap", clap(name = "debug"))]
201 Debug,
202
203 #[cfg_attr(feature = "clap", clap(name = "trace"))]
205 Trace
206}
207
208impl FromStr for LogLevel {
209 type Err = String;
210
211 fn from_str(s: &str) -> Result<Self, Self::Err> {
212 match s {
213 "off" => Ok(Self::Off),
214 "error" => Ok(Self::Error),
215 "warn" => Ok(Self::Warn),
216 "info" => Ok(Self::Info),
217 "debug" => Ok(Self::Debug),
218 "trace" => Ok(Self::Trace),
219 _ => Err(format!("Unknown log level '{s}'"))
220 }
221 }
222}
223
224impl fmt::Display for LogLevel {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 let s = match self {
227 Self::Off => "off",
228 Self::Error => "error",
229 Self::Warn => "warn",
230 Self::Info => "info",
231 Self::Debug => "debug",
232 Self::Trace => "trace"
233 };
234 write!(f, "{s}")
235 }
236}
237
238impl From<LogLevel> for log::LevelFilter {
239 fn from(ll: LogLevel) -> Self {
240 match ll {
241 LogLevel::Off => Self::Off,
242 LogLevel::Error => Self::Error,
243 LogLevel::Warn => Self::Warn,
244 LogLevel::Info => Self::Info,
245 LogLevel::Debug => Self::Debug,
246 LogLevel::Trace => Self::Trace
247 }
248 }
249}
250
251impl From<LogLevel> for Option<tracing::Level> {
252 fn from(ll: LogLevel) -> Self {
253 match ll {
254 LogLevel::Off => None,
255 LogLevel::Error => Some(tracing::Level::ERROR),
256 LogLevel::Warn => Some(tracing::Level::WARN),
257 LogLevel::Info => Some(tracing::Level::INFO),
258 LogLevel::Debug => Some(tracing::Level::DEBUG),
259 LogLevel::Trace => Some(tracing::Level::TRACE)
260 }
261 }
262}
263
264
265pub fn init_console_logging() -> Result<(), Error> {
267 let lf: log::LevelFilter = if let Ok(val) = std::env::var("LOG_LEVEL") {
271 if let Ok(ll) = val.parse::<LogLevel>() {
272 ll.into()
273 } else {
274 return Err(Error::bad_format("Unknown log level specified"));
275 }
276 } else {
277 log::LevelFilter::Warn
279 };
280
281 env_logger::Builder::new()
282 .format(|buf, record| {
284 writeln!(
285 buf,
286 "{} [{}] - {}",
287 chrono::Utc::now().format("%Y-%m-%d %H:%M:%S"),
288 record.level(),
289 record.args()
290 )
291 })
292 .filter(None, lf)
293 .init();
294
295 Ok(())
296}
297
298
299pub fn init_console_tracing(filter: Option<&str>) {
300 let filter = filter.map_or_else(|| EnvFilter::new("none"), EnvFilter::new);
302
303 tracing_subscriber::fmt()
304 .with_env_filter(filter)
305 .init();
307}
308
309
310pub fn init_file_tracing<P>(fname: P, filter: Option<&str>)
324where
325 P: AsRef<Path>
326{
327 let timer = UtcTime::new(format_description!(
331 "[year]-[month]-[day] [hour]:[minute]:[second]"
332 ));
333
334 let Ok(f) = fs::OpenOptions::new().create(true).append(true).open(fname)
335 else {
336 return;
337 };
338
339 let filter = filter.map_or_else(|| EnvFilter::new("warn"), EnvFilter::new);
344
345 tracing_subscriber::fmt()
346 .with_env_filter(filter)
347 .with_writer(f)
348 .with_ansi(false)
349 .with_timer(timer)
350 .init();
351
352 }
354
355