是否可以在不同的环境中使用不同的记录器?

时间:2018-02-07 03:30:00

标签: rust

log crate使用:

  • 注册了一个全局静态LoggerSend + Sync)实例。
  • 所有info!warn!等宏将从所有线程调度到全局记录器。
  • 您使用set_max_level
  • 全局设置最低日志级别

是否有可能以某种方式解开这个并在不同的上下文中使用不同的记录器,例如,对于不同的线程?

以下是我想要实现的具体示例:

目标'A','B'和'C'是不同的记录目标,例如用于A的控制台,用于B的文件以及用于C的网络日志端点.A / B / C是什么并不重要,只是它们彼此不同

#[macro_use]
extern crate log;

use std::thread::spawn;

use log::{set_logger, set_max_level, Level, LevelFilter, Log, Metadata, Record};

struct Logger {}

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

    fn log(&self, record: &Record) {
        println!("{}", record.args());
    }

    fn flush(&self) {}
}

static LOGGER: Logger = Logger {};

fn init() {
    set_logger(&LOGGER).unwrap();
    set_max_level(LevelFilter::Info);
}

fn foo() {
    // Should always log to target 'B'
    info!("B");
}

fn main() {
    init();

    // Should log to target 'A'
    info!("A");
    foo();

    spawn(move || {
        // It would be nice, to be able to also, say, use a different
        // logging level here, e.g. only log error!() in this thread.

        // should log to target 'C'
        info!("C");

        // should still log to target 'B'
        foo();
    }).join()
        .unwrap();
}

显然,上面那个琐碎的记录器没有按照我的意愿行事,但可能以某种方式实现log::Log吗?如果是这样,怎么样?

我看到Record类型有一个metadata字段,但我看不出在自定义记录器中根据上下文调度使用它的任何有意义的方法吗?

是编写自定义日志包的唯一解决方案吗?

值得注意的是,如果全局级别为&gt;,则in log 0.4.1甚至不会调用记录器。最高水平;文档on how to do this显示错误,所以通常你会有一个函数来包装set_max_levelset_logger,但这只是一个简单的例子。

1 个答案:

答案 0 :(得分:1)

作为mentioned in the comments,您可以使用线程本地存储来构建一堆记录器。然后,您可以始终在堆栈顶部登录记录器:

use std::cell::RefCell;

trait Logger {
    fn log(&self, message: &str);
}

struct StderrLogger;

impl Logger for StderrLogger {
    fn log(&self, message: &str) {
        eprintln!("Logger: {}", message);
    }
}

struct NetworkLogger;

impl Logger for NetworkLogger {
    fn log(&self, message: &str) {
        eprintln!("The cloud: {}", message);
    }
}

thread_local! {
    static LOGGER: RefCell<Vec<Box<Logger>>> = RefCell::new(vec![Box::new(StderrLogger)]);
}

fn push_context<L, F, R>(l: L, f: F) -> R
where
    L: Logger + 'static,
    F: FnOnce() -> R,
{
    LOGGER.with(|logger| logger.borrow_mut().push(Box::new(l)));

    let r = f();

    LOGGER.with(|logger| logger.borrow_mut().pop());

    r
}

macro_rules! log {
    ($msg:expr) => {
        LOGGER.with(|logger| {
            if let Some(logger) = logger.borrow().last() {
                logger.log($msg)
            }
        })
    }
}

fn main() {
    log!("a");
    push_context(NetworkLogger, || {
        log!("b");
    });
    log!("c");
}

您需要有方法将新记录器添加到堆栈并将其删除(push_context)。

我没有花时间将其与日志箱进行实际整合,但我相信它应该是直截了当的。您必须实现日志包来执行此log宏中的工作所需的特征。

这是我的观点,但是:全局任何,包括记录器,都是代码味道。当你开始想要在简单的抽象中强加更多细节时,尤其如此。

Growing Object-Oriented Software, Guided by Tests确实帮助我澄清了我对此的看法:

  

记录是功能

     
      
  • 支持日志记录(错误和信息)是   应用程序的用户界面的一部分。这些消息是   旨在由支持人员以及可能的系统进行跟踪   管理员和操作员,诊断故障或监控   运行系统的进展。

  •   
  • 诊断日志记录(调试和跟踪)   是程序员的基础设施。不应该转动这些消息   在生产中,因为它们旨在帮助程序员   了解他们正在开发的系统内部正在发生什么。

  •   

前者应该几乎总是属于系统领域的一部分,不应该是二等公民。使用对您的应用程序有意义的方法创建自定义特征,并使用依赖注入来传递它。

后者是我发现的适合日志箱的东西 - 倾倒原始数据以诊断野外问题。