Skip to content

Commit

Permalink
Generalize the logger to use any backend that implements `fmt::Writ…
Browse files Browse the repository at this point in the history
…e`. (#434)

* Removes the hard dependency on `serial_port` from `logger`, which causes some problematic dependency chains.
  • Loading branch information
kevinaboos committed Aug 19, 2021
1 parent df721e3 commit 358238c
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 41 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions kernel/io/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,7 @@ impl<IO> Seek for Writer<IO> where IO: KnownLength {
/// * [`BlockReader`] and [`BlockWriter`]
/// * [`ByteReader`] and [`ByteWriter`]
/// * [`bare_io::Read`], [`bare_io::Write`], and [`bare_io::Seek`]
/// * [`core::fmt::Write`]
///
/// # Usage and Examples
/// The Rust compiler has difficulty inferring all of the types needed in this struct;
Expand Down Expand Up @@ -770,6 +771,13 @@ impl<'io, IO, L, B> bare_io::Seek for LockableIo<'io, IO, L, B>
{
delegate!{ to self.lock_mut() { fn seek(&mut self, position: bare_io::SeekFrom) -> bare_io::Result<u64>; } }
}
impl<'io, IO, L, B> core::fmt::Write for LockableIo<'io, IO, L, B>
where IO: core::fmt::Write + 'io + ?Sized, L: for <'a> Lockable<'a, IO> + ?Sized, B: Borrow<L>,
{
delegate!{ to self.lock_mut() {
fn write_str(&mut self, s: &str) -> core::fmt::Result;
} }
}


/// Calculates block-wise bounds for an I/O transfer
Expand Down
3 changes: 0 additions & 3 deletions kernel/logger/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ build = "../../build.rs"
spin = "0.9.0"
log = "0.4.8"

[dependencies.serial_port]
path = "../serial_port"

[dependencies.irq_safety]
git = "https://github.com/theseus-os/irq_safety"

Expand Down
83 changes: 48 additions & 35 deletions kernel/logger/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,42 @@
//! A basic logger implementation for system-wide logging in Theseus.
//!
//! This enables Theseus crates to use the `log` crate's macros anywhere.
//! Currently, log statements are written to one or more serial ports.
//! This enables Theseus crates to use the `log` crate's macros anywhere,
//! such as `error!()`, `warn!()`, `info!()`, `debug!()`, and `trace!()`.
//!
//! Currently, log statements are written to one or more **writers`,
//! which are objects that implement the [`core::fmt::Write`] trait.

#![no_std]

extern crate serial_port;
extern crate log;
extern crate spin;
extern crate irq_safety;

use log::{Record, Level, SetLoggerError, Metadata, Log};
use core::fmt::{self, Write};
use spin::Once;
use serial_port::{SerialPort, SerialPortAddress};
use irq_safety::MutexIrqSafe;


/// The static logger instance.
/// This is "static" only because it's required by the `log` crate's design.
static LOGGER: Once<Logger> = Once::new();
/// The singleton system-wide logger instance.
///
/// This is "static" only because it's required by the `log` crate.
static LOGGER: Once<Logger<LOG_MAX_WRITERS>> = Once::new();

/// By default, Theseus will print all log levels, including `Trace` and above.
const DEFAULT_LOG_LEVEL: Level = Level::Trace;
pub const DEFAULT_LOG_LEVEL: Level = Level::Trace;

/// The maximum number of writers: backends to which log streams can be outputted.
pub const LOG_MAX_WRITERS: usize = 2;

/// The signature of a callback function that will optionally be invoked
/// on every log statement to be printed, which enables log mirroring.
/// See [`mirror_to_vga()`].
pub type LogOutputFunc = fn(fmt::Arguments);
static MIRROR_VGA_FUNC: Once<LogOutputFunc> = Once::new();

/// See ANSI terminal formatting schemes
/// ANSI style codes for basic colors.
#[allow(dead_code)]
pub enum LogColor {
enum LogColor {
Black,
Red,
Green,
Expand All @@ -42,7 +49,7 @@ pub enum LogColor {
}

impl LogColor {
pub fn as_terminal_string(&self) -> &'static str {
fn as_terminal_string(&self) -> &'static str {
match *self {
// \x1b is the ESC character (0x1B)
LogColor::Black => "\x1b[30m",
Expand All @@ -66,25 +73,31 @@ pub fn mirror_to_vga(func: LogOutputFunc) {
/// A struct that holds information about logging destinations in Theseus.
///
/// This is the "backend" for the `log` crate that allows Theseus to use its `log!()` macros.
/// Currently, it supports emitting log messages to up to 4 serial ports.
#[derive(Default)]
struct Logger {
serial_ports: [Option<&'static MutexIrqSafe<SerialPort>>; 4],
///
/// We force the use of static references here to enable this crate to be used
/// before dynamic heap allocation has been set up.
struct Logger<const N: usize> {
writers: [Option<&'static MutexIrqSafe<dyn Write + Send>>; N],
}
impl<const N: usize> Default for Logger<{N}> {
fn default() -> Self {
Logger { writers: [None; N] }
}
}

impl Logger {
impl<const N: usize> Logger<{N}> {
/// Re-implementation of the function from `fmt::Write`, but it doesn't require `&mut self`.
fn write_fmt(&self, arguments: fmt::Arguments) -> fmt::Result {
for serial_port in self.serial_ports.iter().flatten() {
let _result = serial_port.lock().write_fmt(arguments);
for writer in self.writers.iter().flatten() {
let _result = writer.lock().write_fmt(arguments);
// If there was an error above, there's literally nothing we can do but ignore it,
// because there is no other lower-level way to log errors than the serial port.
// because there is no other lower-level way to log errors than this logger.
}
Ok(())
}
}

impl Log for Logger {
impl<const N: usize> Log for Logger<{N}> {
#[inline(always)]
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= log::max_level()
Expand Down Expand Up @@ -130,7 +143,7 @@ impl Log for Logger {
}

fn flush(&self) {
// flushing the log is a no-op, since there is no write buffering yet
// flushing the log is a no-op, since there is no write buffering.
}
}

Expand All @@ -139,24 +152,24 @@ impl Log for Logger {
///
/// # Arguments
/// * `log_level`: the log level that should be used.
/// If `None`, the `DEFAULT_LOG_LEVEL` will be used.
/// * `serial_ports`: an iterator over the serial ports that the system logger
/// If `None`, the [`DEFAULT_LOG_LEVEL`] will be used.
/// * `writers`: an iterator over the backends that the system logger
/// will write log messages to.
/// Typically this is just a single port, e.g., `&[COM1]`.
/// Typically this is just a single writer, such as the COM1 serial port.
///
/// This function will initialize up to a maximum of 4 serial ports and use them for logging.
/// Serial ports after the first 4 in the `serial_ports` argument will be ignored.
///
/// This function also initializes and takes ownership of all specified serial ports
/// such that it can atomically write log messages to them.
pub fn init<'p>(
/// This function will initialize the logger with a maximum of [`LOG_MAX_WRITERS`] writers;
/// any additional writers in the given `writers` iterator will be ignored.
///
/// This function accepts only static references to log writers in order to
/// enable loggers to be used before dynamic heap allocation has been set up.
pub fn init<'i, W: Write + Send + 'static>(
log_level: Option<Level>,
serial_ports: impl IntoIterator<Item = &'p SerialPortAddress>
writers: impl IntoIterator<Item = &'i &'static MutexIrqSafe<W>>,
) -> Result<(), SetLoggerError> {
let mut logger = Logger::default();
for (base_port, logger_serial_port) in serial_ports.into_iter().take(4).zip(&mut logger.serial_ports) {
*logger_serial_port = Some(serial_port::get_serial_port(*base_port));
}
for (writer, logger_writer) in writers.into_iter().take(LOG_MAX_WRITERS).zip(&mut logger.writers) {
*logger_writer = Some(*writer);
}

let static_logger = LOGGER.call_once(|| logger);
log::set_logger(static_logger)?;
Expand Down
4 changes: 2 additions & 2 deletions kernel/nano_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ pub extern "C" fn nano_core_start(
println_raw!("Entered nano_core_start(). Interrupts disabled.");

// Initialize the logger up front so we can see early log messages for debugging.
let logger_serial_ports = [serial_port::SerialPortAddress::COM1]; // some servers use COM2 instead.
try_exit!(logger::init(None, &logger_serial_ports).map_err(|_a| "couldn't init logger!"));
let logger_writers = [serial_port::get_serial_port(serial_port::SerialPortAddress::COM1)]; // some servers use COM2 instead.
try_exit!(logger::init(None, &logger_writers).map_err(|_a| "couldn't init logger!"));
info!("Logger initialized.");
println_raw!("nano_core_start(): initialized logger.");

Expand Down

0 comments on commit 358238c

Please sign in to comment.