Tracing 入门
tracing
crate 是一个用于检测 Rust 程序的框架,以收集结构化的、基于事件的诊断信息。
在像 Tokio 这样的异步系统中,解释传统的日志消息通常非常具有挑战性。由于单个任务在同一线程上进行多路复用,因此关联的事件和日志行会混合在一起,使得追踪逻辑流程变得困难。tracing
扩展了日志风格的诊断,允许库和应用程序记录结构化事件,并包含关于时序性和因果关系的附加信息 —— 与日志消息不同,tracing
中的 Span
具有开始和结束时间,可以由执行流进入和退出,并且可以存在于相似 span 的嵌套树中。为了表示在单个时刻发生的事情,tracing
提供了互补的概念 事件。 Span
和 Event
都是结构化的,能够记录类型化数据以及文本消息。
你可以使用 tracing 来
- 将分布式追踪发送到 OpenTelemetry 收集器
- 使用 Tokio Console 调试你的应用程序
- 日志输出到
stdout
,日志文件 或journald
- 分析 你的应用程序在哪些地方花费时间
设置
要开始使用,请添加 tracing
和 tracing-subscriber
作为依赖项
[dependencies]
tracing = "0.1"
tracing-subscriber = "0.3"
tracing
crate 提供了我们将用来发送追踪的 API。 tracing-subscriber
crate 提供了一些基本工具,用于将这些追踪转发到外部监听器(例如,stdout
)。
订阅追踪
如果你正在编写可执行文件(而不是库),则需要注册一个 tracing 订阅器。订阅器是处理由你的应用程序及其依赖项发出的追踪的类型,并且可以执行诸如计算指标、监控错误以及将追踪重新发送到外部世界(例如,journald
、stdout
或一个 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-subscriber
的 FmtSubscriber
类型之外,其他 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,它
- 具有
info
的详细程度 级别 (“中间地带”详细程度), - 被命名为
trace_me
, - 具有字段
a
和b
,其值是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,它
- 具有
info
的详细程度 级别 (“中间地带”详细程度), - 被命名为
Handler::run
, - 具有一些与之关联的结构化数据。
fields(...)
指示发送的 span 应该 在名为peer_addr
的字段中包含连接的SocketAddr
的fmt::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 的上下文进行了装饰。