Tracing 入门

tracing crate 是一个用于检测 Rust 程序的框架,以收集结构化的、基于事件的诊断信息。

在像 Tokio 这样的异步系统中,解释传统的日志消息通常非常具有挑战性。由于单个任务在同一线程上进行多路复用,因此关联的事件和日志行会混合在一起,使得追踪逻辑流程变得困难。tracing 扩展了日志风格的诊断,允许库和应用程序记录结构化事件,并包含关于时序性因果关系的附加信息 —— 与日志消息不同,tracing 中的 Span 具有开始和结束时间,可以由执行流进入和退出,并且可以存在于相似 span 的嵌套树中。为了表示在单个时刻发生的事情,tracing 提供了互补的概念 事件SpanEvent 都是结构化的,能够记录类型化数据以及文本消息。

你可以使用 tracing 来

设置

要开始使用,请添加 tracingtracing-subscriber 作为依赖项

[dependencies]
tracing = "0.1"
tracing-subscriber = "0.3"

tracing crate 提供了我们将用来发送追踪的 API。 tracing-subscriber crate 提供了一些基本工具,用于将这些追踪转发到外部监听器(例如,stdout)。

订阅追踪

如果你正在编写可执行文件(而不是库),则需要注册一个 tracing 订阅器。订阅器是处理由你的应用程序及其依赖项发出的追踪的类型,并且可以执行诸如计算指标、监控错误以及将追踪重新发送到外部世界(例如,journaldstdout 或一个 open-telemetry 守护程序)之类的任务。

在大多数情况下,你应该在 main 函数中尽早注册你的 tracing 订阅器。例如,由 FmtSubscriber 类型(由 tracing-subscriber 提供)将格式化的追踪和事件打印到 stdout,可以像这样注册

#[tokio::main]
pub async fn main() -> mini_redis::Result<()> {
    // construct a subscriber that prints formatted traces to stdout
    let subscriber = tracing_subscriber::FmtSubscriber::new();
    // use that subscriber to process traces emitted after this point
    tracing::subscriber::set_global_default(subscriber)?;
    ...
}

如果你现在运行你的应用程序,你可能会看到 Tokio 发出的一些追踪事件,但是你需要修改你自己的应用程序以发出追踪,才能充分利用 tracing

订阅器配置

在上面的例子中,我们使用其默认配置配置了 FmtSubscriber。然而,tracing-subscriber 也提供了许多方法来配置 FmtSubscriber 的行为,例如自定义输出格式,在日志中包含附加信息(如线程 ID 或源代码位置),以及将日志写入 stdout 以外的其他位置。

例如

// Start configuring a `fmt` subscriber
let subscriber = tracing_subscriber::fmt()
    // Use a more compact, abbreviated log format
    .compact()
    // Display source code file paths
    .with_file(true)
    // Display source code line numbers
    .with_line_number(true)
    // Display the thread ID an event was recorded on
    .with_thread_ids(true)
    // Don't display the event's target (module path)
    .with_target(false)
    // Build the subscriber
    .finish();

有关可用配置选项的详细信息,请参阅 tracing_subscriber::fmt 文档

除了来自 tracing-subscriberFmtSubscriber 类型之外,其他 Subscriber 也可以实现它们自己记录 tracing 数据的方式。这包括替代的输出格式、分析和聚合,以及与其他系统(如分布式追踪或日志聚合服务)的集成。许多 crate 提供了可能感兴趣的附加 Subscriber 实现。有关附加 Subscriber 实现的(不完整)列表,请参阅 此处

最后,在某些情况下,将多种不同的记录追踪的方式组合在一起以构建一个实现多种行为的单个 Subscriber 可能很有用。为此,tracing-subscriber crate 提供了一个 Layer trait,它表示一个可以与其他 Layer 组合在一起以形成 Subscriber 的组件。有关使用 Layer 的详细信息,请参阅 此处

发送 Span 和事件

发送 span 最简单的方法是使用由 tracing 提供的 instrument proc-macro 注解,它会重写函数体,以便在每次调用时发送 span;例如:

#[tracing::instrument]
fn trace_me(a: u32, b: u32) -> u32 {
    a + b
}

每次调用 trace_me 都会发送一个 tracing Span,它

  1. 具有 info 的详细程度 级别 (“中间地带”详细程度),
  2. 被命名为 trace_me
  3. 具有字段 ab,其值是 trace_me 的参数

instrument 属性是高度可配置的;例如,要追踪 mini-redis-server 中处理每个连接的方法

use tracing::instrument;

impl Handler {
    /// Process a single connection.
    #[instrument(
        name = "Handler::run",
        skip(self),
        fields(
            // `%` serializes the peer IP addr with `Display`
            peer_addr = %self.connection.peer_addr().unwrap()
        ),
    )]
    async fn run(&mut self) -> mini_redis::Result<()> {
        ...
    }
}

mini-redis-server 现在将为每个传入连接发送一个 tracing Span,它

  1. 具有 info 的详细程度 级别 (“中间地带”详细程度),
  2. 被命名为 Handler::run
  3. 具有一些与之关联的结构化数据。
    • fields(...) 指示发送的 span 应该 在名为 peer_addr 的字段中包含连接的 SocketAddrfmt::Display 表示。
    • skip(self) 指示发送的 span 不应该 记录 Handler 的 debug 表示。

你也可以通过调用 span! 宏或其任何分级简写宏(error_span!warn_span!info_span!debug_span!trace_span!)手动构造 Span

要发送事件,请调用 event! 宏或其任何分级简写宏(error!warn!info!debug!trace!)。例如,要记录客户端发送了格式错误的命令

// Convert the redis frame into a command struct. This returns an
// error if the frame is not a valid redis command.
let cmd = match Command::from_frame(frame) {
    Ok(cmd) => cmd,
    Err(cause) => {
        // The frame was malformed and could not be parsed. This is
        // probably indicative of an issue with the client (as opposed
        // to our server), so we (1) emit a warning...
        //
        // The syntax here is a shorthand provided by the `tracing`
        // crate. It can be thought of as similar to:
        //      tracing::warn! {
        //          cause = format!("{}", cause),
        //          "failed to parse command from frame"
        //      };
        // `tracing` provides structured logging, so information is
        // "logged" as key-value pairs.
        tracing::warn! {
            %cause,
            "failed to parse command from frame"
        };
        // ...and (2) respond to the client with the error:
        Command::from_error(cause)
    }
};

如果你运行你的应用程序,你现在将看到为每个它处理的传入连接发出的事件,这些事件都用其 span 的上下文进行了装饰。