diff --git a/src/macros.rs b/src/macros.rs index 074ff3e16f..4885401138 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -7,7 +7,8 @@ macro_rules! print { }}; } -/// Print with new line to stdout +/// Print with new line to stdout. +/// Deprecated, consider using log::info instead #[macro_export] macro_rules! println { () => { @@ -27,7 +28,8 @@ macro_rules! eprint { }}; } -/// Print with new line to stderr +/// Print with new line to stderr. +/// Deprecated, consider using log::info instead #[macro_export] macro_rules! eprintln { () => { @@ -38,35 +40,41 @@ macro_rules! eprintln { }; } -/// Lifted from libstd +pub const ISSUE_URL: &str = "https://gitlab.redox-os.org/redox-os/relibc/-/issues/"; + +// Skippable todo!(issue, fmt) #[macro_export] -macro_rules! dbg { - // NOTE: We cannot use `concat!` to make a static string as a format argument - // of `eprintln!` because `file!` could contain a `{` or - // `$val` expression could be a block (`{ .. }`), in which case the `eprintln!` - // will be malformed. - () => { - eprintln!("[{}:{}:{}]", file!(), line!(), column!()); - }; - ($val:expr) => { - // Use of `match` here is intentional because it affects the lifetimes - // of temporaries - https://stackoverflow.com/a/48732525/1063961 - match $val { - tmp => { - eprintln!( - "[{}:{}:{}] {} = {:#?}", - file!(), - line!(), - column!(), - stringify!($val), - &tmp - ); - tmp - } +macro_rules! todo_skip { + ($issue:expr, $($arg:tt)*) => { + if $issue != 0 { + log::info!("TODO ({}{}): {}", crate::macros::ISSUE_URL, $issue, format_args!($($arg)*)) + } else { + log::info!("TODO: {}", format_args!($($arg)*)) } }; - ($($val:expr),+ $(,)?) => { - ($(dbg!($val)),+,) +} + +// Recoverable error todo!(issue, fmt, err) +#[macro_export] +macro_rules! todo_error { + ($issue:expr, $($arg:tt)*, $err:expr) => { + if $issue != 0 { + log::error!("TODO ({}{}): {}: {:?}", crate::macros::ISSUE_URL, $issue, format_args!($($arg)*), $err) + } else { + log::error!("TODO: {}: {:?}", format_args!($($arg)*), $err) + } + }; +} + +// Unrecoverable error todo!(issue, fmt) +#[macro_export] +macro_rules! todo_panic { + ($issue:expr, $($arg:tt)*) => { + if $issue != 0 { + todo!("{} ({}{})", format_args!($($arg)*), crate::macros::ISSUE_URL, $issue) + } else { + todo!("{}", format_args!($($arg)*)) + } }; } diff --git a/src/platform/logger.rs b/src/platform/logger.rs new file mode 100644 index 0000000000..5f6f689890 --- /dev/null +++ b/src/platform/logger.rs @@ -0,0 +1,297 @@ +use core::{fmt, str::FromStr}; + +use crate::{ + c_str::CStr, + fs::{self, File}, + header::fcntl, + io::{self, BufWriter, prelude::*}, + sync::Mutex, +}; + +use alloc::string::String; +use log::{Metadata, Record}; + +const DEFAULT_LOG_LEVEL: log::LevelFilter = log::LevelFilter::Info; + +pub unsafe fn init() { + let mut logger = RedoxLogger::new(); + let log_env = c"RELIBC_LOG_LEVEL".as_ptr(); + #[cfg(feature = "no_trace")] + let mut trace_warn = false; + unsafe { + if let Some(env) = CStr::from_nullable_ptr(crate::header::stdlib::getenv(log_env)) { + if let Ok(level) = log::LevelFilter::from_str(env.to_str().unwrap_or("")) { + #[cfg(feature = "no_trace")] + if level == log::LevelFilter::Trace { + trace_warn = true; + } + + logger = logger.with_max_level_override(level); + } + } + } + if logger.enable().is_err() { + log::error!("Logger already initialized"); + } + + #[cfg(feature = "no_trace")] + if trace_warn { + log::error!( + "the 'no_trace' feature is enabled but LOG_LEVEL=TRACE, there will be no trace logs" + ); + } +} + +/// Copied from redox_log crate with some modifications, in future we might use it instead? +/// An output that will be logged to. The two major outputs for most Redox system programs are +/// usually the log file, and the global stdout. +pub struct Output { + // the actual endpoint to write to. + endpoint: Mutex>, + + // useful for devices like BufWrite or BufRead. You don't want the log file to never but + // written until the program exists. + flush_on_newline: bool, + + // specifies the maximum log level possible + filter: log::LevelFilter, +} + +impl fmt::Debug for Output { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Output") + .field("endpoint", &"opaque") + .field("flush_on_newline", &self.flush_on_newline) + .field("filter", &self.filter) + .finish() + } +} + +impl Default for Output { + fn default() -> Self { + // Uses default level of max_level_in_use == None a.k.a LogLevel::Info + OutputBuilder::stderr().build() + } +} + +pub struct OutputBuilder { + endpoint: Box, + flush_on_newline: Option, + filter: Option, + ansi: Option, +} +impl OutputBuilder { + /* + pub fn in_redox_logging_scheme( + category: A, + subcategory: B, + logfile: C, + ) -> Result + where + A: AsRef, + B: AsRef, + C: AsRef, + { + if !cfg!(target_os = "redox") { + return Ok(Self::with_endpoint(Vec::new())); + } + + let mut path = PathBuf::from("/scheme/logging/"); + path.push(category.as_ref()); + path.push(subcategory.as_ref()); + path.push(logfile.as_ref()); + path.set_extension("log"); + + if let Some(parent) = path.parent() { + if !parent.exists() { + fs::create_dir_all(parent)?; + } + } + + Ok(Self::with_endpoint(BufWriter::new(File::create( + path, + fcntl::O_CREAT | fcntl::O_CLOEXEC, + 0, + )?))) + } + */ + pub fn stdout() -> Self { + Self::with_endpoint(crate::platform::FileWriter::new(1)) + } + pub fn stderr() -> Self { + Self::with_endpoint(crate::platform::FileWriter::new(2)) + } + + pub fn with_endpoint(endpoint: T) -> Self + where + T: fmt::Write + Send + 'static, + { + Self::with_dyn_endpoint(Box::new(endpoint)) + } + pub fn with_dyn_endpoint(endpoint: Box) -> Self { + Self { + endpoint, + flush_on_newline: None, + filter: None, + ansi: None, + } + } + pub fn flush_on_newline(mut self, flush: bool) -> Self { + self.flush_on_newline = Some(flush); + self + } + pub fn with_filter(mut self, filter: log::LevelFilter) -> Self { + self.filter = Some(filter); + self + } + pub fn build(self) -> Output { + Output { + endpoint: Mutex::new(self.endpoint), + filter: self.filter.unwrap_or(DEFAULT_LOG_LEVEL), + flush_on_newline: self.flush_on_newline.unwrap_or(true), + } + } +} + +#[derive(Debug, Default)] +pub struct RedoxLogger { + output: Output, + min_filter: Option, + max_filter: Option, + max_level_in_use: Option, + min_level_in_use: Option, + process_name: Option, +} + +impl RedoxLogger { + pub fn new() -> Self { + Self::default() + } + fn adjust_output_level( + max_filter: Option, + min_filter: Option, + max_in_use: &mut Option, + min_in_use: &mut Option, + output: &mut Output, + ) { + if let Some(max) = max_filter { + output.filter = core::cmp::max(output.filter, max); + } + if let Some(min) = min_filter { + output.filter = core::cmp::min(output.filter, min); + } + match max_in_use { + &mut Some(ref mut max) => *max = core::cmp::max(output.filter, *max), + max @ &mut None => *max = Some(output.filter), + } + match min_in_use { + &mut Some(ref mut min) => *min = core::cmp::min(output.filter, *min), + min @ &mut None => *min = Some(output.filter), + } + } + pub fn with_output(mut self, mut output: Output) -> Self { + Self::adjust_output_level( + self.max_filter, + self.min_filter, + &mut self.max_level_in_use, + &mut self.min_level_in_use, + &mut output, + ); + self.output = output; + self + } + pub fn with_min_level_override(mut self, min: log::LevelFilter) -> Self { + self.min_filter = Some(min); + let output = &mut self.output; + Self::adjust_output_level( + self.max_filter, + self.min_filter, + &mut self.max_level_in_use, + &mut self.min_level_in_use, + output, + ); + self + } + pub fn with_max_level_override(mut self, max: log::LevelFilter) -> Self { + self.max_filter = Some(max); + let output = &mut self.output; + Self::adjust_output_level( + self.max_filter, + self.min_filter, + &mut self.max_level_in_use, + &mut self.min_level_in_use, + output, + ); + self + } + pub fn with_process_name(mut self, name: String) -> Self { + self.process_name = Some(name); + self + } + pub fn enable(self) -> Result<&'static Self, log::SetLoggerError> { + let leak = Box::leak(Box::new(self)); + log::set_logger(leak)?; + if let Some(max) = leak.max_level_in_use { + log::set_max_level(max); + } else { + log::set_max_level(DEFAULT_LOG_LEVEL); + } + Ok(leak) + } + fn write_record( + record: &Record, + process_name: Option<&str>, + writer: &mut W, + ) -> fmt::Result { + use log::Level; + + let target = record.module_path().unwrap_or(record.target()); + let level = record.level(); + let message = record.args(); + + let show_lines = true; + let line_number = if show_lines { record.line() } else { None }; + + let process_name = process_name.unwrap_or(""); + let line = &LineFmt(line_number); + writeln!(writer, "[{process_name}@{target}{line} {level}] {message}",) + } +} + +impl log::Log for RedoxLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + self.max_level_in_use + .map(|min| metadata.level() >= min) + .unwrap_or(false) + && self + .min_level_in_use + .map(|max| metadata.level() <= max) + .unwrap_or(false) + } + fn log(&self, record: &Record) { + let output = &self.output; + if record.metadata().level() <= output.filter { + let mut endpoint_guard = output.endpoint.lock(); + + let _ = Self::write_record( + record, + self.process_name.as_deref(), + endpoint_guard.as_mut(), + ); + } + } + fn flush(&self) { + // no-op + } +} + +struct LineFmt(Option); +impl fmt::Display for LineFmt { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(line) = self.0 { + write!(f, ":{line}") + } else { + write!(f, "") + } + } +} diff --git a/src/platform/mod.rs b/src/platform/mod.rs index c471e6e12f..4a73962531 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -12,6 +12,8 @@ pub use self::allocator::*; mod allocator; +pub mod logger; + pub use self::pal::{Pal, PalEpoll, PalPtrace, PalSignal, PalSocket}; mod pal; diff --git a/src/start.rs b/src/start.rs index 0e272b3940..b9e536f789 100644 --- a/src/start.rs +++ b/src/start.rs @@ -114,9 +114,11 @@ extern "C" fn init_array() { unsafe { crate::pthread::init(); + crate::platform::logger::init(); init_complete = true } } + fn io_init() { unsafe { // Initialize stdin/stdout/stderr.