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}