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