simple_log/inner.rs
1//! simple-log is a very simple configuration log crates.
2//!
3//! # simple-log format output
4//!
5//! ```bash
6//! 2020-12-07 15:06:03.260570000 [INFO] <json_log:16>:info json simple_log
7//! 2020-12-07 15:06:03.262106000 [WARN] <json_log:17>:warn json simple_log
8//! 2020-12-07 15:06:03.262174000 [ERROR] <json_log:18>:error json simple_log
9//! ```
10//!
11//! # Quick Start
12//!
13//! To get you started quickly, the easiest and quick way to used with demo or test project
14//!
15//! ```no_run
16//! #[macro_use]
17//! extern crate simple_log;
18//!
19//! fn main() {
20//! simple_log::quick!();
21//!
22//! debug!("test quick debug");
23//! info!("test quick info");
24//!}
25//! ```
26//!
27//! # Usage in project
28//!
29//! Configuration [LogConfig] in your project.
30//!
31//! ```no_run
32//!#[macro_use]
33//!extern crate simple_log;
34//!
35//!use simple_log::LogConfigBuilder;
36//!
37//!fn main() -> Result<(), String> {
38//! let config = LogConfigBuilder::builder()
39//! .path("./log/builder_log.log")
40//! .size(1 * 100)
41//! .roll_count(10)
42//! .level("debug")?
43//! .output_file()
44//! .output_console()
45//! .build();
46//!
47//! simple_log::new(config)?;
48//! debug!("test builder debug");
49//! info!("test builder info");
50//! Ok(())
51//!}
52//! ```
53//!
54//! # Config with json
55//!
56//! ```no_run
57//! #[macro_use]
58//! extern crate simple_log;
59//!
60//! use simple_log::LogConfig;
61//!
62//! fn main() {
63//! let config = r#"
64//! {
65//! "path":"./log/tmp.log",
66//! "level":"debug",
67//! "size":10,
68//! "out_kind":"file",
69//! "roll_count":10
70//! }"#;
71//! let log_config: LogConfig = serde_json::from_str(config).unwrap();
72//!
73//! simple_log::new(log_config).unwrap();//init log
74//!
75//! info!("info json simple_log");
76//! warn!("warn json simple_log");
77//! error!("error json simple_log");
78//! }
79//! ```
80//!
81//! For the user guide and futher documentation, please read
82//! [The simple-log document](https://github.com/baoyachi/simple-log).
83//!
84//! More than examples can see:
85//! [examples](https://github.com/baoyachi/simple-log/tree/main/examples).
86//!
87
88use crate::level::{parse_level, LevelInto};
89use crate::out_kind::OutKind;
90use crate::{InnerLevel, SimpleResult};
91use log::LevelFilter;
92use log4rs::append::console::ConsoleAppender;
93use log4rs::append::rolling_file::policy::compound::roll::fixed_window::FixedWindowRoller;
94use log4rs::append::rolling_file::policy::compound::trigger::size::SizeTrigger;
95use log4rs::append::rolling_file::policy::compound::CompoundPolicy;
96use log4rs::append::rolling_file::RollingFileAppender;
97use log4rs::config::runtime::LoggerBuilder;
98use log4rs::config::{Appender, Config, Logger, Root};
99use log4rs::encode::pattern::PatternEncoder;
100use once_cell::sync::OnceCell;
101use serde::{Deserialize, Serialize};
102use std::borrow::Cow;
103use std::ffi::OsStr;
104use std::path::{Path, PathBuf};
105use std::sync::Mutex;
106
107const SIMPLE_LOG_FILE: &str = "simple_log_file";
108const SIMPLE_LOG_CONSOLE: &str = "simple_log_console";
109const SIMPLE_LOG_BASE_NAME: &str = "simple_log";
110
111const DEFAULT_DATE_TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S.%f";
112
113/// simple-log global config.
114struct LogConf {
115 log_config: LogConfig,
116 handle: log4rs::Handle,
117}
118
119static LOG_CONF: OnceCell<Mutex<LogConf>> = OnceCell::new();
120
121fn init_log_conf(mut log_config: LogConfig) -> SimpleResult<()> {
122 let config = build_config(&mut log_config)?;
123 let handle = log4rs::init_config(config).map_err(|e| e.to_string())?;
124 LOG_CONF.get_or_init(|| Mutex::new(LogConf { log_config, handle }));
125 Ok(())
126}
127
128/// Update simple-log global config [LogConfig].
129///
130/// ```rust
131/// #[macro_use]
132/// extern crate simple_log;
133///
134/// use simple_log::LogConfigBuilder;
135///
136/// fn main() -> Result<(), String> {
137/// let old_config = LogConfigBuilder::builder()
138/// .path("./log/builder_log.log")
139/// .size(1 * 100)
140/// .roll_count(10)
141/// .level("debug")?
142/// .output_file()
143/// .output_console()
144/// .build();
145///
146/// simple_log::new(old_config.clone())?;
147/// let out = simple_log::get_log_conf()?;
148/// assert_eq!(out, old_config);
149///
150/// debug!("test update_log_conf debug");
151/// info!("test update_log_conf info");
152///
153/// let new_config = LogConfigBuilder::builder()
154/// .path("./log/builder_log.log")
155/// .size(2)
156/// .roll_count(2)
157/// .level("info")?
158/// .output_file()
159/// .output_console()
160/// .build();
161/// simple_log::update_log_conf(new_config.clone())?;
162/// let out = simple_log::get_log_conf()?;
163/// assert_eq!(out, new_config);
164///
165/// debug!("test update_log_conf debug");//ignore
166/// info!("test update_log_conf info");//print
167/// Ok(())
168/// }
169///```
170pub fn update_log_conf(mut log_config: LogConfig) -> SimpleResult<LogConfig> {
171 let log_conf = LOG_CONF.get().unwrap();
172 let mut guard = log_conf.lock().unwrap();
173 let config = build_config(&mut log_config)?;
174 guard.log_config = log_config;
175 guard.handle.set_config(config);
176 Ok(guard.log_config.clone())
177}
178
179/// update simple-log global config log level.
180///
181/// # Examples
182///
183/// ```rust
184/// fn main() -> Result<(), String> {
185/// use simple_log::{LogConfigBuilder, update_log_level};
186/// let config = LogConfigBuilder::builder()
187/// .path("./log/builder_log.log")
188/// .size(1 * 64)
189/// .roll_count(10)
190/// .level("debug")?
191/// .output_file()
192/// .output_console()
193/// .build();
194/// simple_log::new(config)?;
195///
196/// //update log level
197/// let config = update_log_level(log::Level::Debug)?;
198/// assert_eq!("DEBUG",config.get_level());
199/// Ok(())
200/// }
201/// ```
202///
203pub fn update_log_level<S: LevelInto>(level: S) -> SimpleResult<LogConfig> {
204 let log_conf = LOG_CONF.get().unwrap();
205 let mut guard = log_conf.lock().unwrap();
206 guard.log_config.set_level(level)?;
207 let config = build_config(&mut guard.log_config)?;
208 guard.handle.set_config(config);
209 Ok(guard.log_config.clone())
210}
211
212/// Get simple-log global config [LogConfig]
213///
214/// ```rust
215/// #[macro_use]
216/// extern crate simple_log;
217///
218/// use simple_log::LogConfigBuilder;
219///
220/// fn main() -> Result<(), String> {
221/// let old_config = LogConfigBuilder::builder()
222/// .path("./log/builder_log.log")
223/// .size(1 * 100)
224/// .roll_count(10)
225/// .level("debug")?
226/// .output_file()
227/// .output_console()
228/// .build();
229///
230/// simple_log::new(old_config.clone())?;
231/// let out = simple_log::get_log_conf()?;
232/// assert_eq!(out, old_config);
233///
234/// debug!("test get_log_conf debug");
235/// info!("test get_log_conf info");
236/// Ok(())
237/// }
238/// ```
239pub fn get_log_conf() -> SimpleResult<LogConfig> {
240 let log_conf = LOG_CONF.get().unwrap();
241 let config = log_conf.lock().unwrap().log_config.clone();
242 Ok(config)
243}
244
245use crate::level::deserialize_level;
246use crate::out_kind::deserialize_out_kind;
247
248#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
249#[serde(rename_all = "snake_case")]
250pub struct LogConfig {
251 #[serde(default)]
252 pub path: Option<String>,
253 #[serde(default)]
254 pub directory: Option<String>,
255 #[serde(deserialize_with = "deserialize_level")]
256 pub level: InnerLevel,
257 #[serde(default)]
258 pub size: u64,
259 #[serde(deserialize_with = "deserialize_out_kind", default)]
260 pub out_kind: Vec<OutKind>,
261 #[serde(default)]
262 pub roll_count: u32,
263 #[serde(default)]
264 pub time_format: Option<String>,
265}
266
267impl Default for LogConfig {
268 fn default() -> Self {
269 LogConfig {
270 path: None,
271 directory: None,
272 level: (LevelFilter::Debug, vec![]),
273 size: 0,
274 out_kind: vec![],
275 roll_count: 0,
276 time_format: None,
277 }
278 }
279}
280
281impl LogConfig {
282 fn default_basename(&self) -> String {
283 let arg0 = std::env::args()
284 .next()
285 .unwrap_or_else(|| SIMPLE_LOG_BASE_NAME.to_owned());
286 let path = Path::new(&arg0)
287 .file_stem()
288 .map(OsStr::to_string_lossy)
289 .unwrap_or(Cow::Borrowed(SIMPLE_LOG_BASE_NAME))
290 .to_string();
291 format!("{path}.log")
292 }
293 pub fn get_path(&self) -> Option<&String> {
294 self.path.as_ref()
295 }
296
297 pub fn get_directory(&self) -> Option<&String> {
298 self.directory.as_ref()
299 }
300
301 pub fn get_level(&self) -> &str {
302 self.level.0.as_str()
303 }
304
305 pub fn get_size(&self) -> u64 {
306 self.size
307 }
308
309 pub fn get_out_kind(&self) -> &Vec<OutKind> {
310 &self.out_kind
311 }
312
313 pub fn get_roll_count(&self) -> u32 {
314 self.roll_count
315 }
316
317 pub fn get_time_format(&self) -> Option<&String> {
318 self.time_format.as_ref()
319 }
320
321 pub(crate) fn set_level<T: LevelInto>(&mut self, level: T) -> SimpleResult<()> {
322 let level = level.into_level();
323 let level = parse_level(level)?;
324 self.level = level;
325 Ok(())
326 }
327}
328
329/// The [LogConfig] with builder wrapper.
330pub struct LogConfigBuilder(LogConfig);
331
332impl LogConfigBuilder {
333 /// Construct a [LogConfig] by [`LogConfigBuilder::builder`]
334 ///
335 /// # Examples
336 ///
337 /// ```rust
338 /// fn run() {
339 /// use simple_log::{LogConfigBuilder, LogConfig};
340 ///
341 /// let builder:LogConfigBuilder = LogConfigBuilder::builder();
342 /// let log_config:LogConfig = builder.build();
343 /// println!("{:?}",log_config);
344 /// }
345 /// ```
346 ///
347 pub fn builder() -> Self {
348 LogConfigBuilder(LogConfig::default())
349 }
350
351 /// Receive file write path.
352 ///
353 /// simple-log output path when `OutKind` value is `File`.
354 /// When `OutKind` value only is `console`,need ignore this method.
355 ///
356 /// # Examples
357 ///
358 /// ```rust
359 /// fn run() {
360 /// use simple_log::LogConfigBuilder;
361 /// use simple_log::LogConfig;
362 ///
363 /// let builder:LogConfigBuilder = LogConfigBuilder::builder().path("/tmp/log/simple_log.log");
364 /// let config:LogConfig = builder.build();
365 /// println!("{:?}",config);
366 /// }
367 /// ```
368 ///
369 pub fn path<S: Into<String>>(mut self, path: S) -> LogConfigBuilder {
370 self.0.path = Some(path.into());
371 self
372 }
373
374 pub fn directory<S: Into<String>>(mut self, directory: S) -> LogConfigBuilder {
375 self.0.directory = Some(directory.into());
376 self
377 }
378
379 pub fn level<S: LevelInto>(mut self, level: S) -> SimpleResult<LogConfigBuilder> {
380 self.0.set_level(level)?;
381 Ok(self)
382 }
383
384 pub fn size(mut self, size: u64) -> LogConfigBuilder {
385 self.0.size = size;
386 self
387 }
388
389 pub fn output_file(mut self) -> LogConfigBuilder {
390 self.0.out_kind.push(OutKind::File);
391 self
392 }
393
394 /// Configuration [LogConfigBuilder] with log output with console.
395 ///
396 /// If your application build with `--release`.This method should not be used
397 /// `output_file` method is recommended.
398 /// This is usually used with `debug` or `test` mode.
399 pub fn output_console(mut self) -> LogConfigBuilder {
400 self.0.out_kind.push(OutKind::Console);
401 self
402 }
403
404 pub fn roll_count(mut self, roll_count: u32) -> LogConfigBuilder {
405 self.0.roll_count = roll_count;
406 self
407 }
408
409 /// It's optional method.
410 /// Also support default data_time_format:%Y-%m-%d %H:%M:%S.%f
411 ///
412 /// Support data_time_format with link:`<https://docs.rs/chrono/0.4.19/chrono/naive/struct.NaiveDateTime.html#method.parse_from_str>`
413 pub fn time_format<S: Into<String>>(mut self, time_format: S) -> LogConfigBuilder {
414 self.0.time_format = Some(time_format.into());
415 self
416 }
417
418 /// Constructs a new `LogConfig` .
419 ///
420 /// # Examples
421 ///
422 /// ```rust
423 /// fn run() {
424 /// use simple_log::LogConfigBuilder;
425 /// let builder:LogConfigBuilder = LogConfigBuilder::builder();
426 /// let config = LogConfigBuilder::builder()
427 /// .path("./log/builder_log.log")
428 /// .size(1 * 100)
429 /// .roll_count(10)
430 /// .level("debug").unwrap()
431 /// .time_format("%Y-%m-%d %H:%M:%S.%f")
432 /// .output_file()
433 /// .output_console()
434 /// .build();
435 /// println!("{:?}",config);
436 /// }
437 /// ```
438 pub fn build(self) -> LogConfig {
439 self.0
440 }
441}
442
443/// The [new] method provide init simple-log instance with config.
444///
445/// This method need pass [LogConfig] param. Your can use [LogConfigBuilder] `build` [LogConfig].
446/// Also you can use [serde] with `Deserialize` init `LogConfig`.
447///
448/// # Examples
449///
450/// ```no_run
451/// #[macro_use]
452/// extern crate simple_log;
453///
454/// use simple_log::LogConfigBuilder;
455///
456/// fn main() -> Result<(), String> {
457/// let config = LogConfigBuilder::builder()
458/// .path("./log/builder_log.log")
459/// .size(1 * 100)
460/// .roll_count(10)
461/// .level("info")?
462/// .output_file()
463/// .output_console()
464/// .build();
465/// simple_log::new(config)?;
466/// debug!("test builder debug");
467/// info!("test builder info");
468/// Ok(())
469/// }
470/// ```
471///
472pub fn new(log_config: LogConfig) -> SimpleResult<()> {
473 let mut log_config = log_config;
474 init_default_log(&mut log_config);
475 init_log_conf(log_config)?;
476 Ok(())
477}
478
479/// This method can quick init simple-log with no configuration.
480///
481/// If your just want use in demo or test project. Your can use this method.
482/// The [quick()] method not add any params in method. It's so easy.
483///
484/// The [`LogConfig`] filed just used inner default value.
485///
486/// ```bash
487/// path: ./tmp/simple_log.log //output file path
488/// level: debug //log level
489/// size: 10 //single log file size with unit:MB. 10MB eq:10*1024*1024
490/// out_kind:[file,console] //Output to file and terminal at the same time
491/// roll_count:10 //At the same time, it can save 10 files endwith .gz
492///```
493///
494/// If you don't want use [quick!] method.Also can use [new] method.
495///
496/// # Examples
497///
498/// ```rust
499/// #[macro_use]
500/// extern crate simple_log;
501///
502/// fn main() {
503/// simple_log::quick!("info");
504///
505/// debug!("test builder debug");
506/// info!("test builder info");
507/// }
508/// ```
509pub fn quick() -> SimpleResult<()> {
510 quick_log_level::<_, &str>("debug", None)
511}
512
513pub fn quick_log_level<S: LevelInto, P: Into<String>>(
514 level: S,
515 path: Option<P>,
516) -> SimpleResult<()> {
517 let level = level.into_level();
518 let level = parse_level(level)?;
519 let mut config = LogConfig {
520 path: path.map(|v| v.into()),
521 directory: None,
522 level,
523 size: 0,
524 out_kind: vec![],
525 roll_count: 0,
526 time_format: None,
527 };
528 init_default_log(&mut config);
529 init_log_conf(config)?;
530 Ok(())
531}
532
533/// Provide init simple-log instance with stdout console on terminal.
534///
535/// Method receive log level one of [log_level] mod.
536///
537/// ```rust
538/// #[macro_use]
539/// extern crate simple_log;
540///
541/// fn main() -> Result<(), String> {
542/// simple_log::console("debug")?;
543///
544/// debug!("test console debug");
545/// info!("test console info");
546/// Ok(())
547/// }
548/// ```
549pub fn console<S: LevelInto>(level: S) -> SimpleResult<()> {
550 let level = level.into_level();
551 let level = parse_level(level)?;
552 let config = LogConfig {
553 path: None,
554 directory: None,
555 level,
556 size: 0,
557 out_kind: vec![OutKind::Console],
558 roll_count: 0,
559 time_format: Some(DEFAULT_DATE_TIME_FORMAT.to_string()),
560 };
561 init_log_conf(config)?;
562 Ok(())
563}
564
565/// Provide init simple-log instance with write file.
566///
567/// The param `path` is either an absolute path or lacking a leading `/`, relative to the `cwd` of your [LogConfig].
568///
569/// The param `level` config log level with [log_level].
570/// The param `size` config single file size(MB).
571/// The param `roll_count` config single file size(MB).
572///
573/// The file extension of the pattern is `.gz`,the archive files will be gzip-compressed.
574///
575/// ```rust
576/// #[macro_use]
577/// extern crate simple_log;
578///
579/// fn main() -> Result<(), String> {
580/// simple_log::file("./log/file.log", "debug", 100, 10)?;
581///
582/// debug!("test file debug");
583/// info!("test file info");
584/// Ok(())
585/// }
586/// ```
587pub fn file<P: Into<String>, S: LevelInto>(
588 path: P,
589 level: S,
590 size: u64,
591 roll_count: u32,
592) -> SimpleResult<()> {
593 let level = level.into_level();
594 let level = parse_level(level)?;
595 let config = LogConfig {
596 path: Some(path.into()),
597 directory: None,
598 level,
599 size,
600 out_kind: vec![OutKind::File],
601 roll_count,
602 time_format: Some(DEFAULT_DATE_TIME_FORMAT.to_string()),
603 };
604 init_log_conf(config)?;
605 Ok(())
606}
607
608fn build_config(log: &mut LogConfig) -> SimpleResult<Config> {
609 let mut config_builder = Config::builder();
610 let mut root_builder = Root::builder();
611 for kind in &log.out_kind {
612 match kind {
613 OutKind::File => {
614 // Check if the directory is set and path is not set; if so, set the default path.
615 if log.directory.is_some() && log.path.is_none() {
616 log.path = Some(log.default_basename());
617 }
618
619 // If the path is now set (either it was initially or we just set it),
620 // proceed to build the appender and configure it.
621 if log.path.is_some() {
622 config_builder = config_builder
623 .appender(Appender::builder().build(SIMPLE_LOG_FILE, file_appender(log)?));
624 root_builder = root_builder.appender(SIMPLE_LOG_FILE);
625 }
626 }
627 OutKind::Console => {
628 let console = ConsoleAppender::builder()
629 .encoder(Box::new(encoder(log.time_format.as_ref(), true)))
630 .build();
631 config_builder = config_builder
632 .appender(Appender::builder().build(SIMPLE_LOG_CONSOLE, Box::new(console)));
633 root_builder = root_builder.appender(SIMPLE_LOG_CONSOLE);
634 }
635 }
636 }
637
638 for target in &log.level.1 {
639 config_builder = config_builder.logger(LoggerBuilder::build(
640 Logger::builder(),
641 &target.name,
642 target.level,
643 ));
644 }
645
646 let config = config_builder
647 .build(root_builder.build(log.level.0))
648 .map_err(|e| e.to_string())?;
649 Ok(config)
650}
651
652/// check log config,and give default value
653fn init_default_log(log: &mut LogConfig) {
654 if let Some(path) = &log.path {
655 if path.trim().is_empty() {
656 let file_name = log.default_basename();
657 log.path = Some(format!("./tmp/{}", file_name));
658 }
659 }
660
661 if log.size == 0 {
662 log.size = 10 //1MB:1*1024*1024
663 }
664
665 if log.roll_count == 0 {
666 log.roll_count = 10
667 }
668
669 if log.out_kind.is_empty() {
670 log.out_kind
671 .append(&mut vec![OutKind::Console, OutKind::File])
672 }
673}
674
675fn encoder(time_format: Option<&String>, color: bool) -> PatternEncoder {
676 let time_format = if let Some(format) = time_format {
677 format.to_string()
678 } else {
679 DEFAULT_DATE_TIME_FORMAT.to_string()
680 };
681
682 let color_level = match color {
683 true => "{h({l:5})}",
684 false => "{l:5}",
685 };
686 let mut pattern = format!("{{d({})}} [{}] ", time_format, color_level);
687
688 #[cfg(feature = "target")]
689 {
690 pattern += "[{t:7}] <{M}:{L}>:{m}{n}";
691 }
692 #[cfg(not(feature = "target"))]
693 {
694 pattern += "<{M}:{L}>:{m}{n}";
695 }
696
697 PatternEncoder::new(pattern.as_str())
698}
699
700fn file_appender(log: &LogConfig) -> SimpleResult<Box<RollingFileAppender>> {
701 // If the log is written to a file, the path parameter is required
702 let path = log
703 .path
704 .as_ref()
705 .expect("Expected the path to write the log file, but it is empty");
706
707 let mut path = PathBuf::from(path);
708
709 if let Some(directory) = &log.directory {
710 let buf = PathBuf::from(directory);
711 path = buf.join(path);
712 }
713
714 let roll = FixedWindowRoller::builder()
715 .base(0)
716 .build(
717 format!("{}.{{}}.gz", path.display()).as_str(),
718 log.roll_count,
719 )
720 .map_err(|e| e.to_string())?;
721
722 let trigger = SizeTrigger::new(log.size * 1024 * 1024);
723
724 let policy = CompoundPolicy::new(Box::new(trigger), Box::new(roll));
725
726 let logfile = RollingFileAppender::builder()
727 .encoder(Box::new(encoder(log.time_format.as_ref(), false)))
728 .build(path.clone(), Box::new(policy))
729 .map_err(|e| e.to_string())?;
730
731 Ok(Box::new(logfile))
732}