如何在rust中输出日志:在rust中打印日志的各种方式对比

有许多库可以在 Rust 中输出日志,有时很难选择该使用哪一个。当 println!dbg!eprintln! 无法解决问题时,找到一种方便记录日志的方法就很重要,尤其是在生产级应用程序中。本文将帮助您深入了解在 Rust 日志记录方面最适合您的用例的日志 crate。

Rust 中的日志如何工作?

简而言之:Rust 中的日志依赖于一个库来充当"日志门面"------以便其他crate可以和日志 API 配合工作。例如,如果我们有一个像 log 这样的crate,它为我们提供了可以与日志记录器一起使用的日志记录实现,那么我们还需要添加一个实际 执行日志记录的crate 例如, simple-logger 是可以使用 log 的众多crate之一。某些日志门面可能只能由特定日志记录器使用 - 例如, tracing 要求您使用 tracing-subscriber crate,要么实现您自己的自定义类型,该类型实现 tracing::Subscriber

话不多说,下边开始 Rust 日志crate的比较!

log

log 是一个自称为"轻量级日志记录门面"的crate。该crate将日志门面定义为一个库,"提供一个抽象 实际日志记录实现的单一日志记录 API"------本质上,这意味着我们需要运行另一个提供实际日志记录的库,然后使用此crate来提供日志消息。 Log 也由 Rust 核心团队维护,并且可能是您在 Rust Cookbook 上看到的第一个 crate,所以就是这样。

下面是一个关于如何使用它的简单示例,取自 GitHub 存储库:

rust 复制代码
use log;

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

    loop {
        match find_a_razor() {
            Ok(razor) => {
                log::info!("Razor located: {}", razor);
                yak.shave(razor);
                break;
            }
            Err(err) => {
                log::warn!("Unable to locate a razor: {}, retrying", err);
            }
        }
    }
}

应该注意的是 log 还与许多日志记录器crate兼容 - 仅他们的 GitHub 存储库就列出了 20 多个日志记录器crate,并且是一个非详尽的列表!如果您正在寻找一款多功能记录器,这绝对适合您。然而,它也不像其他一些crate那么强大,要记住这一点。

对于大多数常见的用例,使用 log crate 是最简单的:只需设置消息级别,然后发送消息!

A quick summary: 快速总结:

  • 由官方 Rust 团队维护
  • 适用于几乎所有日志记录器crate
  • 不像其他一些特殊日志 crate 那么强大

env-logger 环境记录器

env-logger 是一个简单的 Rust 日志记录器,它易于使用,对于任何想要实现日志记录但不希望需要大量样板文件的 大型项目来说都非常方便。它由 Rust CLI 工作组 (WG) 所有,这意味着它将获得长期支持,这对我们来说非常好。

它可以在一行语句中设置:

rust 复制代码
let logger = Logger::from_default_env();

然后,只需像这样从 Cargo 运行程序,并在命令前面添加 RUST_LOG 环境变量:

bash 复制代码
# This command will run your program and only print out error messages from logs
RUST_LOG=ERROR cargo run

还可以在应用程序中硬编码最低日志级别,如下所示:

rust 复制代码
use env_logger::{Logger, Env};

let env = Env::new()
// filters out any messages that aren't at "info" log level or above
 .filter_or("MY_LOG", "info")
// always use styles when printing
 .write_style_or("MY_LOG_STYLE", "always");

let logger = Logger::from_env(env);

还可以设置特定依赖项的日志级别(这与 log crate结合使用):

rust 复制代码
use env_logger::Builder;
use log::LevelFilter;

let mut builder = Builder::new();

builder.filter_module("path::to::module", LevelFilter::Info);
 .unwrap();

然而,尽管它很方便, env-logger 确实遇到了您可能在生产级应用程序中寻找的一些问题:即,几乎没有关于为日志编写自己的管道的记录功能。可能会使其实现起来相当棘手,而且也不清楚这个crate是否是线程安全的。不用说,它对于任何快速而混乱的日志记录来说都是非常有用的!

A quick summary: 快速总结:

  • 由 Rust CLI 工作组所有
  • 使用简单,使用感觉良好
  • 缺乏有关日志附加/管道等更复杂功能的文档
  • 关于 crate 是否 100% 线程安全的一些不清楚的问题

log4rs

log4rs 是一个以 Java 的 log4j 为模型的日志包,log4j 是一个日志包,可能是部署最多的开源软件之一。该板条箱比其他板条箱需要更多的设置,并且可以使用 YAML 文件或以编程方式完成配置。 log4rslog 兼容,这对我们来说非常好,因为这意味着我们不必仅仅为了使用 log4rs 就采用新的范例。

如果您想创建一个配置文件来加载,您可以像这样设置 YAML 文件:

yaml 复制代码
# set a refresh rate
refresh_rate: 30 seconds

# appenders
appenders:
# this appender will append to the console
  stdout:
    kind: console
# this appender will append to a log file
  requests:
    kind: file
    path: "log/requests.log"
# this is a simple string encoder - this will be explained below
    encoder:
      pattern: "{d} - {m}{n}"

# the appender that prints to stdout will only print if the log level of the message is warn or above
root:
  level: warn
  appenders:
    - stdout

# set minimum logging level - log messages below the mnimum won't be recorded
loggers:
  app::backend::db:
    level: info

  app::requests:
    level: info
    appenders:
      - requests
    additive: false

编码器encoder可以使用 JSON 编码或模式编码。在这里,我们决定使用模式编码,它与原始 log4j 模式类似,但使用 Rust 字符串格式 - 您可以在此处查看有关如何格式化编码器模式的更多信息。

然后可以在设置程序时初始化它,如下所示:

rust 复制代码
log4rs::init_file("log4rs.yml", Default::default()).unwrap();

还可以以编程方式创建配置:

rust 复制代码
use log::LevelFilter;
use log4rs::append::console::ConsoleAppender;
use log4rs::append::file::FileAppender;
use log4rs::encode::pattern::PatternEncoder;
use log4rs::config::{Appender, Config, Logger, Root};

fn main() {
// set up ConsoleAppender to allow appending logs to the console (stdout)
    let stdout = ConsoleAppender::builder().build();

// set up FileAppender to allow appending logs to a log file
    let requests = FileAppender::builder()
        .encoder(Box::new(PatternEncoder::new("{d} - {m}{n}")))
        .build("log/requests.log")
        .unwrap();

    let config = Config::builder()
        .appender(Appender::builder().build("stdout", Box::new(stdout)))
        .appender(Appender::builder().build("requests", Box::new(requests)))
        .logger(Logger::builder().build("app::backend::db", LevelFilter::Info))
        .logger(Logger::builder()
            .appender("requests")
            .additive(false)
            .build("app::requests", LevelFilter::Info))
        .build(Root::builder().appender("stdout").build(LevelFilter::Warn))
        .unwrap();

    let handle = log4rs::init_config(config).unwrap();

    // use handle to change logger configuration at runtime
}

还可以使用 log4rs 自动归档日志,这非常棒!对于大多数(如果不是所有)其他记录器包,此功能必须由您自己手动实现,因此将此功能内置到记录器本身中会带来巨大的便利。我们可以通过将以下内容添加到 YAML 配置文件(在"appenders"下)来开始设置它:

yaml 复制代码
rolling_appender:
 kind: rolling_file
 path: log/foo.log
 append: true
 encoder:
   kind: pattern
   pattern: "{d} - {m}{n}"
 policy:
   kind: compound
   trigger:
     kind: size
     limit: 10 mb
 # upon reaching the max log size, the file simply gets deleted on successful roll
   roller:
     kind: delete

现在我们有一个策略,写入主日志文件,然后在主日志文件达到 10 MB 的日志时将其附加到存档日志文件,然后删除当前活动的日志文件以准备接收更多日志。

正如您所看到的, log4rs 是一个极其通用的 crate,它可以与前面提到的 log crate 配合使用,提供有关 Rust 日志的强大功能,如果您是来自像 Java 这样的语言,您已经了解其心智模型,只是想了解如何在 Rust 中进行日志记录。然而,作为交换,你必须学习如何设置记录器,与其他记录箱相比,设置本身相当复杂,所以请记住这一点。

Summary: 概括:

  • 多功能的大型一体式crate
  • 需要大量的样板代码或配置文件
  • 轻松设置您自己的文件附加日志出口服务
  • 适配 log crate

tracing

tracing是一个自称为"用于检测 Rust 程序以收集结构化、基于事件的诊断信息的框架"的包,需要使用其对应的记录器 tracing-subscriber 或实现 tracing::Subscriber

tracing 使用"跨度(spans)"的概念,用于记录程序的执行流程。事件可以发生在跨度(spans)内部或外部,也可以类似于非结构化日志记录(即,仅以任何方式记录事件)使用,但也可以表示跨度(spans)内的时间点。见下文:

rust 复制代码
use tracing::Level;

// records an event outside of any span context:
tracing::event!(Level::DEBUG, "something happened");

// create the span while entering it
let span = tracing::span!(Level::INFO"my_span").entered();

// records an event within "my_span".
tracing::event!(Level::DEBUG, "something happened inside my_span");

跨度(spans)可以形成树结构,整个子树由其子级表示 - 因此,父级 Span 的持续时间将始终与其寿命最长的子级 Span 一样长(如果不是更长的话)。

因为所有这些可能有点过多, tracing 还包含了其他日志外观库中用于日志记录的常规宏 - 即 info!error!debug!warn!trace! 。每个宏都有一个跨度(spans)版本 - 但如果您来自 log 并且想要尝试 tracing 而不会迷失在尝试确保一切正常的复杂性中很快,tracing就为您提供了支持。

rust 复制代码
use tracing;

tracing::debug!("Looks just like the log crate!");

tracing::info_span!("a more convenient version of creating spans!");

Tracing-subscriber 是一个日志 create,旨在与 tracing 一起使用,让您定义一个实现 tracing 中的 Subscriber 特征的记录器。

您可以启动一个采用 RUST_LOG 环境变量的订阅者,如下所示:

rust 复制代码
tracing_subscriber::registry()
    .with(fmt::layer())
    .with(EnvFilter::from_default_env())
    .init();

还可以以编程方式应用硬编码过滤器:

rust 复制代码
use tracing_subscriber::filter::{EnvFilter, LevelFilter};

let my_filter = EnvFilter::builder()
    .with_default_directive(LevelFilter::ERROR.into())
    .from_env_lossy();

tracing_subscriber::registry()
    .with(fmt::layer())
    .with(filter)
    .init();

还可以将过滤器分层!如果想要同时拥有多个订阅者的效果,这非常有用。

如果需要将日志导出到某个地方,还有tracing_appender crate。需要使用 .with_writer() 方法将其添加到您的跟踪订阅者中,如下所示:

rust 复制代码
// create a file appender that rotates hourly
let file_appender = tracing_appender::rolling::hourly("/some/directory", "prefix.log");
// make the file appender non-blocking
// the guard exists to make sure buffered logs get flushed to output
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);

// add the file appender to your tracing subscriber
tracing_subscriber::fmt()
    .with_writer(non_blocking)
    .init();

non_blocking 编写器是使用实现 std::io::Write 的类型构建的 - 因此,如果想实现自己的实现 std::io::Write 的东西(假设想要一个日志记录表达自动将您的所有内容导出到 BetterStack 或 Datadog) - 想尝试一下。见下文:

rust 复制代码
use std::io::Error;

struct TestWriter;

impl std::io::Write for TestWriter {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        let buf_len = buf.len();
        println!("{:?}", buf);
        Ok(buf_len)
    }

    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }
}

let (non_blocking, _guard) = tracing_appender::non_blocking(TestWriter);
tracing_subscriber::fmt()
    .with_writer(non_blocking)
    .init();

正如您所看到的, tracing 系列 crate 提供了强大的功能,并且对于任何 Web 应用程序来说都足够强大,并且由 Tokio 团队维护,因此肯定会受到支持许久。但是,使用它需要了解 tracing 的工作原理,因为它使用其他日志crate中未使用的概念 - 因此,如果您出于某种原因需要从该crate迁移,并且您将被锁定,那么您将被锁定重新使用跨度span。但是,如果您问自己"Rust 中最好的日志crate是什么",那么就这个crate系列的强大功能而言,您选择 tracing crate不会出错。

Summary: 概括:

  • 需要了解一些关于跨度span等的知识才能充分利用
  • 由 Tokio 团队维护,因此很可能会看到 LTS
  • 拆分crate 意味着不必安装不打算使用的东西
  • 由于其构建方式,可能是列表中最复杂的系统

Conclusions 结论

谢谢阅读!现在我们已经结束了,我希望您对 Rust 登录有更好的了解。日志箱如此之多,很难确定您应该使用哪一个,但希望本文能够让您清楚地了解哪个crate是最适合您的用例的 Rust 日志库。


Logging in Rust - How to Get Started

相关推荐
跟着珅聪学java40 分钟前
spring boot +Elment UI 上传文件教程
java·spring boot·后端·ui·elementui·vue
徐小黑ACG2 小时前
GO语言 使用protobuf
开发语言·后端·golang·protobuf
叠叠乐4 小时前
rust Send Sync 以及对象安全和对象不安全
开发语言·安全·rust
战族狼魂4 小时前
CSGO 皮肤交易平台后端 (Spring Boot) 代码结构与示例
java·spring boot·后端
niandb5 小时前
The Rust Programming Language 学习 (九)
windows·rust
杉之6 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
hycccccch6 小时前
Canal+RabbitMQ实现MySQL数据增量同步
java·数据库·后端·rabbitmq
bobz9657 小时前
k8s 怎么提供虚拟机更好
后端
bobz9657 小时前
nova compute 如何创建 ovs 端口
后端
用键盘当武器的秋刀鱼8 小时前
springBoot统一响应类型3.5.1版本
java·spring boot·后端