captains_log/
console_impl.rs

1use crate::{
2    config::{LogFormat, SinkConfigTrait},
3    env::EnvVarDefault,
4    log_impl::{LogSink, LogSinkTrait},
5    time::Timer,
6};
7use log::{Level, Record};
8use std::hash::{Hash, Hasher};
9use std::path::Path;
10use std::str::FromStr;
11
12/// Log config for output to console
13///
14/// # Example
15///
16/// source of [crate::recipe::console_logger()]
17///
18/// ``` rust
19/// use captains_log::*;
20///
21/// pub fn console_logger(target: ConsoleTarget, max_level: Level) -> Builder {
22///     let console_config = LogConsole::new(target, max_level, recipe::LOG_FORMAT_DEBUG);
23///     let mut config = Builder::default().console(console_config);
24///     // panic on debugging
25///     #[cfg(debug_assertions)]
26///     {
27///         config.continue_when_panic = false;
28///     }
29///     // do not panic on release
30///     #[cfg(not(debug_assertions))]
31///     {
32///         config.continue_when_panic = true;
33///     }
34///     return config;
35/// }
36/// ```
37#[derive(Hash)]
38pub struct LogConsole {
39    pub target: ConsoleTarget,
40
41    /// max log level in this file
42    pub level: Level,
43
44    pub format: LogFormat,
45}
46
47impl LogConsole {
48    pub fn new(target: ConsoleTarget, level: Level, format: LogFormat) -> Self {
49        Self { target, level, format }
50    }
51}
52
53#[derive(Copy, Clone, Debug, Hash, PartialEq)]
54#[repr(u8)]
55pub enum ConsoleTarget {
56    Stdout = 1,
57    Stderr = 2,
58}
59
60impl FromStr for ConsoleTarget {
61    type Err = ();
62
63    /// accepts case-insensitive: stdout, stderr, out, err, 1, 2
64    fn from_str(s: &str) -> Result<Self, ()> {
65        let v = s.to_lowercase();
66        match v.as_str() {
67            "stdout" => Ok(ConsoleTarget::Stdout),
68            "stderr" => Ok(ConsoleTarget::Stderr),
69            "out" => Ok(ConsoleTarget::Stdout),
70            "err" => Ok(ConsoleTarget::Stderr),
71            "1" => Ok(ConsoleTarget::Stdout),
72            "2" => Ok(ConsoleTarget::Stderr),
73            _ => Err(()),
74        }
75    }
76}
77
78// Tried to impl blanket trait T: FromStr, rust reports conflict with
79// - impl<T, U> Into<U> for T where U: From<T>;
80crate::impl_from_env!(ConsoleTarget);
81
82impl SinkConfigTrait for LogConsole {
83    fn get_level(&self) -> Level {
84        self.level
85    }
86
87    fn get_file_path(&self) -> Option<Box<Path>> {
88        None
89    }
90
91    fn write_hash(&self, hasher: &mut Box<dyn Hasher>) {
92        self.hash(hasher);
93        hasher.write(b"LogConsole");
94    }
95
96    fn build(&self) -> LogSink {
97        LogSink::Console(LogSinkConsole::new(self))
98    }
99}
100
101pub(crate) struct LogSinkConsole {
102    target_fd: libc::c_int,
103    max_level: Level,
104    formatter: LogFormat,
105}
106
107impl LogSinkConsole {
108    pub fn new(config: &LogConsole) -> Self {
109        Self {
110            target_fd: config.target as i32,
111            max_level: config.level,
112            formatter: config.format.clone(),
113        }
114    }
115}
116
117impl LogSinkTrait for LogSinkConsole {
118    fn reopen(&self) -> std::io::Result<()> {
119        Ok(())
120    }
121
122    #[inline(always)]
123    fn log(&self, now: &Timer, r: &Record) {
124        if r.level() <= self.max_level {
125            let buf = self.formatter.process(now, r);
126            unsafe {
127                let _ = libc::write(self.target_fd, buf.as_ptr() as *const libc::c_void, buf.len());
128            }
129        }
130    }
131
132    #[inline(always)]
133    fn flush(&self) {}
134}