captains_log/
console_impl.rs

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