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}