1use anyhow::Result;
2use flexi_logger::{
3 Age, Cleanup, Criterion, DeferredNow, Duplicate, FileSpec, LogSpecification, Logger, Naming,
4 Record,
5};
6use log::LevelFilter;
7use serde::{Deserialize, Serialize};
8const TIME_FORMAT: &str = "%Y.%m.%d %H:%M:%S%.3f";
9#[derive(Clone, Debug, Serialize, Deserialize)]
10pub enum LogStyle {
11 Default,
12 Line,
13 Module,
14 Full,
15}
16
17#[derive(Clone, Debug, Serialize, Deserialize)]
18pub struct LogConfig {
19 #[serde(default = "default_level")]
21 pub level: u8,
22 #[serde(default = "default_usize")]
24 pub size_mb: u64,
25 #[serde(default = "default_console")]
27 pub console: bool,
28 #[serde(default = "default_log_dir")]
30 pub dir: String,
31 #[serde(default = "default_keep_day")]
33 pub keep_day: usize,
34 pub filters: Option<Vec<String>>,
36 pub log_name: Option<String>,
38 pub style: LogStyle,
40 pub cleanup_sync: Option<bool>,
42}
43
44fn default_usize() -> u64 {
45 3
46}
47fn default_console() -> bool {
48 true
49}
50fn default_keep_day() -> usize {
51 3
52}
53fn default_level() -> u8 {
54 4
55}
56fn default_log_dir() -> String {
57 "./logs".to_string()
58}
59
60impl Default for LogConfig {
61 fn default() -> Self {
62 Self {
63 level: default_level(),
64 size_mb: default_usize(),
65 console: default_console(),
66 dir: default_log_dir(),
67 keep_day: default_keep_day(),
68 filters: None,
69 log_name: None,
70 style: LogStyle::Default,
71 cleanup_sync: Some(false),
72 }
73 }
74}
75
76pub fn string_to_level(level: &str) -> u8 {
77 let v = match level {
78 "trace" | "TRACE" => 5,
79 "debug" | "DEBUG" => 4,
80 "info" | "INFO" => 3,
81 "warn" | "WARN" => 2,
82 "error" | "ERROR" => 1,
83 _ => 4,
84 };
85 v
86}
87
88pub fn _default(
89 w: &mut dyn std::io::Write,
90 now: &mut DeferredNow,
91 record: &Record,
92) -> Result<(), std::io::Error> {
93 if record.level() == LevelFilter::Error || record.level() == LevelFilter::Warn {
94 write!(
95 w,
96 "{} [{}] {}:{}: ",
97 now.format(TIME_FORMAT),
98 record.level(),
99 record.file().unwrap_or("<unnamed>"),
100 record.line().unwrap_or(0),
101 )?;
102 } else {
103 write!(w, "{} [{}]: ", now.format(TIME_FORMAT), record.level(),)?;
104 }
105 write!(w, "{}", &record.args())
106}
107
108pub fn _line(
109 w: &mut dyn std::io::Write,
110 now: &mut DeferredNow,
111 record: &Record,
112) -> Result<(), std::io::Error> {
113 write!(
114 w,
115 "{} [{}] {}:{}: ",
116 now.format(TIME_FORMAT),
117 record.level(),
118 record.file().unwrap_or("<unnamed>"),
119 record.line().unwrap_or(0),
120 )?;
121 write!(w, "{}", &record.args())
122}
123
124pub fn _module(
125 w: &mut dyn std::io::Write,
126 now: &mut DeferredNow,
127 record: &Record,
128) -> Result<(), std::io::Error> {
129 write!(
130 w,
131 "{} [{}] [{}]: ",
132 now.format(TIME_FORMAT),
133 record.level(),
134 record.module_path().unwrap_or("<unnamed>"),
135 )?;
136 write!(w, "{}", &record.args())
137}
138
139pub fn _full(
140 w: &mut dyn std::io::Write,
141 now: &mut DeferredNow,
142 record: &Record,
143) -> Result<(), std::io::Error> {
144 write!(
145 w,
146 "{} [{}] [{}] {}:{}: ",
147 now.format(TIME_FORMAT),
148 record.level(),
149 record.module_path().unwrap_or("<unnamed>"),
150 record.file().unwrap_or("<unnamed>"),
151 record.line().unwrap_or(0),
152 )?;
153 write!(w, "{}", &record.args())
154}
155pub fn setup(cfg: LogConfig) -> Result<()> {
156 let level = match cfg.level {
157 1 => LevelFilter::Error,
158 2 => LevelFilter::Warn,
159 3 => LevelFilter::Info,
160 4 => LevelFilter::Debug,
161 5 => LevelFilter::Trace,
162 _ => LevelFilter::Debug,
163 };
164
165 let mut log_spec = LogSpecification::builder();
166 log_spec.default(level);
167 if let Some(items) = &cfg.filters {
168 for m in items {
169 log_spec.module(m, LevelFilter::Warn);
170 }
171 }
172
173 let hand = Logger::with(log_spec.build());
174
175 let hand = match cfg.style {
177 LogStyle::Line => hand.format(_line),
178 LogStyle::Module => hand.format(_module),
179 LogStyle::Full => hand.format(_full),
180 _ => hand.format(_default),
181 };
182
183 let basename = match &cfg.log_name {
184 Some(name) => name,
185 None => "",
186 };
187 let filespec = FileSpec::default().directory(cfg.dir).basename(basename);
188
189 let mut hand = hand
190 .rotate(
191 Criterion::AgeOrSize(Age::Day, cfg.size_mb * 1024 * 1024),
192 Naming::TimestampsCustomFormat {
193 current_infix: None,
194 format: "r%Y-%m-%d_%H-%M-%S",
195 },
196 Cleanup::KeepForDays(cfg.keep_day),
197 )
198 .cleanup_in_background_thread(cfg.cleanup_sync.unwrap_or(false))
199 .log_to_file(filespec);
200
201 if cfg.console {
202 let d = match cfg.level {
203 1 => Duplicate::Error,
204 2 => Duplicate::Warn,
205 3 => Duplicate::Info,
206 4 => Duplicate::Debug,
207 5 => Duplicate::Trace,
208 _ => Duplicate::Debug,
209 };
210 hand = hand.duplicate_to_stdout(d)
211 }
212 hand.start()?;
213
214 Ok(())
215}