1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
//! This optional feature adds support for the `log` crate, providing
//! a custom logger implementation which writes to a UEFI text output protocol.
//!
//! The main export of this module is the `Logger` structure,
//! which implements the `log` crate's trait `Log`.
//!
//! # Implementation details
//!
//! The implementation is not the most efficient, since there is no buffering done,
//! and the messages have to be converted from UTF-8 to UEFI's UCS-2.
//!
//! The last part also means that some Unicode characters might not be
//! supported by the UEFI console. Don't expect emoji output support.
use crate::proto::console::text::Output;
use core::fmt::{self, Write};
use core::ptr::NonNull;
/// Logging implementation which writes to a UEFI output stream.
///
/// If this logger is used as a global logger, you must disable it using the
/// `disable` method before exiting UEFI boot services in order to prevent
/// undefined behaviour from inadvertent logging.
pub struct Logger {
writer: Option<NonNull<Output<'static>>>,
}
impl Logger {
/// Creates a new logger.
///
/// You must arrange for the `disable` method to be called or for this logger
/// to be otherwise discarded before boot services are exited.
///
/// # Safety
///
/// Undefined behaviour may occur if this logger is still active after the
/// application has exited the boot services stage.
pub unsafe fn new(output: &mut Output) -> Self {
Logger {
writer: NonNull::new(output as *const _ as *mut _),
}
}
/// Disable the logger
pub fn disable(&mut self) {
self.writer = None;
}
}
impl<'boot> log::Log for Logger {
fn enabled(&self, _metadata: &log::Metadata) -> bool {
self.writer.is_some()
}
fn log(&self, record: &log::Record) {
if let Some(mut ptr) = self.writer {
let writer = unsafe { ptr.as_mut() };
let result = DecoratedLog::write(
writer,
record.level(),
record.args(),
record.file().unwrap_or("<unknown file>"),
record.line().unwrap_or(0),
);
// Some UEFI implementations, such as the one used by VirtualBox,
// may intermittently drop out some text from SimpleTextOutput and
// report an EFI_DEVICE_ERROR. This will be reported here as an
// `fmt::Error`, and given how the `log` crate is designed, our main
// choices when that happens are to ignore the error or panic.
//
// Ignoring errors is bad, especially when they represent loss of
// precious early-boot system diagnosis data, so we panic by
// default. But if you experience this problem and want your UEFI
// application to keep running when it happens, you can enable the
// `ignore-logger-error` cargo feature. If you do so, logging errors
// will be ignored by `uefi-rs` instead.
//
if !cfg!(feature = "ignore-logger-errors") {
result.unwrap()
}
}
}
fn flush(&self) {
// This simple logger does not buffer output.
}
}
// The logger is not thread-safe, but the UEFI boot environment only uses one processor.
unsafe impl Sync for Logger {}
unsafe impl Send for Logger {}
/// Writer wrapper which prints a log level in front of every line of text
///
/// This is less easy than it sounds because...
///
/// 1. The fmt::Arguments is a rather opaque type, the ~only thing you can do
/// with it is to hand it to an fmt::Write implementation.
/// 2. Without using memory allocation, the easy cop-out of writing everything
/// to a String then post-processing is not available.
///
/// Therefore, we need to inject ourselves in the middle of the fmt::Write
/// machinery and intercept the strings that it sends to the Writer.
struct DecoratedLog<'writer, 'a, W: fmt::Write> {
writer: &'writer mut W,
log_level: log::Level,
at_line_start: bool,
file: &'a str,
line: u32,
}
impl<'writer, 'a, W: fmt::Write> DecoratedLog<'writer, 'a, W> {
// Call this method to print a level-annotated log
fn write(
writer: &'writer mut W,
log_level: log::Level,
args: &fmt::Arguments,
file: &'a str,
line: u32,
) -> fmt::Result {
let mut decorated_writer = Self {
writer,
log_level,
at_line_start: true,
file,
line,
};
writeln!(decorated_writer, "{}", *args)
}
}
impl<'writer, 'a, W: fmt::Write> fmt::Write for DecoratedLog<'writer, 'a, W> {
fn write_str(&mut self, s: &str) -> fmt::Result {
// Split the input string into lines
let mut lines = s.lines();
// The beginning of the input string may actually fall in the middle of
// a line of output. We only print the log level if it truly is at the
// beginning of a line of output.
let first = lines.next().unwrap_or("");
if self.at_line_start {
write!(
self.writer,
"[{:>5}]: {:>12}@{:03}: ",
self.log_level, self.file, self.line
)?;
self.at_line_start = false;
}
write!(self.writer, "{}", first)?;
// For the remainder of the line iterator (if any), we know that we are
// truly at the beginning of lines of output.
for line in lines {
write!(self.writer, "\n{}: {}", self.log_level, line)?;
}
// If the string ends with a newline character, we must 1/propagate it
// to the output (it was swallowed by the iteration) and 2/prepare to
// write the log level of the beginning of the next line (if any).
if let Some('\n') = s.chars().next_back() {
writeln!(self.writer)?;
self.at_line_start = true;
}
Ok(())
}
}