Skip to main content

relibc/platform/
logger.rs

1use core::{fmt, str::FromStr};
2
3use crate::{c_str::CStr, io::prelude::*, sync::Mutex};
4
5use alloc::string::{String, ToString};
6use log::{Metadata, Record};
7
8const DEFAULT_LOG_LEVEL: log::LevelFilter = log::LevelFilter::Info;
9
10pub unsafe fn init() {
11    let mut logger = RedoxLogger::new();
12    let log_env = c"RELIBC_LOG_LEVEL".as_ptr();
13    #[cfg(feature = "no_trace")]
14    let mut trace_warn = false;
15    unsafe {
16        if let Some(env) = CStr::from_nullable_ptr(crate::header::stdlib::getenv(log_env))
17            && let Ok(level) = log::LevelFilter::from_str(env.to_str().unwrap_or(""))
18        {
19            #[cfg(feature = "no_trace")]
20            if level == log::LevelFilter::Trace {
21                trace_warn = true;
22            }
23
24            logger = logger.with_output(OutputBuilder::stderr().with_filter(level).build());
25        }
26        if let Some(name) = CStr::from_nullable_ptr(crate::platform::program_invocation_short_name)
27        {
28            logger = logger.with_process_name(name.to_str().unwrap_or("").to_string());
29        }
30    }
31    if logger.enable().is_err() {
32        log::error!("Logger already initialized");
33    }
34
35    #[cfg(feature = "no_trace")]
36    if trace_warn {
37        log::warn!(
38            "The 'no_trace' feature is enabled but RELIBC_LOG_LEVEL=TRACE, there will be no trace logs"
39        );
40    }
41}
42
43/// Copied from redox_log crate with some modifications, in future we might use it instead?
44/// An output that will be logged to. The two major outputs for most Redox system programs are
45/// usually the log file, and the global stdout.
46pub struct Output {
47    // the actual endpoint to write to.
48    endpoint: Mutex<Box<dyn fmt::Write + Send + 'static>>,
49
50    // useful for devices like BufWrite or BufRead. You don't want the log file to never but
51    // written until the program exists.
52    flush_on_newline: bool,
53
54    // specifies the maximum log level possible
55    filter: log::LevelFilter,
56}
57
58impl fmt::Debug for Output {
59    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60        f.debug_struct("Output")
61            .field("endpoint", &"opaque")
62            .field("flush_on_newline", &self.flush_on_newline)
63            .field("filter", &self.filter)
64            .finish()
65    }
66}
67
68impl Default for Output {
69    fn default() -> Self {
70        // Uses default level of max_level_in_use == None  a.k.a LogLevel::Info
71        OutputBuilder::stderr().build()
72    }
73}
74
75pub struct OutputBuilder {
76    endpoint: Box<dyn fmt::Write + Send + 'static>,
77    flush_on_newline: Option<bool>,
78    filter: Option<log::LevelFilter>,
79    ansi: Option<bool>,
80}
81impl OutputBuilder {
82    /*
83    pub fn in_redox_logging_scheme<A, B, C>(
84        category: A,
85        subcategory: B,
86        logfile: C,
87    ) -> Result<Self, io::Error>
88    where
89        A: AsRef<OsStr>,
90        B: AsRef<OsStr>,
91        C: AsRef<OsStr>,
92    {
93        if !cfg!(target_os = "redox") {
94            return Ok(Self::with_endpoint(Vec::new()));
95        }
96
97        let mut path = PathBuf::from("/scheme/logging/");
98        path.push(category.as_ref());
99        path.push(subcategory.as_ref());
100        path.push(logfile.as_ref());
101        path.set_extension("log");
102
103        if let Some(parent) = path.parent() {
104            if !parent.exists() {
105                fs::create_dir_all(parent)?;
106            }
107        }
108
109        Ok(Self::with_endpoint(BufWriter::new(File::create(
110            path,
111            fcntl::O_CREAT | fcntl::O_CLOEXEC,
112            0,
113        )?)))
114    }
115         */
116    pub fn stdout() -> Self {
117        Self::with_endpoint(crate::platform::FileWriter::new(1))
118    }
119    pub fn stderr() -> Self {
120        Self::with_endpoint(crate::platform::FileWriter::new(2))
121    }
122
123    pub fn with_endpoint<T>(endpoint: T) -> Self
124    where
125        T: fmt::Write + Send + 'static,
126    {
127        Self::with_dyn_endpoint(Box::new(endpoint))
128    }
129    pub fn with_dyn_endpoint(endpoint: Box<dyn fmt::Write + Send + 'static>) -> Self {
130        Self {
131            endpoint,
132            flush_on_newline: None,
133            filter: None,
134            ansi: None,
135        }
136    }
137    pub fn flush_on_newline(mut self, flush: bool) -> Self {
138        self.flush_on_newline = Some(flush);
139        self
140    }
141    pub fn with_filter(mut self, filter: log::LevelFilter) -> Self {
142        self.filter = Some(filter);
143        self
144    }
145    pub fn build(self) -> Output {
146        Output {
147            endpoint: Mutex::new(self.endpoint),
148            filter: self.filter.unwrap_or(DEFAULT_LOG_LEVEL),
149            flush_on_newline: self.flush_on_newline.unwrap_or(true),
150        }
151    }
152}
153
154#[derive(Debug, Default)]
155pub struct RedoxLogger {
156    output: Output,
157    min_filter: Option<log::LevelFilter>,
158    max_filter: Option<log::LevelFilter>,
159    max_level_in_use: Option<log::LevelFilter>,
160    min_level_in_use: Option<log::LevelFilter>,
161    process_name: Option<String>,
162}
163
164impl RedoxLogger {
165    pub fn new() -> Self {
166        Self::default()
167    }
168    fn adjust_output_level(
169        max_filter: Option<log::LevelFilter>,
170        min_filter: Option<log::LevelFilter>,
171        max_in_use: &mut Option<log::LevelFilter>,
172        min_in_use: &mut Option<log::LevelFilter>,
173        output: &mut Output,
174    ) {
175        if let Some(max) = max_filter {
176            output.filter = core::cmp::max(output.filter, max);
177        }
178        if let Some(min) = min_filter {
179            output.filter = core::cmp::min(output.filter, min);
180        }
181        match max_in_use {
182            &mut Some(ref mut max) => *max = core::cmp::max(output.filter, *max),
183            max @ &mut None => *max = Some(output.filter),
184        }
185        match min_in_use {
186            &mut Some(ref mut min) => *min = core::cmp::min(output.filter, *min),
187            min @ &mut None => *min = Some(output.filter),
188        }
189    }
190    pub fn with_output(mut self, mut output: Output) -> Self {
191        Self::adjust_output_level(
192            self.max_filter,
193            self.min_filter,
194            &mut self.max_level_in_use,
195            &mut self.min_level_in_use,
196            &mut output,
197        );
198        self.output = output;
199        self
200    }
201    pub fn with_min_level_override(mut self, min: log::LevelFilter) -> Self {
202        self.min_filter = Some(min);
203        let output = &mut self.output;
204        Self::adjust_output_level(
205            self.max_filter,
206            self.min_filter,
207            &mut self.max_level_in_use,
208            &mut self.min_level_in_use,
209            output,
210        );
211        self
212    }
213    pub fn with_max_level_override(mut self, max: log::LevelFilter) -> Self {
214        self.max_filter = Some(max);
215        let output = &mut self.output;
216        Self::adjust_output_level(
217            self.max_filter,
218            self.min_filter,
219            &mut self.max_level_in_use,
220            &mut self.min_level_in_use,
221            output,
222        );
223        self
224    }
225    pub fn with_process_name(mut self, name: String) -> Self {
226        self.process_name = Some(name);
227        self
228    }
229    pub fn enable(self) -> Result<&'static Self, log::SetLoggerError> {
230        let leak = Box::leak(Box::new(self));
231        log::set_logger(leak)?;
232        if let Some(max) = leak.max_level_in_use {
233            log::set_max_level(max);
234        } else {
235            log::set_max_level(DEFAULT_LOG_LEVEL);
236        }
237        Ok(leak)
238    }
239    fn write_record<W: fmt::Write + ?Sized>(
240        record: &Record,
241        process_name: Option<&str>,
242        writer: &mut W,
243    ) -> fmt::Result {
244        let target = record.module_path().unwrap_or(record.target());
245        let level = record.level();
246        let message = record.args();
247
248        let show_lines = true;
249        let line_number = if show_lines { record.line() } else { None };
250
251        let process_name = process_name.unwrap_or("");
252        let line = &LineFmt(line_number);
253        writeln!(writer, "[{process_name}@{target}{line} {level}] {message}",)
254    }
255}
256
257impl log::Log for RedoxLogger {
258    fn enabled(&self, metadata: &Metadata) -> bool {
259        self.max_level_in_use
260            .map(|min| metadata.level() >= min)
261            .unwrap_or(false)
262            && self
263                .min_level_in_use
264                .map(|max| metadata.level() <= max)
265                .unwrap_or(false)
266    }
267    fn log(&self, record: &Record) {
268        let output = &self.output;
269        if record.metadata().level() <= output.filter {
270            let mut endpoint_guard = output.endpoint.lock();
271
272            let _ = Self::write_record(
273                record,
274                self.process_name.as_deref(),
275                endpoint_guard.as_mut(),
276            );
277        }
278    }
279    fn flush(&self) {
280        // no-op
281    }
282}
283
284struct LineFmt(Option<u32>);
285impl fmt::Display for LineFmt {
286    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
287        if let Some(line) = self.0 {
288            write!(f, ":{line}")
289        } else {
290            write!(f, "")
291        }
292    }
293}