1#[macro_use]
39extern crate log;
40#[macro_use]
41extern crate nydus_error;
42#[macro_use]
43extern crate serde;
44
45use std::env::current_dir;
46use std::io::Result;
47use std::path::PathBuf;
48
49use flexi_logger::{
50 self, style, Cleanup, Criterion, DeferredNow, FileSpec, Logger, Naming,
51 TS_DASHES_BLANK_COLONS_DOT_BLANK,
52};
53use log::{Level, LevelFilter, Record};
54
55pub mod signal;
56
57pub fn log_level_to_verbosity(level: log::LevelFilter) -> usize {
58 if level == log::LevelFilter::Off {
59 0
60 } else {
61 level as usize - 1
62 }
63}
64
65pub mod built_info {
66 pub const PROFILE: &str = env!("PROFILE");
67 pub const RUSTC_VERSION: &str = env!("RUSTC_VERSION");
68 pub const BUILT_TIME_UTC: &str = env!("BUILT_TIME_UTC");
69 pub const GIT_COMMIT_VERSION: &str = env!("GIT_COMMIT_VERSION");
70 pub const GIT_COMMIT_HASH: &str = env!("GIT_COMMIT_HASH");
71}
72
73pub fn dump_program_info() {
75 info!(
76 "Program Version: {}, Git Commit: {:?}, Build Time: {:?}, Profile: {:?}, Rustc Version: {:?}",
77 built_info::GIT_COMMIT_VERSION,
78 built_info::GIT_COMMIT_HASH,
79 built_info::BUILT_TIME_UTC,
80 built_info::PROFILE,
81 built_info::RUSTC_VERSION,
82 );
83}
84
85#[derive(Serialize, Clone)]
87pub struct BuildTimeInfo {
88 pub package_ver: String,
89 pub git_commit: String,
90 build_time: String,
91 profile: String,
92 rustc: String,
93}
94
95impl BuildTimeInfo {
96 pub fn dump() -> (String, Self) {
97 let info_string = format!(
98 "\rVersion: \t{}\nGit Commit: \t{}\nBuild Time: \t{}\nProfile: \t{}\nRustc: \t\t{}\n",
99 built_info::GIT_COMMIT_VERSION,
100 built_info::GIT_COMMIT_HASH,
101 built_info::BUILT_TIME_UTC,
102 built_info::PROFILE,
103 built_info::RUSTC_VERSION,
104 );
105
106 let info = Self {
107 package_ver: built_info::GIT_COMMIT_VERSION.to_string(),
108 git_commit: built_info::GIT_COMMIT_HASH.to_string(),
109 build_time: built_info::BUILT_TIME_UTC.to_string(),
110 profile: built_info::PROFILE.to_string(),
111 rustc: built_info::RUSTC_VERSION.to_string(),
112 };
113
114 (info_string, info)
115 }
116}
117
118fn get_file_name<'a>(record: &'a Record) -> Option<&'a str> {
119 record.file().map(|v| match v.rfind("/src/") {
120 None => v,
121 Some(pos) => match v[..pos].rfind('/') {
122 None => &v[pos..],
123 Some(p) => &v[p..],
124 },
125 })
126}
127
128fn opt_format(
129 w: &mut dyn std::io::Write,
130 now: &mut DeferredNow,
131 record: &Record,
132) -> std::result::Result<(), std::io::Error> {
133 let level = record.level();
134 if level == Level::Info {
135 write!(
136 w,
137 "[{}] {} {}",
138 now.format(TS_DASHES_BLANK_COLONS_DOT_BLANK),
139 record.level(),
140 &record.args()
141 )
142 } else {
143 write!(
144 w,
145 "[{}] {} [{}:{}] {}",
146 now.format(TS_DASHES_BLANK_COLONS_DOT_BLANK),
147 record.level(),
148 get_file_name(record).unwrap_or("<unnamed>"),
149 record.line().unwrap_or(0),
150 &record.args()
151 )
152 }
153}
154
155fn colored_opt_format(
156 w: &mut dyn std::io::Write,
157 now: &mut DeferredNow,
158 record: &Record,
159) -> std::result::Result<(), std::io::Error> {
160 let level = record.level();
161 if level == Level::Info {
162 write!(
163 w,
164 "[{}] {} {}",
165 style(level).paint(now.format(TS_DASHES_BLANK_COLONS_DOT_BLANK)),
166 style(level).paint(level.to_string()),
167 style(level).paint(&record.args().to_string())
168 )
169 } else {
170 write!(
171 w,
172 "[{}] {} [{}:{}] {}",
173 style(level).paint(now.format(TS_DASHES_BLANK_COLONS_DOT_BLANK)),
174 style(level).paint(level.to_string()),
175 get_file_name(record).unwrap_or("<unnamed>"),
176 record.line().unwrap_or(0),
177 style(level).paint(&record.args().to_string())
178 )
179 }
180}
181
182pub fn setup_logging(
190 log_file_path: Option<PathBuf>,
191 level: LevelFilter,
192 rotation_size: u64,
193) -> Result<()> {
194 if let Some(ref path) = log_file_path {
195 let mut spec = FileSpec::default().suppress_timestamp();
197
198 let basename = path
202 .file_stem()
203 .ok_or_else(|| {
204 eprintln!("invalid file name input {:?}", path);
205 einval!()
206 })?
207 .to_str()
208 .ok_or_else(|| {
209 eprintln!("invalid file name input {:?}", path);
210 einval!()
211 })?;
212 spec = spec.basename(basename);
213
214 if let Some(suffix) = path.extension() {
216 let suffix = suffix.to_str().ok_or_else(|| {
217 eprintln!("invalid file extension {:?}", suffix);
218 einval!()
219 })?;
220 spec = spec.suffix(suffix);
221 }
222
223 let parent_dir = path.parent();
225 if let Some(p) = parent_dir {
226 let cwd = current_dir()?;
227 let dir = if !p.has_root() {
228 cwd.join(p)
229 } else {
230 p.to_path_buf()
231 };
232 spec = spec.directory(dir);
233 }
234
235 let mut logger = Logger::try_with_env_or_str("trace")
239 .map_err(|_e| enosys!())?
240 .log_to_file(spec)
241 .append()
242 .format(opt_format);
243
244 if rotation_size > 0 {
246 let log_rotation_size_byte: u64 = rotation_size * 1024 * 1024;
247 logger = logger.rotate(
248 Criterion::Size(log_rotation_size_byte),
249 Naming::Timestamps,
250 Cleanup::KeepCompressedFiles(10),
251 );
252 }
253
254 logger.start().map_err(|e| {
255 eprintln!("{:?}", e);
256 eother!(e)
257 })?;
258 } else {
259 Logger::try_with_env_or_str("trace")
263 .map_err(|_e| enosys!())?
264 .format(colored_opt_format)
265 .start()
266 .map_err(|e| eother!(e))?;
267 }
268
269 log::set_max_level(level);
270
271 log_panics::Config::new()
273 .backtrace_mode(log_panics::BacktraceMode::Resolved)
274 .install_panic_hook();
275
276 Ok(())
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn test_log_level_to_verbosity() {
285 assert_eq!(log_level_to_verbosity(log::LevelFilter::Off), 0);
286 assert_eq!(log_level_to_verbosity(log::LevelFilter::Error), 0);
287 assert_eq!(log_level_to_verbosity(log::LevelFilter::Warn), 1);
288 }
289
290 #[test]
291 fn test_log_rotation() {
292 let log_file = Some(PathBuf::from("test_log_rotation"));
293 let level = LevelFilter::Info;
294 let rotation_size = 1; assert!(setup_logging(log_file, level, rotation_size).is_ok());
297 }
298}