Log 子系统使用指南

概述

本文档提供 Log 子系统的实用指南,包括基本使用、配置方法、格式化技巧、性能最佳实践、多核并发场景、常见陷阱和调试技巧。通过丰富的代码示例,帮助开发者快速掌握日志系统的使用。

基本使用

引入宏

在需要使用日志的模块中引入对应的宏:

#![allow(unused)]
fn main() {
use log::{pr_info, pr_err, pr_warn, pr_debug};
}

或者引入所有宏:

#![allow(unused)]
fn main() {
use log::*;
}

记录简单日志

最基本的用法是记录字符串消息:

#![allow(unused)]
fn main() {
pr_info!("System initialization started");
pr_warn!("Low memory warning");
pr_err!("Failed to initialize device");
}

格式化日志

使用 Rust 的格式化语法,类似于 println!format!

#![allow(unused)]
fn main() {
let pid = 42;
let name = "init";
pr_info!("Process started: pid={}, name={}", pid, name);

let count = 100;
pr_debug!("Allocated {} frames", count);

let addr = 0x80001000usize;
pr_info!("Page table at {:#x}", addr);  // 十六进制格式
}

不同级别的使用示例

#![allow(unused)]
fn main() {
// Emergency: 系统即将崩溃
pr_emerg!("Kernel panic: unable to handle page fault");

// Alert: 需要立即采取行动
pr_alert!("Filesystem corruption detected");

// Critical: 严重错误
pr_crit!("Failed to initialize memory subsystem");

// Error: 普通错误
pr_err!("Cannot open file: {}", filename);

// Warning: 警告
pr_warn!("Memory usage: {}%", usage_percent);

// Notice: 重要信息
pr_notice!("Network interface {} is up", interface);

// Info: 常规信息
pr_info!("Loading module: {}", module_name);

// Debug: 调试信息
pr_debug!("Entering function: allocate_frame()");
}

配置级别过滤器

查询当前级别

#![allow(unused)]
fn main() {
use log::{get_global_level, get_console_level, LogLevel};

let global = get_global_level();
let console = get_console_level();

pr_info!("Current levels: global={:?}, console={:?}", global, console);
}

设置全局级别

控制哪些日志被缓存:

#![allow(unused)]
fn main() {
use log::{set_global_level, LogLevel};

// 缓存所有日志(包括 Debug)
set_global_level(LogLevel::Debug);

// 只缓存 Info 及以上级别(默认)
set_global_level(LogLevel::Info);

// 只缓存警告和错误
set_global_level(LogLevel::Warning);

// 只缓存错误
set_global_level(LogLevel::Error);
}

设置控制台级别

控制哪些日志立即打印:

#![allow(unused)]
fn main() {
use log::{set_console_level, LogLevel};

// 显示所有日志(包括 Debug)
set_console_level(LogLevel::Debug);

// 显示 Info 及以上级别
set_console_level(LogLevel::Info);

// 只显示警告和错误(默认)
set_console_level(LogLevel::Warning);

// 只显示错误
set_console_level(LogLevel::Error);
}

典型配置场景

开发调试配置

#![allow(unused)]
fn main() {
// 缓存所有日志,但只显示 Info 及以上
// 这样 Debug 日志被保留,需要时可以读取缓冲区查看
set_global_level(LogLevel::Debug);
set_console_level(LogLevel::Info);

pr_debug!("This will be buffered but not shown");
pr_info!("This will be buffered and shown");
}

正常运行配置

#![allow(unused)]
fn main() {
// 默认配置:缓存常规信息,只显示警告和错误
set_global_level(LogLevel::Info);
set_console_level(LogLevel::Warning);

pr_info!("Normal operation");      // 缓存,不显示
pr_warn!("Warning condition");     // 缓存,显示
}

生产环境配置

#![allow(unused)]
fn main() {
// 最小化开销:只记录和显示问题
set_global_level(LogLevel::Warning);
set_console_level(LogLevel::Error);

pr_info!("This will be ignored");    // 完全跳过
pr_warn!("Warning");                 // 缓存,不显示
pr_err!("Error");                    // 缓存,显示
}

临时启用详细日志

#![allow(unused)]
fn main() {
// 调试特定问题时,临时启用所有日志
let old_global = get_global_level();
let old_console = get_console_level();

set_global_level(LogLevel::Debug);
set_console_level(LogLevel::Debug);

// ... 执行需要调试的代码 ...

// 恢复原来的配置
set_global_level(old_global);
set_console_level(old_console);
}

读取日志缓冲区

读取单条日志

#![allow(unused)]
fn main() {
use log::read_log;

if let Some(entry) = read_log() {
    // 使用 Display trait 格式化输出
    println!("{}", entry);

    // 或者访问字段
    println!("Level: {:?}", entry.level());
    println!("CPU: {}", entry.cpu_id());
    println!("Timestamp: {}", entry.timestamp());
    println!("Message: {}", entry.message());
}
}

读取所有日志

#![allow(unused)]
fn main() {
use log::read_log;

// 顺序读取所有日志(FIFO 顺序)
while let Some(entry) = read_log() {
    println!("{}", entry);
}
}

检查缓冲区状态

#![allow(unused)]
fn main() {
use log::{log_len, log_dropped_count};

// 检查有多少条日志等待读取
let buffered_count = log_len();
println!("Buffered logs: {}", buffered_count);

// 检查是否有日志被丢弃(缓冲区溢出)
let dropped = log_dropped_count();
if dropped > 0 {
    pr_warn!("Warning: {} logs were dropped due to buffer overflow", dropped);
}
}

定期读取日志(避免溢出)

#![allow(unused)]
fn main() {
use log::{read_log, log_len, log_dropped_count};

// 日志读取任务(可以在内核线程中运行)
fn log_reader_task() {
    loop {
        // 定期检查缓冲区
        let count = log_len();
        if count > 0 {
            println!("=== Reading {} buffered logs ===", count);

            while let Some(entry) = read_log() {
                // 处理日志(打印、写入文件、发送到网络等)
                process_log_entry(entry);
            }
        }

        // 检查溢出
        let dropped = log_dropped_count();
        if dropped > 0 {
            println!("WARNING: {} logs were dropped", dropped);
        }

        // 休眠一段时间
        sleep_ms(1000);
    }
}

fn process_log_entry(entry: LogEntry) {
    // 示例:写入到文件或发送到远程服务器
    // file.write_fmt(format_args!("{}\n", entry)).ok();
    println!("{}", entry);
}
}

非破坏性读取

使用 peek_log() 可以读取日志而不删除它们:

#![allow(unused)]
fn main() {
use log::{peek_log, log_reader_index, log_writer_index};

// 获取可读范围
let start = log_reader_index();
let end = log_writer_index();

println!("Available logs: {}", end - start);

// 遍历所有日志(不删除)
for index in start..end {
    if let Some(entry) = peek_log(index) {
        println!("Log #{}: {}", index, entry);
    }
}

// 可以再次读取相同的日志
for index in start..end {
    if let Some(entry) = peek_log(index) {
        // 处理日志,但它们仍保留在缓冲区中
        if entry.level() <= LogLevel::Error {
            send_alert(&entry);
        }
    }
}
}

查询缓冲区状态

#![allow(unused)]
fn main() {
use log::{log_len, log_unread_bytes, log_dropped_count};

// 查询未读日志数量和字节数
let count = log_len();
let bytes = log_unread_bytes();
let dropped = log_dropped_count();

println!("Buffered logs: {} entries, {} bytes", count, bytes);
println!("Dropped logs: {}", dropped);

// 检查缓冲区使用率
let capacity = 58;  // 约58条
let usage_percent = (count * 100) / capacity;
if usage_percent > 80 {
    pr_warn!("Log buffer is {}% full", usage_percent);
}
}

使用 syslog 系统调用

用户空间程序可以通过 syslog 系统调用读取和控制内核日志。

基本用法

#include <sys/klog.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    char buf[8192];

    // 读取内核日志(破坏性)
    int len = syscall(SYS_syslog, 2, buf, sizeof(buf));
    if (len > 0) {
        write(STDOUT_FILENO, buf, len);
    }

    return 0;
}

非破坏性读取

#include <sys/klog.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <stdlib.h>

#define SYSLOG_ACTION_READ_ALL 3
#define SYSLOG_ACTION_SIZE_UNREAD 9

int main() {
    // 1. 查询需要多少空间
    int size = syscall(SYS_syslog, SYSLOG_ACTION_SIZE_UNREAD, NULL, 0);
    if (size < 0) {
        perror("syslog");
        return 1;
    }

    printf("Unread logs: %d bytes\n", size);

    // 2. 分配足够的缓冲区
    char *buf = malloc(size + 1);
    if (!buf) {
        perror("malloc");
        return 1;
    }

    // 3. 读取所有日志(非破坏性)
    int len = syscall(SYS_syslog, SYSLOG_ACTION_READ_ALL, buf, size);
    if (len < 0) {
        perror("syslog");
        free(buf);
        return 1;
    }

    // 4. 显示日志
    buf[len] = '\0';
    printf("%s", buf);

    free(buf);
    return 0;
}

控制控制台输出

#define SYSLOG_ACTION_CONSOLE_OFF 6
#define SYSLOG_ACTION_CONSOLE_ON 7
#define SYSLOG_ACTION_CONSOLE_LEVEL 8

// 禁用控制台输出(只记录到缓冲区)
syscall(SYS_syslog, SYSLOG_ACTION_CONSOLE_OFF, NULL, 0);

// 启用控制台输出
syscall(SYS_syslog, SYSLOG_ACTION_CONSOLE_ON, NULL, 0);

// 设置控制台级别为 Warning (4)
int old_level = syscall(SYS_syslog, SYSLOG_ACTION_CONSOLE_LEVEL, NULL, 5);
printf("Old console level: %d\n", old_level);

清空日志缓冲区

#define SYSLOG_ACTION_CLEAR 5

// 清空所有日志
int ret = syscall(SYS_syslog, SYSLOG_ACTION_CLEAR, NULL, 0);
if (ret < 0) {
    perror("syslog");
}

实现 dmesg 工具

完整的 dmesg 工具实现示例:

// dmesg.c - 简化的 dmesg 实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>

#define SYSLOG_ACTION_READ 2
#define SYSLOG_ACTION_READ_ALL 3
#define SYSLOG_ACTION_READ_CLEAR 4
#define SYSLOG_ACTION_CLEAR 5
#define SYSLOG_ACTION_CONSOLE_LEVEL 8
#define SYSLOG_ACTION_SIZE_UNREAD 9
#define SYSLOG_ACTION_SIZE_BUFFER 10

static void usage(const char *prog) {
    fprintf(stderr, "Usage: %s [options]\n", prog);
    fprintf(stderr, "Options:\n");
    fprintf(stderr, "  -c        Clear the ring buffer\n");
    fprintf(stderr, "  -C        Clear after reading\n");
    fprintf(stderr, "  -r        Print raw (do not consume)\n");
    fprintf(stderr, "  -n <level> Set console log level (1-8)\n");
    fprintf(stderr, "  -s        Show buffer size\n");
    exit(1);
}

int main(int argc, char *argv[]) {
    int opt;
    int action = SYSLOG_ACTION_READ_ALL;  // 默认非破坏性读取
    int clear_only = 0;
    int show_size = 0;
    int set_level = 0;
    int level = 0;

    // 解析命令行参数
    while ((opt = getopt(argc, argv, "cCrn:s")) != -1) {
        switch (opt) {
            case 'c':
                clear_only = 1;
                break;
            case 'C':
                action = SYSLOG_ACTION_READ_CLEAR;
                break;
            case 'r':
                action = SYSLOG_ACTION_READ_ALL;
                break;
            case 'n':
                set_level = 1;
                level = atoi(optarg);
                if (level < 1 || level > 8) {
                    fprintf(stderr, "Invalid level: %d (must be 1-8)\n", level);
                    return 1;
                }
                break;
            case 's':
                show_size = 1;
                break;
            default:
                usage(argv[0]);
        }
    }

    // 设置控制台级别
    if (set_level) {
        int ret = syscall(SYS_syslog, SYSLOG_ACTION_CONSOLE_LEVEL, NULL, level);
        if (ret < 0) {
            perror("syslog");
            return 1;
        }
        printf("Console level set to %d (was %d)\n", level, ret);
        if (!show_size && !clear_only && action == SYSLOG_ACTION_READ_ALL) {
            return 0;  // 只设置级别,不读取日志
        }
    }

    // 显示缓冲区大小
    if (show_size) {
        int size = syscall(SYS_syslog, SYSLOG_ACTION_SIZE_BUFFER, NULL, 0);
        int unread = syscall(SYS_syslog, SYSLOG_ACTION_SIZE_UNREAD, NULL, 0);
        if (size < 0 || unread < 0) {
            perror("syslog");
            return 1;
        }
        printf("Buffer size: %d bytes\n", size);
        printf("Unread: %d bytes\n", unread);
        return 0;
    }

    // 清空缓冲区
    if (clear_only) {
        int ret = syscall(SYS_syslog, SYSLOG_ACTION_CLEAR, NULL, 0);
        if (ret < 0) {
            perror("syslog");
            return 1;
        }
        return 0;
    }

    // 查询未读字节数
    int size = syscall(SYS_syslog, SYSLOG_ACTION_SIZE_UNREAD, NULL, 0);
    if (size < 0) {
        perror("syslog");
        return 1;
    }

    if (size == 0) {
        // 没有日志
        return 0;
    }

    // 分配缓冲区
    char *buf = malloc(size + 1);
    if (!buf) {
        perror("malloc");
        return 1;
    }

    // 读取日志
    int len = syscall(SYS_syslog, action, buf, size);
    if (len < 0) {
        perror("syslog");
        free(buf);
        return 1;
    }

    // 输出日志
    if (len > 0) {
        buf[len] = '\0';
        printf("%s", buf);
    }

    free(buf);
    return 0;
}

编译和使用

# 编译
gcc -o dmesg dmesg.c

# 查看内核日志
./dmesg

# 查看并清空
./dmesg -C

# 只清空
./dmesg -c

# 显示缓冲区状态
./dmesg -s

# 设置控制台级别
./dmesg -n 5  # 设置为 Notice

syslog 操作类型完整列表

宏定义描述参数
0SYSLOG_ACTION_CLOSE关闭日志(NOP)-
1SYSLOG_ACTION_OPEN打开日志(NOP)-
2SYSLOG_ACTION_READ破坏性读取buf, len
3SYSLOG_ACTION_READ_ALL非破坏性读取buf, len
4SYSLOG_ACTION_READ_CLEAR读取并清空buf, len
5SYSLOG_ACTION_CLEAR清空缓冲区-
6SYSLOG_ACTION_CONSOLE_OFF禁用控制台-
7SYSLOG_ACTION_CONSOLE_ON启用控制台-
8SYSLOG_ACTION_CONSOLE_LEVEL设置级别len (1-8)
9SYSLOG_ACTION_SIZE_UNREAD查询未读字节-
10SYSLOG_ACTION_SIZE_BUFFER查询缓冲区大小-

格式化复杂数据

基本格式化选项

#![allow(unused)]
fn main() {
let value = 42;

// 十进制
pr_info!("Value: {}", value);         // Value: 42

// 十六进制
pr_info!("Value: {:#x}", value);      // Value: 0x2a

// 二进制
pr_info!("Value: {:#b}", value);      // Value: 0b101010

// 指定宽度
pr_info!("Value: {:08x}", value);     // Value: 0000002a
}

格式化指针和地址

#![allow(unused)]
fn main() {
let addr = 0x80000000usize;
let ptr: *const u8 = 0x80001000 as *const u8;

pr_info!("Physical address: {:#x}", addr);
pr_info!("Pointer: {:p}", ptr);
pr_debug!("Page table entry: PTE[{}] = {:#018x}", index, pte_value);
}

格式化多个参数

#![allow(unused)]
fn main() {
let start_addr = 0x80000000usize;
let end_addr = 0x80001000usize;
let size = end_addr - start_addr;

pr_info!("Memory region: {:#x} - {:#x}, size = {} bytes",
         start_addr, end_addr, size);
}

使用 Debug trait

#![allow(unused)]
fn main() {
use core::fmt::Debug;

#[derive(Debug)]
struct Frame {
    ppn: usize,
    flags: u8,
}

let frame = Frame { ppn: 0x80000, flags: 0x7 };

// 使用 {:?} 格式化
pr_debug!("Allocated frame: {:?}", frame);
// 输出:Allocated frame: Frame { ppn: 524288, flags: 7 }

// 使用 {:#?} 格式化(多行美化)
pr_debug!("Frame details: {:#?}", frame);
// 输出:Frame details: Frame {
//     ppn: 524288,
//     flags: 7,
// }
}

条件格式化

#![allow(unused)]
fn main() {
let result: Result<usize, &str> = Err("out of memory");

match result {
    Ok(value) => pr_info!("Operation succeeded: value = {}", value),
    Err(e) => pr_err!("Operation failed: {}", e),
}

// 或者使用更简洁的方式
pr_info!("Result: {:?}", result);
}

格式化字符串切片

#![allow(unused)]
fn main() {
let name = "hello.txt";
let message = b"Hello, world!";

pr_info!("Filename: {}", name);
pr_debug!("Message: {:?}", message);  // 字节数组

// UTF-8 字符串
let utf8_str = core::str::from_utf8(message).unwrap();
pr_info!("Content: {}", utf8_str);
}

性能最佳实践

避免格式化被禁用的日志

早期过滤会自动处理,但了解其工作原理有助于编写高效代码:

#![allow(unused)]
fn main() {
// 好:使用宏,自动早期过滤
pr_debug!("Value: {}", expensive_calculation());
// 如果 Debug 被禁用,expensive_calculation() 不会被调用

// 坏:手动调用 log_impl,无早期过滤
use log::{log_impl, LogLevel};
log_impl(LogLevel::Debug, format_args!("Value: {}", expensive_calculation()));
// expensive_calculation() 总是被调用,即使 Debug 被禁用
}

热路径中的日志

在性能关键的代码路径中,即使是早期过滤也有微小开销:

#![allow(unused)]
fn main() {
// 方案 1:使用条件编译(推荐)
#[cfg(debug_assertions)]
pr_debug!("Processing item {}", i);

// 方案 2:减少日志频率
if i % 1000 == 0 {
    pr_debug!("Processed {} items", i);
}

// 方案 3:使用更低的级别
// 如果日志不是必需的,考虑完全移除
}

避免在中断处理程序中大量记录日志

中断处理程序应该快速完成,避免阻塞系统:

#![allow(unused)]
fn main() {
// 中断处理程序
fn timer_interrupt_handler() {
    // 好:只记录关键错误
    if critical_error {
        pr_err!("Timer interrupt error");
    }

    // 坏:记录每次中断(会严重影响性能)
    // pr_debug!("Timer interrupt fired");  // 不要这样做!
}
}

控制台输出的性能影响

控制台输出(串口通信)比缓冲区写入慢得多:

#![allow(unused)]
fn main() {
// 性能测试示例
use arch::timer::get_time;

let start = get_time();

// 1000 次缓冲区写入(不输出到控制台)
set_console_level(LogLevel::Emergency);  // 禁用控制台
for i in 0..1000 {
    pr_info!("Message {}", i);
}

let buffered_time = get_time() - start;

// 1000 次控制台输出
set_console_level(LogLevel::Info);  // 启用控制台
let start = get_time();
for i in 0..1000 {
    pr_info!("Message {}", i);
}

let console_time = get_time() - start;

pr_info!("Buffered: {} cycles, Console: {} cycles",
         buffered_time, console_time);
// 预期:console_time >> buffered_time(可能是 100 倍以上)
}

消息长度优化

避免超过 256 字节的消息:

#![allow(unused)]
fn main() {
// 好:简洁的日志
pr_info!("File opened: {}", filename);

// 坏:过长的日志(会被截断)
pr_info!("File opened with following properties: name={}, size={}, \
          permissions={}, owner={}, group={}, created={}, modified={}, \
          accessed={}, ... [very long message]", ...);

// 更好:分多条日志
pr_info!("File opened: {}", filename);
pr_debug!("File size: {} bytes", size);
pr_debug!("File owner: uid={}, gid={}", uid, gid);
}

多核并发场景

多核并发写入

Log 子系统是并发安全的,多个 CPU 可以同时记录日志:

#![allow(unused)]
fn main() {
// CPU 0
fn task_on_cpu0() {
    pr_info!("[CPU0] Starting task A");
    // ... 执行任务 ...
    pr_info!("[CPU0] Task A completed");
}

// CPU 1
fn task_on_cpu1() {
    pr_info!("[CPU1] Starting task B");
    // ... 执行任务 ...
    pr_info!("[CPU1] Task B completed");
}

// 两个 CPU 可以同时调用 pr_info!,无需担心竞争条件
// 日志会按照时间戳顺序记录到缓冲区
}

日志中包含 CPU ID

日志条目自动包含 CPU ID,帮助追踪多核执行:

#![allow(unused)]
fn main() {
// 在不同 CPU 上执行
for i in 0..100 {
    pr_debug!("Processing item {}", i);
}

// 输出示例:
// [000012345678] [DEBUG] [CPU0/Task1] Processing item 0
// [000012345679] [DEBUG] [CPU1/Task2] Processing item 1
// [000012345680] [DEBUG] [CPU0/Task1] Processing item 2
// [000012345681] [DEBUG] [CPU2/Task3] Processing item 3
}

使用时间戳分析并发行为

#![allow(unused)]
fn main() {
pr_info!("Task started");
// ... 执行任务 ...
pr_info!("Task completed");

// 读取日志后,通过时间戳计算执行时间
// [000012000000] [INFO] [CPU0/Task1] Task started
// [000012005000] [INFO] [CPU0/Task1] Task completed
// 执行时间:5000 个时钟周期
}

竞态条件的调试

#![allow(unused)]
fn main() {
// 使用日志追踪竞态条件
static SHARED_COUNTER: AtomicUsize = AtomicUsize::new(0);

fn increment_counter() {
    let old = SHARED_COUNTER.fetch_add(1, Ordering::SeqCst);
    pr_debug!("Counter: {} -> {}", old, old + 1);
}

// 多个 CPU 并发调用 increment_counter()
// 日志会显示每个 CPU 看到的值和顺序
}

常见陷阱

陷阱 1:消息被截断

问题:消息超过 256 字节会被截断

#![allow(unused)]
fn main() {
// 错误示例:超长消息
let long_string = "a".repeat(300);
pr_info!("Data: {}", long_string);
// 只会记录前 256 字节,后面的内容丢失
}

解决方案:分多条日志记录

#![allow(unused)]
fn main() {
// 正确做法:分段记录
let data = vec![1, 2, 3, /* ... 很多数据 */];
pr_info!("Data (total {} items):", data.len());
for (i, chunk) in data.chunks(10).enumerate() {
    pr_debug!("  Chunk {}: {:?}", i, chunk);
}
}

陷阱 2:忘记读取日志导致缓冲区溢出

问题:日志写入速度超过读取速度,旧日志被覆盖

#![allow(unused)]
fn main() {
// 持续写入日志,但从不读取
for i in 0..1000 {
    pr_info!("Message {}", i);
}

// 缓冲区只能容纳约 60 条日志
// 早期的日志(0-940)会被覆盖,只能读取到后 60 条
}

解决方案:定期读取日志

#![allow(unused)]
fn main() {
// 创建日志读取任务
fn log_reader() {
    loop {
        while let Some(entry) = read_log() {
            // 处理日志(打印、存储等)
            handle_log(entry);
        }
        sleep_ms(100);  // 每 100ms 读取一次
    }
}
}

陷阱 3:在日志中使用昂贵的计算

问题:即使有早期过滤,但如果计算在宏参数中,仍然会执行

#![allow(unused)]
fn main() {
// 错误示例:昂贵的计算
pr_debug!("Hash: {}", compute_expensive_hash(&data));
// 即使 Debug 被禁用,compute_expensive_hash 仍然会被调用!
}

解决方案:先检查级别再计算

#![allow(unused)]
fn main() {
// 正确做法:条件计算
use log::is_level_enabled;
if is_level_enabled(LogLevel::Debug) {
    let hash = compute_expensive_hash(&data);
    pr_debug!("Hash: {}", hash);
}

// 或者使用条件编译
#[cfg(debug_assertions)]
{
    let hash = compute_expensive_hash(&data);
    pr_debug!("Hash: {}", hash);
}
}

注意:这个陷阱是 Rust 宏的特性决定的,宏参数在宏展开前求值。

陷阱 4:日志级别配置不当

问题:global_level 低于 console_level,导致部分日志无法显示

#![allow(unused)]
fn main() {
// 错误配置
set_global_level(LogLevel::Warning);  // 只缓存 Warning 及以上
set_console_level(LogLevel::Info);    // 期望显示 Info 及以上

pr_info!("This message will not appear!");
// Info < Warning,不会被缓存,console_level 无效
}

解决方案:确保 global_level <= console_level

#![allow(unused)]
fn main() {
// 正确配置
set_global_level(LogLevel::Info);      // 缓存 Info 及以上
set_console_level(LogLevel::Warning);  // 显示 Warning 及以上

pr_info!("This will be buffered but not shown");
pr_warn!("This will be buffered and shown");
}

陷阱 5:在 panic handler 中记录日志

问题:panic handler 可能在不稳定状态下运行,日志系统可能无法正常工作

#![allow(unused)]
fn main() {
#[panic_handler]
fn panic_handler(info: &PanicInfo) -> ! {
    // 谨慎使用日志,此时系统状态可能不一致
    // pr_emerg! 是最安全的选择
    pr_emerg!("Kernel panic: {}", info);

    // 不要尝试读取日志缓冲区或复杂操作
    // 直接 shutdown 或进入死循环
    loop {}
}
}

调试技巧

追踪函数调用

#![allow(unused)]
fn main() {
fn allocate_frame() -> Result<Frame, Error> {
    pr_debug!(">>> Entering allocate_frame()");

    let result = do_allocate();

    match &result {
        Ok(frame) => pr_debug!("<<< allocate_frame() -> Ok(Frame {{ ppn: {:#x} }})", frame.ppn),
        Err(e) => pr_debug!("<<< allocate_frame() -> Err({:?})", e),
    }

    result
}
}

使用条件日志

#![allow(unused)]
fn main() {
// 只在特定条件下记录日志
if unlikely_condition {
    pr_warn!("Rare condition occurred: {}", details);
}

// 使用断言 + 日志
debug_assert!({
    pr_debug!("Assertion check: value = {}", value);
    value > 0
});
}

性能分析

#![allow(unused)]
fn main() {
use arch::timer::get_time;

fn performance_critical_function() {
    let start = get_time();

    // ... 执行代码 ...

    let elapsed = get_time() - start;
    pr_debug!("Function took {} cycles", elapsed);
}
}

状态转换日志

#![allow(unused)]
fn main() {
#[derive(Debug)]
enum State {
    Idle,
    Running,
    Blocked,
    Terminated,
}

fn set_task_state(task: &mut Task, new_state: State) {
    pr_debug!("Task {} state: {:?} -> {:?}", task.id, task.state, new_state);
    task.state = new_state;
}
}

使用日志分析死锁

#![allow(unused)]
fn main() {
// 记录锁的获取和释放
pr_debug!("Trying to acquire lock: {}", lock_name);
lock.acquire();
pr_debug!("Acquired lock: {}", lock_name);

// ... 临界区代码 ...

pr_debug!("Releasing lock: {}", lock_name);
lock.release();
pr_debug!("Released lock: {}", lock_name);

// 如果系统挂起,查看日志可以发现哪个锁未被释放
}

内存泄漏追踪

#![allow(unused)]
fn main() {
static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
static FREE_COUNT: AtomicUsize = AtomicUsize::new(0);

fn allocate() -> *mut u8 {
    let ptr = do_allocate();
    let count = ALLOC_COUNT.fetch_add(1, Ordering::Relaxed) + 1;
    pr_debug!("Allocated: {:p}, total allocations: {}", ptr, count);
    ptr
}

fn deallocate(ptr: *mut u8) {
    let count = FREE_COUNT.fetch_add(1, Ordering::Relaxed) + 1;
    pr_debug!("Freed: {:p}, total frees: {}", ptr, count);
    do_free(ptr);
}

// 定期检查
fn check_memory_leaks() {
    let allocs = ALLOC_COUNT.load(Ordering::Relaxed);
    let frees = FREE_COUNT.load(Ordering::Relaxed);
    if allocs != frees {
        pr_warn!("Potential memory leak: {} allocations, {} frees", allocs, frees);
    }
}
}

使用日志辅助 GDB 调试

#![allow(unused)]
fn main() {
// 在关键点记录日志
pr_info!("Checkpoint A: value = {}", value);

// 在 GDB 中设置断点:
// (gdb) break os/src/module.rs:123
// (gdb) condition 1 value == 42

// 结合日志查看执行流程
}

示例:完整的日志使用场景

文件系统操作日志

#![allow(unused)]
fn main() {
fn open_file(path: &str, flags: u32) -> Result<FileDescriptor, Error> {
    pr_info!("Opening file: {}, flags: {:#x}", path, flags);

    // 查找文件
    pr_debug!("Looking up inode for: {}", path);
    let inode = match lookup_inode(path) {
        Ok(inode) => {
            pr_debug!("Found inode: {}", inode.number);
            inode
        }
        Err(e) => {
            pr_err!("Failed to lookup file {}: {:?}", path, e);
            return Err(e);
        }
    };

    // 检查权限
    pr_debug!("Checking permissions for inode {}", inode.number);
    if !check_permissions(&inode, flags) {
        pr_warn!("Permission denied: {}, uid={}", path, current_uid());
        return Err(Error::PermissionDenied);
    }

    // 分配文件描述符
    let fd = allocate_fd(inode)?;
    pr_info!("File opened successfully: {} -> fd {}", path, fd);

    Ok(fd)
}
}

进程调度日志

#![allow(unused)]
fn main() {
fn schedule() -> ! {
    loop {
        // 选择下一个任务
        let next_task = scheduler::pick_next_task();

        pr_debug!("Scheduling: CPU{} switching to task {} ({})",
                 current_cpu_id(), next_task.id, next_task.name);

        // 上下文切换
        let prev_task = current_task();
        pr_debug!("Context switch: task {} -> task {}",
                 prev_task.id, next_task.id);

        context_switch(prev_task, next_task);

        // 任务恢复执行后
        pr_debug!("Task {} resumed", current_task().id);
    }
}
}

内存管理日志

#![allow(unused)]
fn main() {
fn allocate_pages(count: usize) -> Result<PhysAddr, Error> {
    pr_debug!("Allocating {} pages", count);

    // 检查可用内存
    let free_pages = get_free_page_count();
    pr_debug!("Free pages: {}, requested: {}", free_pages, count);

    if free_pages < count {
        pr_warn!("Low memory: {} pages free, {} requested",
                free_pages, count);

        // 尝试回收内存
        pr_info!("Attempting memory reclaim");
        reclaim_pages();

        let free_pages = get_free_page_count();
        if free_pages < count {
            pr_err!("Out of memory: {} pages free, {} requested",
                   free_pages, count);
            return Err(Error::OutOfMemory);
        }
    }

    let addr = frame_allocator::allocate(count)?;
    pr_debug!("Allocated pages: {:#x}, count: {}", addr, count);

    Ok(addr)
}
}

总结

Log 子系统提供了强大而灵活的日志功能:

  • 8 个宏覆盖不同的严重程度
  • 双过滤器平衡日志完整性和实时性
  • 无锁设计支持多核并发
  • 早期过滤保证性能
  • 丰富的格式化支持复杂数据

遵循本文档的最佳实践,可以有效利用日志系统进行开发、调试和问题诊断。