[rCore学习笔记 012]彩色化LOG

实验要求

  • 实现分支:ch1
  • 完成实验指导书中的内容并在裸机上实现 hello world 输出。
  • 实现彩色输出宏(只要求可以彩色输出,不要求 log 等级控制,不要求多种颜色)
  • 隐形要求
    可以关闭内核所有输出。从 lab2 开始要求关闭内核所有输出(如果实现了 log 等级控制,那么这一点自然就实现了)。
  • 利用彩色输出宏输出 os 内存空间布局
    输出 .text.data.rodata.bss 各段位置,输出等级为 INFO

ANSI 转义字符

ANSI转义序列

echo -e "\x1b[31mhello world\x1b[0m"

参考资源

官方给出如下资源:对于 Rust, 可以使用 crate log ,推荐参考 rCore

crate log 的应用

打开提供的参考链接,可以了解到应用log这个特质的方式,看Use这一节,
The basic use of the log crate is through the five logging macros: error!warn!info!debug! and trace! where error! represents the highest-priority log messages and trace! the lowest. The log messages are filtered by configuring the log level to exclude messages with a lower priority. Each of these macros accept format strings similarly to println!.

In libraries里可以看到,我们如果要使用log,则需要使用crate log,
Libraries should link only to the log crate, and use the provided macros to log whatever information will be useful to downstream consumers.

例子

参考Examples,创建一个新的工程来测试它,在/homework/homework-1-log下用cargo new test_log创建一个测试工程,在test_log/src下,万万没想到,这个例子竟然成为了最猛的一集,完美展示了不懂一门语言硬学导致的问题.

RustCrate log的例子中为:

use log::{info, warn};

pub fn shave_the_yak(yak: &mut Yak) {
    info!(target: "yak_events", "Commencing yak shaving for {:?}", yak);

    loop {
        match find_a_razor() {
            Ok(razor) => {
                info!("Razor located: {}", razor);
                yak.shave(razor);
                break;
            }
            Err(err) => {
                warn!("Unable to locate a razor: {}, retrying", err);
            }
        }
    }
}

但是这一段是伪代码,运行的时候是会报错的:

   Compiling test_log v0.1.0 (/home/winddevil/workspace/homework/homework-1-log/test_log)
error[E0432]: unresolved import `log`
 --> src/main.rs:2:5
  |
2 | use log::{info, warn};
  |     ^^^ use of undeclared crate or module `log`

error[E0412]: cannot find type `Yak` in this scope
 --> src/main.rs:3:32
  |
3 | pub fn shave_the_yak(yak: &mut Yak) 
  |                                ^^^ not found in this scope

error[E0425]: cannot find function `find_a_razor` in this scope
 --> src/main.rs:9:15
  |
9 |         match find_a_razor() 
  |               ^^^^^^^^^^^^ not found in this scope

Some errors have detailed explanations: E0412, E0425, E0432.
For more information about an error, try `rustc --explain E0412`.
error: could not compile `test_log` (bin "test_log") due to 3 previous errors

这就是我们对于Rust 的一种比丁真还要纯真的认识导致的,这时候我们把这个报错error[E0432]: unresolved import 'log'喂给GPT,人猿相揖别。只几个石头磨过,小儿时节。善于使用工具正是一个懒狗所需要的,

遇到错误 error[E0432]: unresolved import 'log' 表示Rust编译器无法找到你尝试引入的log库。这通常是因为以下几个原因:

  1. 未添加log库为依赖:确保你的Cargo.toml文件中已经添加了log库作为依赖。如果没有,你需要在 [dependencies] 部分添加它。对于最常见的使用场景,添加如下内容:
    [dependencies]
    log = "0.4" # 或者其它适用的版本号
    

然后在项目目录下运行 cargo update 来下载依赖。
2. Cargo特性(gated features):如果你的项目或某个依赖启用了特定的Cargo特性,并且这些特性影响了log的使用,确保正确配置了这些特性。检查Cargo.toml中的[features]部分和相关依赖的特性要求。
3. 路径问题:如果你的项目结构比较特殊,可能需要使用相对路径或绝对路径来正确引入log。但这通常不是导致上述错误的原因,因为log通常作为crate直接引入。
4. 编译目标或环境问题:确保你的编译目标和环境配置没有排除log库的编译或链接。这在交叉编译时可能会成为问题。
5. Cargo.lock的干扰:有时候,旧的Cargo.lock文件可能锁定了不兼容的依赖版本,导致新添加的依赖无法正确解析。尝试删除Cargo.lock文件(如果有),然后重新运行cargo buildcargo run让Cargo重新生成它。
解决方法通常是先检查并修正Cargo.toml,确保log库已经被正确添加为依赖,然后更新依赖并重新编译项目。如果问题依旧存在,检查是否有其他依赖或项目配置干扰了log的正确解析。

What can I say?答案是Cargo没学会,这时候我们修改Cargo.toml,访问log包的文档,可以看到当前的最新版本为0.4.21,在文件中添加这一段,

[dependencies]
log = "0.4.21"

接下来我们解决error[E0412]: cannot find type 'Yak' in this scope的问题,这个问题说明Yak并不是std里原本有的一个东西,当然也倒逼我们一定要看清楚源代码,这次我们直接读和理解这个报错,说的是在当前scope即作用域中找不到Yak,考虑到这个幺蛾子文档的习惯,我们直接在刚才文档的右上角搜索Yak,
![[Pasted image 20240628015106.png]]

这里我们看到shave it意为给它剃毛,考虑原本的代码中的find_a_razor函数即寻找剃刀也报错了error[E0425]: cannot find function find_a_razor in this scope,那么就很好理解了,Yak可能是被制造出来的一个例子库,我们尝试在Cargo.toml中依赖它,

[dependencies]
yak = "0.1.0"

但是很悲剧的是,这个函数并不存在,也就是这段代码纯是一段伪代码,那么我们直接看这段伪代码:

use log::{info, warn};

pub fn shave_the_yak(yak: &mut Yak) {
    info!(target: "yak_events", "Commencing yak shaving for {:?}", yak);

    loop {
        match find_a_razor() {
            Ok(razor) => {
                info!("Razor located: {}", razor);
                yak.shave(razor);
                break;
            }
            Err(err) => {
                warn!("Unable to locate a razor: {}, retrying", err);
            }
        }
    }
}

假设Yak是一个struct,可以它有一个对应的impl,是shave,而还有一个函数find_a_razor是可以返回一个razor类型或者一个err类型的函数.那么我们可以手动补全这段代码:

use std::error::Error;
use std::fmt;
use std::sync::Mutex;

#[derive(Debug)]
pub struct Yak
{
    yak_name: String,
    is_shaved: bool
}

pub struct razor
{
    used_time: u32
}

#[derive(Debug)]
pub struct err
{
    context: String
}

impl Error for err
{
  
}

impl fmt::Display for err
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result
    {
        write!(f, "Error: {}", self.context)
    }
}

impl Yak {
    pub fn new(yak_name: String) -> Self {
        Yak {
            yak_name,
            is_shaved: false,
        }
    }

    pub fn get_yak_name(&self) -> &str {
        &self.yak_name
    }

    pub fn set_yak_name(&mut self, new_name: String) {
        self.yak_name = new_name;
    }

    pub fn is_shaved(&self) -> bool {
        self.is_shaved
    }

    pub fn shave(&mut self, mut razor: razor)
    {
        self.is_shaved = true;
        razor.used_time+=1;
    }
}

impl fmt::Display for razor
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result
    {
        write!(f, "Razor used time: {}", self.used_time)
    }
}

  

pub fn find_a_razor() -> Result<razor,err>
{
    let mut one_razor = razor{used_time:0};
    if one_razor.used_time<100
    {
        one_razor.used_time+=1;
        Ok(one_razor)
    }
    else
    {
        let the_err:err = err{context:"No razor found".to_string()};
        Err(the_err)
    }
}

那么这段代码里就含有几个概念没有搞定,补全过程中极尽折磨:

  1. 枚举类型的泛型
  2. 分支结构
  3. Debug输出

枚举类型

这段代码中,Result<razor,err>是用到了枚举和泛型.

首先Rust的枚举更像是C的枚举的增强版本,C的枚举的几个成员是一个整数,可以按照默认的0-N来用,一般和switch结合增强状态机的可读性.但是很明显这样的成员很难满足Rust的灵活性的需求,尤其是这个场景,又要返回Ok(razor)类型,又要返回Err(err)类型.

那么可以理解Rust的枚举,它不只是为了用一系列代号代表这个类型的所有的可能,而且这个类型可能对应很多种基础类型,也可以被代表.

这样这一段可以理解了,Result<T,E>是一个枚举类型,源代码我们可以在这里找到:

pub enum Result<T, E> {
    Ok(T),
    Err(E),
}

可能还是比较难理解,因为这里掺杂了泛型:

pub enum OBJ
{
	A(u32),
	B(u64)
}

上边这段代码代表OBJ只可能是A或者B,两种情况,但是A实际上包裹---可以理解为Au32类型的一个代号,但是不是直接的u32而是A携带了这样一个变量,B也同理.

考虑到使用了泛型,这就意味着我们可以自定义OkErr各自包裹着什么.

分支结构

这里主要卡住人的是一点,rustif后边的条件是没有括号的.

Debug输出

如果使用了{:?}这种输出,则需要为这个需要输出的类实现一个Debug特性,或者使用#[derive(Debug)].

Cargo的输出等级

编辑main.rs之后发现不能输出:

mod yak;

use log::{info, warn};
use yak::{Yak,find_a_razor};

pub fn shave_the_yak(yak: &mut Yak)
{
    info!(target: "yak_events", "Commencing yak shaving for {:?}", yak);
    loop
    {
        match find_a_razor()
        {
            Ok(razor) =>
            {
                info!("Razor located: {}", razor);
                yak.shave(razor);
                break;
            }
            Err(err) =>
            {
                warn!("Unable to locate a razor: {}, retrying", err);
            }
        }
    }
}

fn main()
{
    env_logger::init();
    let mut yak = Yak::new("Fred".to_string());
    shave_the_yak(&mut yak);
    println!("Hello, world!");
}

这里是存在两个问题:

  1. 错误信息没有输出到工作台
  2. Cargo的输出没有到info这个级别
输出到工作台

编辑Cargo.toml增加一个依赖:

[package]
name = "test_log"
version = "0.1.0"
edition = "2021"

[dependencies]
log = "0.4.21"
env_logger = "0.8"

编辑main.rs,使用env_logger::init();即可.

修改cargodebug等级

使用这个方法即可:

RUST_LOG=info cargo run

os工程适配log

我们首先看题目中给的参考代码:

use core::fmt;

use lazy_static::lazy_static;
use log::{self, Level, LevelFilter, Log, Metadata, Record};

use crate::sync::SpinNoIrqLock as Mutex;

lazy_static! {
    static ref LOG_LOCK: Mutex<()> = Mutex::new(());
}

pub fn init() {
    static LOGGER: SimpleLogger = SimpleLogger;
    log::set_logger(&LOGGER).unwrap();
    log::set_max_level(match option_env!("LOG") {
        Some("error") => LevelFilter::Error,
        Some("warn") => LevelFilter::Warn,
        Some("info") => LevelFilter::Info,
        Some("debug") => LevelFilter::Debug,
        Some("trace") => LevelFilter::Trace,
        _ => LevelFilter::Off,
    });
}

#[macro_export]
macro_rules! print {
    ($($arg:tt)*) => ({
        $crate::logging::print(format_args!($($arg)*));
    });
}

#[macro_export]
macro_rules! println {
    ($fmt:expr) => (print!(concat!($fmt, "\n")));
    ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
}

/// Add escape sequence to print with color in Linux console
macro_rules! with_color {
    ($args: ident, $color_code: ident) => {{
        format_args!("\u{1B}[{}m{}\u{1B}[0m", $color_code as u8, $args)
    }};
}

fn print_in_color(args: fmt::Arguments, color_code: u8) {
    use crate::arch::io;
    let _guard = LOG_LOCK.lock();
    io::putfmt(with_color!(args, color_code));
}

pub fn print(args: fmt::Arguments) {
    use crate::arch::io;
    let _guard = LOG_LOCK.lock();
    io::putfmt(args);
}

struct SimpleLogger;

impl Log for SimpleLogger {
    fn enabled(&self, _metadata: &Metadata) -> bool {
        true
    }
    fn log(&self, record: &Record) {
        if !self.enabled(record.metadata()) {
            return;
        }

        /*
        if let Some(tid) = processor().tid_option() {
            print_in_color(
                format_args!(
                    "[{:>5}][{},{}] {}\n",
                    record.level(),
                    crate::arch::cpu::id(),
                    tid,
                    record.args()
                ),
                level_to_color_code(record.level()),
            );
        } else {
            */
        print_in_color(
            format_args!(
                "[{:>5}][{},-] {}\n",
                record.level(),
                crate::arch::cpu::id(),
                record.args()
            ),
            level_to_color_code(record.level()),
        );
        //}
    }
    fn flush(&self) {}
}

fn level_to_color_code(level: Level) -> u8 {
    match level {
        Level::Error => 31, // Red
        Level::Warn => 93,  // BrightYellow
        Level::Info => 34,  // Blue
        Level::Debug => 32, // Green
        Level::Trace => 90, // BrightBlack
    }
}

我们可以看到参考代码自己实现了一个SimpleLogger类型,并且给它实现了Log这个特性,这是起初很让人难以理解的,但是这让我们想到一个细节,即我们实际上使用了env_logger这个库,用一句看似简单的初始化完成了我们的任务,即把info等宏和硬件输出联系在一起,所以因此我们也需要自己去实现一个带有Log特性的SimpleLogger类,由于我们有了参考源码,实际上的工作反而没有那么困难.

[[012 彩色化LOG#^bf99b4|这里]]实际上又进行了一个简单的Log特质的实现,因为思路先后问题,写到了下一节.

简单的Log特质的实现

^bf99b4

可以在这里看到Log特质实现的时候需要实现的方法为enabled,log,flush.并且可以在这里看到一个小的样例,

use log::{Record, Level, Metadata};
struct SimpleLogger;

impl log::Log for SimpleLogger {
    fn enabled(&self, metadata: &Metadata) -> bool {
        metadata.level() <= Level::Info
    }

    fn log(&self, record: &Record) {
        if self.enabled(record.metadata()) {
            println!("{} - {}", record.level(), record.args());
        }
    }

    fn flush(&self) {}
}

可以根据在在这里看到Log特质实现的时候需要实现的方法,

  1. enabled方法是传入一个Metadata的引用返回一个bool的函数,用于判断当前的这个数据是不是应该被logger给记录下来,样例里简单地写成了只需要<=Level::Info即可.
  2. log方法是传入一个Record,因为Rcord里存在很多信息,然后对它进行判定,然后按照我们需要的方式来进行日志记录.这里就是直接判断等级是不是够了,然后输出其levelargs
  3. flush方法:
    1. 在Rust的日志记录系统中,特别是使用log库和相关的宏如info!, warn!, error!等进行日志记录时,这些宏会调用日志框架来记录消息。通常情况下,当你的应用程序正常退出时,日志框架会自动处理缓冲区中的日志记录,确保所有未提交的日志条目都被写入到日志目的地,比如控制台或文件。
    2. 然而,有时候应用程序可能在异常情况下终止,或者日志框架的默认行为可能不足以满足你的需求。在这种情况下,日志框架通常会提供一个方法,允许你显式地触发日志的刷新,以确保所有日志记录都被正确处理。这个方法通常称为flush
    3. 在这个例子里,因为我们的日志是直接输出到命令行的,因此只需要实现为空即可.

为一个Logger类实现Log特质之后,我们可以直接使用info!等宏来实现输出.
参照参考代码写出来的Logger如下:

use core::fmt;
use log::{self,Level,LevelFilter,Metadata,Log,Record};

struct SimpleLogger;

impl Log for SimpleLogger
{
    fn enabled(&self, metadata: &Metadata) -> bool
    {
        true
    }
    fn log(&self, record: &Record)
    {
        if !self.enabled(record.metadata())
        {
            return;
        }
        print_in_color(
            format_args!(
                "[{:>5}] {}\n",
                record.level(),
                record.args()
            ),
            level_to_color_code(record.level()),
        );
    }
    fn flush(&self) {}
}

fn print_in_color(args: fmt::Arguments, color_code: u8)
{
    println!("\u{1B}[{}m{}\u{1B}[0m", color_code, args);
}

fn level_to_color_code(level: Level) -> u8 {
    match level {
        Level::Error => 31, // Red
        Level::Warn => 93,  // BrightYellow
        Level::Info => 34,  // Blue
        Level::Debug => 32, // Green
        Level::Trace => 90, // BrightBlack
    }
}

pub fn init()
{
    static LOGGER: SimpleLogger = SimpleLogger;
    log::set_logger(&LOGGER).unwrap();
    // log::set_max_level(LevelFilter::Trace);
    log::set_max_level(match option_env!("LOG") {
        Some("error") => LevelFilter::Error,
        Some("warn") => LevelFilter::Warn,
        Some("info") => LevelFilter::Info,
        Some("debug") => LevelFilter::Debug,
        Some("trace") => LevelFilter::Trace,
        _ => LevelFilter::Off,
    });
}

写出来的main.rs如下:

// os/src/main.rs
#![no_std]
#![no_main]
#![feature(panic_info_message)]

use core::{arch::global_asm};
use delay::sleep;
use log::{debug, info, error};


#[macro_use]
mod console;
mod sbi;
mod lang_items;
mod delay;
mod logging;

//use sbi::shutdown;
global_asm!(include_str!("entry.asm"));

#[no_mangle]
pub fn rust_main() -> ! {
    extern "C" {
        fn stext(); // begin addr of text segment
        fn etext(); // end addr of text segment
        fn srodata(); // start addr of Read-Only data segment
        fn erodata(); // end addr of Read-Only data ssegment
        fn sdata(); // start addr of data segment
        fn edata(); // end addr of data segment
    }
    clear_bss();
    logging::init();
    println!("Hello World");
    info!(".text [{:#x}, {:#x})", stext as usize, etext as usize);
    debug!(".rodata [{:#x}, {:#x})", srodata as usize, erodata as usize);
    error!(".data [{:#x}, {:#x})", sdata as usize, edata as usize);
    panic!("Shutdown machine!");
}

fn clear_bss() {
    extern "C" {
        fn sbss();
        fn ebss();
    }
    (sbss as usize..ebss as usize).for_each(|a| {
        unsafe { (a as *mut u8).write_volatile(0) }
    });
}

这里只讲关键点:

  1. \u{1B}[{}m{}\u{1B}[0m里的{}为占位符,第一个{}对应的是输出的颜色,第二个是输出的内容
  2. format_args!这个宏是一个非常神奇的宏,可以实现传入一个字符串,然后把占位符用后边传入的参数实现,就像C里边printf的格式化和python里边f"string{var}".
  3. 在运行cargo时,增加env LOG="info",可以有效传入环境变量参数,注意不要搞错大小写什么的以至于贻笑大方,这里注意不是在运行qemu的时候这么做.
posted @ 2024-07-09 19:55  winddevil  阅读(107)  评论(0编辑  收藏  举报