【Rust】日志库log

日志库一般会实现日志分级、日志过滤、日志输出格式化、日志回滚等功能。本文介绍了Rust log库的使用,并给出了几个常用日志库的使用示例。


一、Rust log

1、log库

这个log库给出了日志库的一般抽象,后面具体的日志库需要基于这个抽象实现具体的实例。后面通过示例说明。

log的最基本的用法是通过5个宏来实现的,分别是: [error!], [warn!], [info!], [debug!] ,[trace!]。

//levels of the logging
pub enum LogLevel {
    Error,         //error是日志分级的最高等级
    Warn,
    Info,
    Debug,
    Trace,         //trace是最低等级
}

对于如何自定义分级,暂不涉及,自定义分级的话,当项目比较大,涉及很多库时,如果多个库自定义分级,最后日志是很难处理的,所以当多个库都涉及日志时,不同库的日志分级不同,会有问题,所以不建议自定义日志分级,除非当前分级不满足项目需求时才考虑自定义的问题。

与日志level相关的还有一个日志过滤,以后会用到:

//level filters of the logging framework
pub enum LogLevelFilter {
    Off,
    Error,
    Warn,
    Info,
    Debug,
    Trace,
}

下面的示例展示类log最基本的用法:

#[macro_use]
extern crate log;

use log::LogLevel;//日志等级

fn main() {
    log_lever_fn();
}

fn log_lever_fn(){
    let data=(42,"Forty-two");
    let private_data="private";
    log!(LogLevel::Error,"Received errors:{},{}",data.0,data.1);
    log!(target:"app_events",LogLevel::Warn,"App warning:{},{},{}",data.0,data.1,private_data);

    let (err_info,port)=("No connection",22);
    error!("Error:{} on port {}",err_info,port);
}

下面这段源代码有些难读,可以参考这里:https://kaisery.gitbooks.io/rust-book-chinese/content/content/Macros 宏.html

//log宏源代码
/// This macro will generically log with the specified `LogLevel` and `format!`
/// based argument list.
#[macro_export]
macro_rules! log {
    (target: $target:expr, $lvl:expr, $($arg:tt)+) => ({
        static _LOC: $crate::LogLocation = $crate::LogLocation {
            __line: line!(),
            __file: file!(),
            __module_path: module_path!(),
        };
        let lvl = $lvl;
        if lvl <= $crate::__static_max_level() && lvl <= $crate::max_log_level() {
            $crate::__log(lvl, $target, &_LOC, format_args!($($arg)+))
        }
    });
    ($lvl:expr, $($arg:tt)+) => (log!(target: module_path!(), $lvl, $($arg)+))
}

下面是error宏源代码,其他几个等级宏类似,这里不一一列出:

//Logs a message at the error level.
#[macro_export]
macro_rules! error {
    (target: $target:expr, $($arg:tt)*) => (
        log!(target: $target, $crate::LogLevel::Error, $($arg)*);
    );
    ($($arg:tt)*) => (
        log!($crate::LogLevel::Error, $($arg)*);
    )
}

2、logger

仅仅用log库很多情况下是不能满足功能需求的,为此有不同的logger实现,下面是目前较常用的logger:

  • Simple minimal loggers:

    • env_logger——A logger configured via an environment variable(通过环境变量进行日志配置)
    • simple_logger
    • simplelog
  • Complex configurable frameworks:

实际工程应用中需要根据不同的需求选用不同的logger。下面以env_logger为例:

#[macro_use]
extern crate log;
extern crate env_logger;

fn main() {
    env_logger_fn();
}

fn env_logger_fn(){
	//Initializes the global logger with an env logger.
    env_logger::init().unwrap();
    info!("starting up");
    error!("error:{}",404);
}
//Cargo.toml
[dependencies]
log = "0.3"
env_logger = "0.4.3"

编译、运行结果:
这里写图片描述
通过环境变量设置日志等级为info level,运行结果如下:
这里写图片描述

3、env_logger库详解

上面提到了env_logger,并且给了一个示例,这里给出env_logger的用法及源代码。

env_logger——A logging implementation for log which is configured via an environment variable. 。也就是说日志配置是通过环境变量实现的,后面会学习到其他logger通过配置文件的方式去进行日志配置,这也是我们最常用的情况。

日志输出定义:

/// Log target, either stdout or stderr.
#[derive(Debug)]
pub enum LogTarget {
    Stdout,
    Stderr,
}

Logger定义:

/// The logger.
pub struct Logger {
    directives: Vec<LogDirective>,
    filter: Option<filter::Filter>,
    format: Box<Fn(&LogRecord) -> String + Sync + Send>,
    target: LogTarget,
}

impl Logger {
    pub fn new() -> Logger {
        let mut builder = LogBuilder::new();

        if let Ok(s) = env::var("RUST_LOG") {
            builder.parse(&s);
        }

        builder.build()
    }

//实现Log特性trait    
impl Log for Logger {
    fn enabled(&self, metadata: &LogMetadata) -> bool {
        self.enabled(metadata.level(), metadata.target())
    }

    fn log(&self, record: &LogRecord) {
        if !Log::enabled(self, record.metadata()) {
            return;
        }

        if let Some(filter) = self.filter.as_ref() {
            if !filter.is_match(&*record.args().to_string()) {
                return;
            }
        }

        match self.target {
            LogTarget::Stdout => println!("{}", (self.format)(record)),
            LogTarget::Stderr => {
                let _ = writeln!(&mut io::stderr(), "{}", (self.format)(record));
            },
        };
    }
}

log::Log特性trait(A trait encapsulating the operations required of a logger),每个logger都必须实现Log特性:

pub trait Log: Sync + Send {
    fn enabled(&self, metadata: &LogMetadata) -> bool;//Determines if a log message with the specified metadata would be logged.
    fn log(&self, record: &LogRecord);//Logs the LogRecord.
}

LogBuilder定义(非常重要,可以配置日志输出格式等):

/// LogBuilder acts as builder for initializing the Logger.
/// It can be used to customize the log format , change the enviromental variable used
/// to provide the logging directives and also set the default log level filter.
pub struct LogBuilder {
    directives: Vec<LogDirective>,
    filter: Option<filter::Filter>,
    format: Box<Fn(&LogRecord) -> String + Sync + Send>,
    target: LogTarget,
}

impl LogBuilder {
    /// Initializes the log builder with defaults
    pub fn new() -> LogBuilder {
        LogBuilder {
            directives: Vec::new(),
            filter: None,
            format: Box::new(|record: &LogRecord| {
                format!("{}:{}: {}", record.level(),
                        record.location().module_path(), record.args())
            }),
            target: LogTarget::Stderr,
        }
    }
    
    ......
}

如何配置日志输出格式等,见下例:

#[macro_use]
extern crate log;
extern crate env_logger;

use std::env;
use log::{LogRecord, LogLevelFilter};
use env_logger::LogBuilder;

fn main() {
    env_log_builder();
}

#[warn(dead_code)]
fn env_log_builder(){
    let format=|record:&LogRecord|{
        format!("自定义格式:{}-{}",record.level(),record.args())
    };//配置日志输出格式

    let mut builder=LogBuilder::new();
    builder.format(format).filter(None,log::LogLevelFilter::Info);//设置默认日志level,可以改log::LogLevelFilter::Info为log::LogLevelFilter::Warn,重新编译运行,则INFO级信息就过滤掉了。后面可以通过环境变量修改日志level。

    if env::var("RUST_LOG").is_ok(){
        builder.parse(&env::var("RUST_LOG").unwrap());
    }

    builder.init().unwrap();
    error!("error message");
    info!("info message");
}

编译、运行结果如下:
这里写图片描述

二、simple_logger库详解

基本是最简单的日志库了。下面是simple_logger的简单示例:

1、示例1——最简单用法
#[macro_use]
extern crate log;
extern crate simple_logger;

fn main() {
    simple_logger_fn();
}

#[warn(dead_code)]
fn simple_logger_fn(){
    simple_logger::init().unwrap();
    warn!("This is an example message.");
}
//Cargo.toml
[dependencies]
log = "0.4"
simple_logger = "0.5"

编译、运行结果如下:
这里写图片描述

2、示例2——初始化时改变默认日志level等级
#[macro_use]
extern crate log;
extern crate simple_logger;

fn main() {
    simple_logger_level();
}

#[warn(dead_code)]
fn simple_logger_level(){
    simple_logger::init_with_level(log::LogLevel::Warn).unwrap();
    warn!("This will be logged.");
    info!("This will NOT be logged.");//过滤掉了
}

编译、运行结果如下:
这里写图片描述

3、源代码分析

因为这个源代码较少,所以就把所有源代码都列出来了:

//! A logger that prints all messages with a readable output format.
#[macro_use]
extern crate log;
extern crate time;

use log::{Log,LogLevel,LogMetadata,LogRecord,SetLoggerError};

struct SimpleLogger {
    log_level: LogLevel,
}

//实现Log特性
impl Log for SimpleLogger {
    fn enabled(&self, metadata: &LogMetadata) -> bool {
        metadata.level() <= self.log_level
    }

    fn log(&self, record: &LogRecord) {
        if self.enabled(record.metadata()) {
            println!(
                "{} {:<5} [{}] {}",
                time::strftime("%Y-%m-%d %H:%M:%S", &time::now()).unwrap(),
                record.level().to_string(),
                record.location().module_path(),
                record.args());
        }
    }
}

//2种初始化logger的方式:

/// Initializes the global logger with a SimpleLogger instance with
/// `max_log_level` set to a specific log level.
pub fn init_with_level(log_level: LogLevel) -> Result<(), SetLoggerError> {
    log::set_logger(|max_log_level| {
        max_log_level.set(log_level.to_log_level_filter());//设置日志level
        return Box::new(SimpleLogger { log_level: log_level });
    })
}

/// Initializes the global logger with a SimpleLogger instance with
/// `max_log_level` set to `LogLevel::Trace`.
pub fn init() -> Result<(), SetLoggerError> {
    init_with_level(LogLevel::Trace)//默认等级为Trace
}

代码简洁,功能简单。

三、simplelog库详解

simplelog的目标不是提供非常丰富的特性,也不是提供最佳日志方案,而是旨在提供简单易用的适合中小规模工程的日志方案。

simplelog提供了一些logging facilities如下所示(也是simplelog中最重要的概念):

  • SimpleLogger—— very basic logger that logs to stderr/out, should never fail
  • TermLogger ——advanced terminal logger, that splits to stderr/out and has color support (can be excluded on unsupported platforms)
  • WriteLogger ——logs to a given struct implementing Write. e.g. a file
  • CombinedLogger ——can be used to form combinations of the above loggers

对应的,simplelog中4个重要的structs:
分析SimpleLogger源代码如下:

//! Module providing the SimpleLogger Implementation

use std::io::{stderr, stdout};
use log::{LogLevel, LogLevelFilter, LogMetadata, LogRecord, SetLoggerError, set_logger, Log};
use ::{Config, SharedLogger};
use super::logging::try_log;

/// The SimpleLogger struct. Provides a very basic Logger implementation
pub struct SimpleLogger {
    level: LogLevelFilter,
    config: Config,
}

impl SimpleLogger {

    /// init function. Globally initializes the SimpleLogger as the one and only used log facility.
    ///
    /// Takes the desired `LogLevel` and `Config` as arguments. They cannot be changed later on.
    /// Fails if another Logger was already initialized.
    pub fn init(log_level: LogLevelFilter, config: Config) -> Result<(), SetLoggerError> {
        set_logger(|max_log_level| {
            max_log_level.set(log_level.clone());
            SimpleLogger::new(log_level, config)
        })
    }

    /// allows to create a new logger, that can be independently used, no matter what is globally set.
    ///
    /// no macros are provided for this case and you probably
    /// dont want to use this function, but `init()`, if you dont want to build a `CombinedLogger`.
    ///
    /// Takes the desired `LogLevel` and `Config` as arguments. They cannot be changed later on.
    pub fn new(log_level: LogLevelFilter, config: Config) -> Box<SimpleLogger> {
        Box::new(SimpleLogger { level: log_level, config: config })
    }
}
//实现Log Trait
impl Log for SimpleLogger {

    fn enabled(&self, metadata: &LogMetadata) -> bool {
        metadata.level() <= self.level
    }

    fn log(&self, record: &LogRecord) {
        if self.enabled(record.metadata()) {
            match record.level() {
                LogLevel::Error => {
                    let stderr = stderr();
                    let mut stderr_lock = stderr.lock();
                    let _ = try_log(&self.config, record, &mut stderr_lock);
                },
                _ => {
                    let stdout = stdout();
                    let mut stdout_lock = stdout.lock();
                    let _ = try_log(&self.config, record, &mut stdout_lock);
                }
            }
        }
    }
}
//实现SharedLogger Trait 
impl SharedLogger for SimpleLogger {

    fn level(&self) -> LogLevelFilter {
        self.level
    }

    fn config(&self) -> Option<&Config>
    {
        Some(&self.config)
    }

    fn as_log(self: Box<Self>) -> Box<Log> {
        Box::new(*self)
    }

}

SimpleLogger是代码最简单的一个,其他Structs源代码参看:https://github.com/Drakulix/simplelog.rs/tree/master/src/loggers
另外,因为下面的用法示例用到了CombinedLogger,所以把它的源代码也列出来学习一下:

//! Module providing the CombinedLogger Implementation

use log::{LogLevelFilter, LogMetadata, LogRecord, SetLoggerError, set_logger, Log};
use ::{SharedLogger, Config};

/// The CombinedLogger struct. Provides a Logger implementation that proxies multiple Loggers as one.
///
/// The purpose is to allow multiple Loggers to be set globally
pub struct CombinedLogger {
    level: LogLevelFilter,
    logger: Vec<Box<SharedLogger>>,
}

impl CombinedLogger {

    /// init function. Globally initializes the CombinedLogger as the one and only used log facility.
    ///
    /// Takes all used Loggers as a Vector argument. None of those Loggers should already be set globally.
    ///
    /// All loggers need to implement `log::Log` and `logger::SharedLogger` and need to provide a way to be
    /// initialized without calling `set_logger`. All loggers of this library provide a `new(..)`` method
    /// for that purpose.
    /// Fails if another logger is already set globally.
    pub fn init(logger: Vec<Box<SharedLogger>>) -> Result<(), SetLoggerError> {
        set_logger(|max_log_level| {
            let result = CombinedLogger::new(logger);
            max_log_level.set(result.level());
            result
        })
    }

    /// allows to create a new logger, that can be independently used, no matter whats globally set.
    ///
    /// no macros are provided for this case and you probably
    /// dont want to use this function, but `init()`, if you dont want to build a `CombinedLogger`.
    ///
    /// Takes all used loggers as a Vector argument. The log level is automatically determined by the
    /// lowest log level used by the given loggers.
    ///
    /// All loggers need to implement log::Log.
    pub fn new(logger: Vec<Box<SharedLogger>>) -> Box<CombinedLogger> {
        let mut log_level = LogLevelFilter::Off;
        for log in &logger {
            if log_level < log.level() {
                log_level = log.level();
            }
        }

        Box::new(CombinedLogger { level: log_level, logger: logger })
    }

}
//实现Log Trait
impl Log for CombinedLogger {

    fn enabled(&self, metadata: &LogMetadata) -> bool {
        metadata.level() <= self.level
    }

    fn log(&self, record: &LogRecord) {
        if self.enabled(record.metadata()) {
            for log in &self.logger {
                log.log(record);
            }
        }
    }
}
//实现SharedLogger Trait
impl SharedLogger for CombinedLogger {

    fn level(&self) -> LogLevelFilter {
        self.level
    }

    fn config(&self) -> Option<&Config>
    {
        None
    }

    fn as_log(self: Box<Self>) -> Box<Log> {
        Box::new(*self)
    }

}

这里面有个Config structs,对应源代码如下:

use log::LogLevel;

/// Configuration for the Loggers
///
/// All loggers print the message in the following form:
/// `00:00:00 [LEVEL] crate::module: [lib.rs::100] your_message`
/// Every space delimited part except the actual message is optional.
///
/// Pass this struct to your logger to change when these information shall
/// be logged. Every part can be enabled for a specific LogLevel and is then
/// automatically enable for all lower levels as well.
///
/// The Result is that the logging gets more detailed the more verbose it gets.
/// E.g. to have one part shown always use `LogLevel::Error`. But if you
/// want to show the source line only on `Trace` use that.
/// Passing `None` will completely disable the part.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Config
{
    ///At which level and below the current time shall be logged
    pub time: Option<LogLevel>,
    ///At which level and below the level itself shall be logged
    pub level: Option<LogLevel>,
    ///At which level and below the target shall be logged
    pub target: Option<LogLevel>,
    ///At which level and below a source code reference shall be logged
    pub location: Option<LogLevel>,
}

impl Default for Config {
    fn default() -> Config {
        Config {
            time: Some(LogLevel::Error),
            level: Some(LogLevel::Error),
            target: Some(LogLevel::Debug),
            location: Some(LogLevel::Trace),
        }
    }
}

用法示例:

#[macro_use]
extern crate log;
extern crate simplelog;

fn main() {
    simplelog_fn();
}

#[warn(dead_code)]
fn simplelog_fn(){
    use std::fs::File;
    use simplelog::*;

    CombinedLogger::init(
        vec![
            TermLogger::new(LogLevelFilter::Warn, Config::default()).unwrap(),//terminal logger
            WriteLogger::new(LogLevelFilter::Info, Config::default(), File::create("my_rust_binary.log").unwrap()),//记录日志到"*.log"文件中
        ]
    ).unwrap();

    error!("Bright red error");
    info!("This only appears in the log file");
    debug!("This level is currently not enabled for any logger");  
}

运行结果:
终端显示如下:
这里写图片描述
日志文件内容如下:
这里写图片描述

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页