rustic_rs/config/
logging.rs1use std::{path::PathBuf, sync::OnceLock};
2
3use anyhow::Result;
4use clap::{Parser, ValueHint};
5use conflate::Merge;
6use log::LevelFilter;
7use log4rs::{
8 Handle,
9 append::{
10 console::{ConsoleAppender, Target},
11 file::FileAppender,
12 },
13 config::{Appender, Config, Logger, Root},
14 encode::pattern::PatternEncoder,
15 filter::threshold::ThresholdFilter,
16};
17use serde::{Deserialize, Serialize};
18use serde_with::{DisplayFromStr, serde_as};
19
20use crate::config::progress_options::multi_progress;
21
22#[serde_as]
24#[derive(Default, Debug, Parser, Clone, Deserialize, Serialize, Merge)]
25#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
26pub struct LoggingOptions {
27 #[clap(long, global = true, env = "RUSTIC_LOG_LEVEL",
29 value_parser(["off", "error", "warn", "info", "debug", "trace"]))]
30 #[serde_as(as = "Option<DisplayFromStr>")]
31 #[merge(strategy=conflate::option::overwrite_none)]
32 pub log_level: Option<String>,
33
34 #[clap(long, global = true, env = "RUSTIC_LOG_LEVEL_LOGFILE",
36 value_parser(["off", "error", "warn", "info", "debug", "trace"]))]
37 #[merge(strategy=conflate::option::overwrite_none)]
38 pub log_level_logfile: Option<String>,
39
40 #[clap(long, global = true, env = "RUSTIC_LOG_LEVEL_DRYRUN",
42 value_parser(["off", "error", "warn", "info", "debug", "trace"]))]
43 #[merge(strategy=conflate::option::overwrite_none)]
44 pub log_level_dryrun: Option<String>,
45
46 #[clap(long, global = true, env = "RUSTIC_LOG_LEVEL_DEPENDENCIES",
48 value_parser(["off", "error", "warn", "info", "debug", "trace"]))]
49 #[merge(strategy=conflate::option::overwrite_none)]
50 pub log_level_dependencies: Option<String>,
51
52 #[clap(long, global = true, env = "RUSTIC_LOG_FILE", value_name = "LOGFILE", value_hint = ValueHint::FilePath)]
54 #[merge(strategy=conflate::option::overwrite_none)]
55 pub log_file: Option<PathBuf>,
56}
57
58impl LoggingOptions {
59 pub fn config(&self, dry_run: bool) -> Result<Config> {
60 let log_level = if dry_run {
61 &self.log_level_dryrun
62 } else {
63 &self.log_level
64 };
65
66 let level_filter = log_level
67 .as_ref()
68 .map_or(LevelFilter::Info, |l| l.parse().unwrap());
69 let level_filter_logfile = self
70 .log_level_logfile
71 .as_ref()
72 .map_or(LevelFilter::Info, |l| l.parse().unwrap());
73 let level_filter_dependencies = self
74 .log_level_dependencies
75 .as_ref()
76 .map_or(LevelFilter::Warn, |l| l.parse().unwrap());
77
78 let stdout = ConsoleAppender::builder()
79 .target(Target::Stderr)
80 .encoder(Box::new(PatternEncoder::new("{h([{l}])} {m}{n}")))
81 .build();
82 let stdout = PbPauseAppender(stdout);
83
84 let mut root_builder = Root::builder().appender("stdout");
85 let mut config_builder = Config::builder().appender(
86 Appender::builder()
87 .filter(Box::new(ThresholdFilter::new(level_filter)))
88 .build("stdout", Box::new(stdout)),
89 );
90
91 if let Some(file) = &self.log_file {
92 let file_appender = FileAppender::builder()
93 .encoder(Box::new(PatternEncoder::new("{d} [{l}] - {m}{n}")))
94 .build(file)?;
95 root_builder = root_builder.appender("logfile");
96 config_builder = config_builder.appender(
97 Appender::builder()
98 .filter(Box::new(ThresholdFilter::new(level_filter_logfile)))
99 .build("logfile", Box::new(file_appender)),
100 );
101 }
102
103 let root = root_builder.build(level_filter_dependencies);
104 let config = config_builder
105 .logger(Logger::builder().build("rustic_rs", LevelFilter::Trace))
106 .logger(Logger::builder().build("rustic_core", LevelFilter::Trace))
107 .logger(Logger::builder().build("rustic_backend", LevelFilter::Trace))
108 .build(root)?;
109 Ok(config)
110 }
111
112 pub fn start_logger(&self, dry_run: bool) -> Result<()> {
113 static HANDLE: OnceLock<Handle> = OnceLock::new();
114
115 let config = self.config(dry_run)?;
116 if let Some(handle) = HANDLE.get() {
117 handle.set_config(config);
118 } else {
119 let handle = log4rs::init_config(config)?;
120 _ = HANDLE.set(handle);
121 }
122 Ok(())
123 }
124}
125
126#[derive(Debug)]
128struct PbPauseAppender(ConsoleAppender);
129
130impl log4rs::append::Append for PbPauseAppender {
131 fn append(&self, record: &log::Record<'_>) -> Result<()> {
132 multi_progress().suspend(|| self.0.append(record))
133 }
134
135 fn flush(&self) {
136 self.0.flush();
142 }
143}