relibc/platform/
logger.rs1use 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
43pub struct Output {
47 endpoint: Mutex<Box<dyn fmt::Write + Send + 'static>>,
49
50 flush_on_newline: bool,
53
54 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 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 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 }
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}