一款优秀的应用离不开优秀的遥测数据,毫不夸张地说日志是所有系统的基石。

Crate log

log提供了一个单一的日志 API,抽象了实际的日志实现,是rust最轻量级的日志抽象库。许多受欢迎的日志库基于该库实现,我们也可以使用log实现自定义的日志记录器。

Usage

log提供了5个宏,分别对应5个日志级别来记录日志

  • error!
  • warn!
  • info!
  • debug!
  • trace!
fn main() {
    trace!("trace log");
    debug!("debug log");
    info!("info log");
    warn!("warn log");
    error!("error log");
}

上述代码仅会对日志信息进行记录,我们要对日志进行消费需要实现一个日志记录器并在程序运行前初始化全局日志记录器,log提供了一个set_logger函数来进行日志记录器的初始化工作,

pub fn set_logger(logger: &'static dyn Log) -> Result<(), SetLoggerError>

需要注意的是日志记录器只能初始化一次

Log Level

#[repr(usize)]
// [repr(usize)]设置枚举的内存布局为
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub enum Level {
    /// The "error" level.
    ///
    /// Designates very serious errors.
    // This way these line up with the discriminants for LevelFilter below
    // This works because Rust treats field-less enums the same way as C does:
    // https://doc.rust-lang.org/reference/items/enumerations.html#custom-discriminant-values-for-field-less-enumerations
    Error = 1,
    /// The "warn" level.
    ///
    /// Designates hazardous situations.
    Warn,
    /// The "info" level.
    ///
    /// Designates useful information.
    Info,
    /// The "debug" level.
    ///
    /// Designates lower priority information.
    Debug,
    /// The "trace" level.
    ///
    /// Designates very low priority, often extremely verbose, information.
    Trace,
}

Implement a Logger

log提供了一个Logger Trait来抽象日志记录器的行为,因此我们只需要实现该Trait就可以实现一个自定义的日志记录器

struct MyLogger;

impl log::Log for MyLogger {
    // 设置日志记录级别
    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) {}
}

Example

use log::{
    debug, error, info, trace, warn, Level, LevelFilter, Log, Metadata, Record, SetLoggerError,
};
static LOGGER: MyLogger = MyLogger;

fn main() {
    init_log(&LOGGER).expect("init log failed");
    trace!("trace log");
    debug!("debug log");
    info!("info log");
    warn!("warn log");
    error!("error log");
}

struct MyLogger;

impl log::Log for MyLogger {
    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) {}
}

#[inline]
fn init_log(loger: &'static impl Log) -> Result<(), SetLoggerError> {
    log::set_logger(loger).map(|()| log::set_max_level(LevelFilter::Info))
}

Crate env_logger

A simple logger that can be configured via environment variables, for use with the logging facade exposed by the log crate.

Despite having “env” in its name, env_logger can also be configured by other means besides environment variables. See the examples in the source repository for more approaches.

By default, env_logger writes logs to stderr, but can be configured to instead write them to stdout.

一个基于log的极简日志框架,支持环境变量配置日志输出级别。

Usage

env_logger提供了两种初始化方式,使用默认配置和自定义配置两种方式

 // 默认配置,如果设置了ROG_LOG环境变量,则按照环境变量进行日志过滤,没有设置则默认过滤error以下级别的日志
env_logger::try_init().expect("init logger failed");
trace!("hello rust");
debug!("hello rust");
info!("hello rust");
warn!("hello rust");
error!("hello rust");
...
 // 构建者模式,使用builder对logger对象进行配置
let mut logger_builder = builder();
    logger_builder
        .filter_level(LevelFilter::Info) // 过滤级别
        .target(Target::Stdout) // 日志输出目标
        .target(Target::Pipe(Box::new(FilePipe::new(
            current_dir().unwrap().join("logs"),
        )))) // 多个target设置,后者会覆盖前者
        .try_init() // 尝试初始化
        .expect("init logger failed");
env_logger::try_init().expect("init logger failed");
trace!("hello rust");
debug!("hello rust");
info!("hello rust");
warn!("hello rust");
error!("hello rust");

功能扩展

env_logger虽然可以配置日志输出流,但它默认仅提供了标准输出流和错误流

#[non_exhaustive]
#[derive(Default)]
pub enum Target {
    /// Logs will be sent to standard output.
    Stdout,
    /// Logs will be sent to standard error.
    #[default]
    Stderr,
    /// Logs will be sent to a custom pipe.
    Pipe(Box<dyn std::io::Write + Send + 'static>),
}

好在给我们预留了一个Pipe枚举,我们只需要实现std::io::Write + Send + 'static即可自定义日志输出位置,假设我们想把日志同时输出至stdout和文件中,实现一个复合式的Pipe即可。首先,定义一个结构体

struct CombinePipe
{
    inner: Vec<Box<dyn Write + Send>>,
}

接下来实现WriteSend(Send会被自动实现)

impl CombinePipe

{
    #[inline]
    pub fn new(pipes: Vec<Box<dyn Write + Send>>) -> Self {
        CombinePipe { inner: pipes }
    }
}

impl Write for CombinePipe
{
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        for pipe in &mut self.inner {
            pipe.write(buf)?;
        }
        Ok(buf.iter().count())
    }

    fn flush(&mut self) -> std::io::Result<()> {
        for pipe in &mut self.inner{
            pipe.flush()?;
        }
        Ok(())
    }
}

这样一个复合的日志输出管道就实现了,下面看看使用效果

fn main() {
    let stdout_pipe = stdout();
    let file_pipe = FilePipe::new(current_dir().unwrap());
    let pipes = CombinePipe::new(vec![Box::new(stdout_pipe), Box::new(file_pipe)]);
    let mut logger_builder = builder();
    logger_builder
        .filter_level(LevelFilter::Info)
        .target(Target::Pipe(Box::new(pipes)))
        .try_init()
        .expect("init logger failed");
    trace!("hello rust");
    info!("hello rust");
    debug!("hello rust");
    error!("hello rust");
    warn!("hello rust");
}

struct CombinePipe
{
    inner: Vec<Box<dyn Write + Send>>,
}

impl CombinePipe

{
    #[inline]
    pub fn new(pipes: Vec<Box<dyn Write + Send>>) -> Self {
        CombinePipe { inner: pipes }
    }
}

impl Write for CombinePipe
{
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        for pipe in &mut self.inner {
            pipe.write(buf)?;
        }
        Ok(buf.iter().count())
    }

    fn flush(&mut self) -> std::io::Result<()> {
        for pipe in &mut self.inner{
            pipe.flush()?;
        }
        Ok(())
    }
}

// 自定义一个文件输出管道
struct FilePipe {
    dir: PathBuf,
}

impl FilePipe {
    pub fn new(dir: PathBuf) -> Self {
        if dir.is_dir() && dir.exists() {
            FilePipe { dir }
        } else {
            panic!("{dir:?} is not exist or not a dir")
        }
    }

    // 根据日期计算日志文件名称
    pub fn current_target(&self) -> String {
        format!(
            "{}-{}-{}.log",
            Local::now().year(),
            Local::now().month(),
            Local::now().day()
        )
    }
}


impl Write for FilePipe {
    // 将日志内容更写入到指定文件
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        let mut writer = OpenOptions::new()
            .create(true)
            .read(true)
            .write(true)
            .append(true)
            .open(&self.dir.join(self.current_target()))
            .expect("open log file failed");
        writer.write(buf)
    }

    fn flush(&mut self) -> std::io::Result<()> {
        let mut writer = OpenOptions::new()
            .create(true)
            .read(true)
            .write(true)
            .append(true)
            .open(&self.dir.join(self.current_target()))
            .expect("open log file failed");
        writer.flush()
    }
}

image-20250106110300823