在这里插入图片描述

就像 SLF4J 是 Java 的日志门面库一样,log 是 Rust 官方提供的日志门面库(logging facade)。
它由官方团队维护,更新活跃、稳定可靠,大家可以放心使用。

使用也非常简单——只需在 Cargo.toml 中添加依赖即可:

[dependencies]
log = "0.4"

“日志门面”并不是指什么“排场很大”,而是指这一套日志 API 已成为事实上的标准接口,被各类日志框架广泛支持。
借助这种统一的日志门面,开发者无需再被具体框架所束缚——即使将来想更换日志实现,也能轻松切换,无需修改业务逻辑。

既然称为“门面”,log 自然提供了一套统一的日志接口和特征(traits),
对日志的相关操作进行了抽象与规范化。

Log 特征

例如,它定义了一个 Log 特征:

pub trait Log: Sync + Send {
    fn enabled(&self, metadata: &Metadata<'_>) -> bool;
    fn log(&self, record: &Record<'_>);
    fn flush(&self);
}

enabled:用于判断带有特定元数据的日志记录是否应被启用,常与 log_enabled! 宏配合使用。

log:负责实际记录日志,由传入的 record 对象表示具体的日志内容。

flush:将缓存中的日志数据立即写出,例如输出到控制台或日志文件中。

日志宏

log 同时提供了一套标准化的日志宏,方便我们快速输出日志信息。
这些宏包括:trace!、debug!、info!、warn!、error! —— 看起来是不是很熟悉?
它们与前面提到的日志级别一一对应,只是多了一个 trace!。
trace! 的级别比 debug! 更低,用于记录最为细致的调试信息。
如果你希望完整追踪某个流程的每个细节,就可以使用它。

use log::{info, trace, warn};

pub fn shave_the_yak(yak: &mut Yak) {
    trace!("Commencing yak shaving");

    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);
            }
        }
    }
}

在上面的示例中,我们用 trace! 记录了一条无关紧要的信息——例如“准备开始剃须”,接着尝试寻找剃须刀。
如果找到了,就用 info! 记录一条普通状态信息;
如果没找到,则使用 warn! 输出一条具有参考价值的警告日志,其中不仅包含原因,还能统计出现的次数,方便后续排查问题。

可以看到,这里的日志级别使用方式与前一章节的描述基本一致。

此外,log 还提供了两个辅助宏:

log!:用于在运行时动态指定日志级别;

log_enabled!:用于判断某条日志在当前模块、特定级别下是否会被记录。

use log::Level::Debug;
use log::{debug, log_enabled};

// 判断能否记录 Debug 消息
if log_enabled!(Debug) {
    let data = expensive_call();
     // 下面的日志记录较为昂贵,因此我们先在前面判断了是否能够记录,能,才继续这里的逻辑
    debug!("expensive debug data: {} {}", data.x, data.y);
}
if log_enabled!(target: "Global", Debug) {
   let data = expensive_call();
   debug!(target: "Global", "expensive debug data: {} {}", data.x, data.y);
}

log! 宏就简单的多,它是一个通用的日志记录方式,因此需要我们手动指定日志级别:

use log::{log, Level};

let data = (42, "Forty-two");
let private_data = "private";

log!(Level::Error, "Received errors: {}, {}", data.0, data.1);
log!(target: "app_events", Level::Warn, "App warning: {}, {}, {}",
    data.0, data.1, private_data);

日志输出在哪里?

也许有同学已经尝试运行了上面的示例代码,但你会发现——控制台上什么都没有输出。

为什么呢?原因很简单:log 只是一个日志门面库,它本身并不具备完整的日志实现功能。
换句话说,log 只定义了统一的日志接口,却不会真正输出任何日志内容。
在这种情况下,老老实实用 println! 反而更有用。

当然,这只是暂时的。
接下来,我们将看看如何为 log 配置真正的日志实现,让它“活”起来。

使用具体的日志库

log 包这么设计,其实是有很多好处的。

Rust 库的开发者

最大的好处在于:如果你是 Rust 库的开发者,你和你的用户都不希望库被强制绑定到某个特定的日志实现上。
否则,用户想用 log1,而你的库却依赖 log2,这不仅会造成冲突,还会让集成变得复杂。

因此,作为库的开发者,只需在库中使用日志门面库即可——至于具体选择哪个日志实现(如 env_logger、fern、tracing 等),完全可以交由用户自行决定与绑定。

use log::{info, trace, warn};
pub fn deal_with_something() {
    // 开始处理

    // 记录一些日志
    trace!("a trace log");
    info!("a info long: {}", "abc");
    warn!("a warning log: {}, retrying", err);

    // 结束处理
}

应用开发者

如果你是应用开发者,程序运行后却看不到任何日志输出,那种感觉肯定让人着急。
这时就需要为 log 选择并绑定一个具体的日志实现库。

目前已经有不少成熟的日志库可供选择,官方也在文档中推荐了一些。
其中,env_logger 是一个非常不错的选择——它简单易用,功能完善。

log 本身提供了 set_logger 函数用于注册日志实现,
以及 set_max_level 用于设置日志输出的最大级别。
不过,如果你使用了像 env_logger 这样的具体日志库,通常无需手动调用这些函数——库本身已经封装了更高级的初始化接口,使用起来更加方便。

env_logger

修改 Cargo.toml , 添加以下内容:

# in Cargo.toml

[dependencies]
log = "0.4.0"
env_logger = "0.9"

src/main.rs 中添加如下代码:

use log::{debug, error, log_enabled, info, Level};

fn main() {
    // 注意,env_logger 必须尽可能早的初始化
    env_logger::init();

    debug!("this is a debug {}", "message");
    error!("this is printed by default");

    if log_enabled!(Level::Info) {
        let x = 3 * 4; // expensive computation
        info!("the answer was: {}", x);
    }
}

在运行程序时,可以通过环境变量来设定日志级别:

$ RUST_LOG=error ./main
[2017-11-09T02:12:24Z ERROR main] this is printed by default

我们还可以为单独一个模块指定日志级别:

$ RUST_LOG=main=info ./main
[2017-11-09T02:12:24Z ERROR main] this is printed by default
[2017-11-09T02:12:24Z INFO main] the answer was: 12

还能为某个模块开启所有日志级别:

$ RUST_LOG=main ./main
[2017-11-09T02:12:24Z DEBUG main] this is a debug message
[2017-11-09T02:12:24Z ERROR main] this is printed by default
[2017-11-09T02:12:24Z INFO main] the answer was: 12

需要注意的是,如果文件名包含 -,你需要将其替换成下划线来使用,原因是 Rust 的模块和包名不支持使用 -

$ RUST_LOG=my_app ./my-app
[2017-11-09T02:12:24Z DEBUG my_app] this is a debug message
[2017-11-09T02:12:24Z ERROR my_app] this is printed by default
[2017-11-09T02:12:24Z INFO my_app] the answer was: 12

默认情况下,env_logger 会输出到标准错误 stderr,如果你想要输出到标准输出 stdout,可以使用 Builder 来改变日志对象( target ):

use std::env;
use env_logger::{Builder, Target};

let mut builder = Builder::from_default_env();
builder.target(Target::Stdout);

builder.init();

默认

if cfg!(debug_assertions) {
       eprintln!("debug: {:?} -> {:?}",
              record, fields);
     }

日志库开发者

对于这类开发者而言,自然要实现自己的 Log 特征咯:

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) {}
}

除此之外,我们还需要像 env_logger 一样包装下 set_loggerset_max_level:

use log::{SetLoggerError, LevelFilter};
static LOGGER: SimpleLogger = SimpleLogger;
pub fn init() -> Result<(), SetLoggerError> {
    log::set_logger(&LOGGER)
        .map(|()| log::set_max_level(LevelFilter::Info))
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐