feat(sprout): implement custom logger which shortens output

This commit is contained in:
2025-11-03 14:45:48 -05:00
parent 79615f7436
commit 37abe49347
4 changed files with 117 additions and 11 deletions

View File

@@ -20,7 +20,7 @@ log.workspace = true
[dependencies.uefi]
workspace = true
features = ["alloc", "global_allocator", "logger", "panic_handler"]
features = ["alloc", "global_allocator", "panic_handler"]
[dependencies.uefi-raw]
workspace = true

View File

@@ -0,0 +1,94 @@
//! Based on: https://github.com/rust-osdev/uefi-rs/blob/main/uefi/src/helpers/logger.rs
use alloc::format;
use core::fmt::Write;
use core::ptr;
use core::sync::atomic::{AtomicPtr, Ordering};
use log::{Log, Record};
use uefi::proto::console::text::Output;
/// The global logger object.
static LOGGER: Logger = Logger::new();
/// Logging mechanism for Sprout.
/// Must be initialized to be used, as we use atomic pointers to store the output to write to.
pub struct Logger {
writer: AtomicPtr<Output>,
}
impl Default for Logger {
/// Creates a default logger, which is uninitialized with an output.
fn default() -> Self {
Self::new()
}
}
impl Logger {
/// Create a new logger with an output not specified.
/// This will cause the logger to not print anything until it is configured.
pub const fn new() -> Self {
Self {
writer: AtomicPtr::new(ptr::null_mut()),
}
}
/// Retrieves the pointer to the output.
/// SAFETY: This pointer might be null, it should be checked before use.
#[must_use]
fn output(&self) -> *mut Output {
self.writer.load(Ordering::Acquire)
}
/// Sets the output to write to.
///
/// # Safety
/// This function is unsafe because the output is technically leaked and unmanaged.
pub unsafe fn set_output(&self, output: *mut Output) {
self.writer.store(output, Ordering::Release);
}
}
impl Log for Logger {
/// Enable the logger always.
fn enabled(&self, _metadata: &log::Metadata<'_>) -> bool {
true
}
/// Log the specified `record` to the output if one is set.
fn log(&self, record: &Record) {
// Acquire the output. If one is not set, we do nothing.
let Some(output) = (unsafe { self.output().as_mut() }) else {
return;
};
// Format the log message.
let message = format!("{}", record.args());
// Iterate over every line, formatting the message and writing it to the output.
for line in message.lines() {
// The format writes the log level in front of every line of text.
let _ = writeln!(output, "[{:>5}] {}", record.level(), line);
}
}
/// This log is not buffered, so flushing isn't required.
fn flush(&self) {}
}
/// Initialize the logging environment, calling panic if something goes wrong.
pub fn init() {
// Retrieve the stdout handle and set it as the output for the global logger.
uefi::system::with_stdout(|stdout| unsafe {
// SAFETY: We are using the stdout handle to create a pointer to the output.
// The handle is global and is guaranteed to be valid for the lifetime of the program.
LOGGER.set_output(stdout);
});
// Set the logger to the global logger.
if let Err(error) = log::set_logger(&LOGGER) {
panic!("unable to set logger: {}", error);
}
// Set the max level to the level specified by the log features.
log::set_max_level(log::STATIC_MAX_LEVEL);
}

View File

@@ -51,18 +51,24 @@ pub mod extractors;
/// generators: Runtime code that can generate entries with specific values.
pub mod generators;
/// platform: Integration or support code for specific hardware platforms.
pub mod platform;
/// integrations: Code that interacts with other systems.
pub mod integrations;
/// logger: Code for the logging mechanism of Sprout.
pub mod logger;
/// menu: Display a boot menu to select an entry to boot.
pub mod menu;
/// integrations: Code that interacts with other systems.
pub mod integrations;
/// options: Parse the options of the Sprout executable.
pub mod options;
/// phases: Hooks into specific parts of the boot process.
pub mod phases;
/// platform: Integration or support code for specific hardware platforms.
pub mod platform;
/// sbat: Secure Boot Attestation section.
pub mod sbat;
@@ -72,9 +78,6 @@ pub mod secure;
/// setup: Code that initializes the UEFI environment for Sprout.
pub mod setup;
/// options: Parse the options of the Sprout executable.
pub mod options;
/// utils: Utility functions that are used by other parts of Sprout.
pub mod utils;
@@ -385,6 +388,9 @@ fn run() -> Result<()> {
fn efi_main() -> Status {
// Initialize the basic UEFI environment.
// If initialization fails, we will return ABORTED.
// NOTE: This function will also initialize the logger.
// The logger will panic if it is unable to initialize.
// It is guaranteed that if this returns, the logger is initialized.
if let Err(error) = setup::init() {
error!("unable to initialize environment: {}", error);
return Status::ABORTED;
@@ -394,7 +400,7 @@ fn efi_main() -> Status {
let result = run();
if let Err(ref error) = result {
// Print an error trace.
error!("sprout encountered an error");
error!("sprout encountered an error:");
for (index, stack) in error.chain().enumerate() {
error!("[{}]: {}", index, stack);
}

View File

@@ -1,8 +1,14 @@
use crate::logger;
use anyhow::{Context, Result};
/// Initializes the UEFI environment.
pub fn init() -> Result<()> {
// Initialize the uefi internals.
uefi::helpers::init().context("unable to initialize uefi")?;
// Initialize the logger for Sprout.
// NOTE: This cannot use a result type as errors need to be printed
// using the logger, which is not initialized until this returns.
logger::init();
// Initialize further UEFI internals.
uefi::helpers::init().context("unable to initialize uefi environment")?;
Ok(())
}