在 Rust 自定義日誌中使用 tracing 和 tracing-subscriber
大家好,我是螃蟹哥。
當談到 Rust 中的日誌記錄和分析時,我有一種直覺,tracing
[1] 就是未來的方式。rust 編譯器使用 tracing[2]。Tokio 正在用它做很酷的事情 [3]。甚至我們使用的 GraphQL 庫也與跟蹤集成 [4]。
因爲這似乎是在生態系統的發展方向,我想要在 Zenlist[5] 中使用 tracing[6] 替代基於日誌的 log[7]。但無論出於何種原因,tracing[8] 的 JSON 記錄器都感覺不太對勁。
你知道這意味着什麼:是時候建立我們自己的了!如果你想學習如何構建自己的 logger,想了解更多關於跟蹤層的知識,或者只是想加入,讓我們一起踏上這段旅程。
在本文中,我們將瞭解如何構建一個打印出_即時_事件的記錄器。在下篇文章,我們一起看看如何能夠利用 tracing
[9] 的 period-of-time 的跨度,以提供更好的結構化的日誌。
01 基礎
讓我們擺脫無聊的東西。cargo new --bin
諸如此類。
首先,我們需要引入一些依賴項。tracing
[10] 是生態系統的核心。tracing-subscriber
[11] 是負責捕獲跨度和事件並用它們做一些事情的 crates。由於我們的自定義日誌將輸出結構化的 JSON,讓我們也引入 serde_json
[12]。
[dependencies]
serde_json = "1"
tracing = "0.1"
tracing-subscriber = "0.3"
這只是 Cargo.toml[13],所有的代碼都在 github.com/bryanburgers/tracing-blog-post[14] 裏。
在我們的 中main.rs
,我們將從一個非常基本的程序開始:設置自定義日誌,然後使用 info!
來記錄一些簡單的事情。
use tracing::info;
use tracing_subscriber::prelude::*;
mod custom_layer;
use custom_layer::CustomLayer;
fn main() {
// Set up how `tracing-subscriber` will deal with tracing data.
tracing_subscriber::registry().with(CustomLayer).init();
// Log something simple. In `tracing` parlance, this creates an "event".
info!(a_bool = true, answer = 42, message = "first example");
}
examples/figure_0/main.rs[15]
有了這個,讓我們直接進入並創建我們的自定義 logger。tracing-subscriber
[16] 公開了一個 trait,調用 Layer
[17] 處理跟蹤跨度和事件的功能。
use tracing_subscriber::Layer;
pub struct CustomLayer;
impl<S> Layer<S> for CustomLayer where S: tracing::Subscriber {}
examples/figure_0/custom_layer.rs[18]
如果我們運行它,發現什麼也沒有。這並不奇怪:我們還沒有實現任何 Layer
[19] 的方法。
02 捕捉事件
在 tracing 中,任何時候 info!
,error!
或其他宏 [20] 被調用時,一個 Event[21] 被創建。
Layer [22] 接口有一個 on_event
[23] 方法。我們能做些什麼。
impl<S> Layer<S> for CustomLayer
where
S: tracing::Subscriber,
{
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
println!("Got event!");
println!(" level={:?}", event.metadata().level());
println!(" target={:?}", event.metadata().target());
println!(" name={:?}", event.metadata().name());
for field in event.fields() {
println!(" field={}", field.name());
}
}
}
examples/figure_1/custom_layer.rs[24]
根據 API,我們可以獲得級別、目標(似乎是創建事件的模塊路徑 [25])和事件的名稱。以及字段的名稱。
運行它,我們實際上得到了一些有用的東西!好吧,這不是 JSON。不要假裝你以前從未進行過println!
探索或調試。
Got event!
level=Level(Info)
target="figure_1"
field=a_bool
field=answer
field=message
cargo run --example figure_1
這是一個很好的開始。但是看看 Event::fields()
[26],似乎沒有辦法獲取字段的值。
那麼—— 如果我們無法獲得我們想要記錄的值,我們應該如何創建一個記錄器?
03 訪問者
tracing
[27] 選擇永遠不永久存儲它在事件和跨度中給出的數據。如果我們想要獲取數據,我們需要自己存儲它。
我們這樣做的方式是通過訪問者模式。所以看起來我們需要創建一個 Visit
[28] 實現來從事件中獲取值。讓我們再做一些println!
探索。
Visit
[29] 會爲tracing
可以處理的每一種類型公開一個record_*
方法。所以我們將它們全部連接起來打印出名稱和值。
struct PrintlnVisitor;
impl tracing::field::Visit for PrintlnVisitor {
fn record_f64(&mut self, field: &tracing::field::Field, value: f64) {
println!(" field={} value={}", field.name(), value)
}
fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
println!(" field={} value={}", field.name(), value)
}
fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
println!(" field={} value={}", field.name(), value)
}
fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {
println!(" field={} value={}", field.name(), value)
}
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
println!(" field={} value={}", field.name(), value)
}
fn record_error(
&mut self,
field: &tracing::field::Field,
value: &(dyn std::error::Error + 'static),
) {
println!(" field={} value={}", field.name(), value)
}
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
println!(" field={} value={:?}", field.name(), value)
}
}
examples/figure_2/custom_layer.rs[30]
在我們的事件處理程序中,我們使用這個新訪問者event.record(&mut visitor)
來訪問所有值。
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
println!("Got event!");
println!(" level={:?}", event.metadata().level());
println!(" target={:?}", event.metadata().target());
println!(" name={:?}", event.metadata().name());
let mut visitor = PrintlnVisitor;
event.record(&mut visitor);
}
examples/figure_2/custom_layer.rs[31]
看看當我們運行程序時會發生什麼。
Got event!
level=Level(Info)
target="figure_2"
field=a_bool value=true
field=answer value=42
field=message value=first example
cargo run --example figure_2
04 我們最終可以構建一個 JSON 記錄器嗎?
經過println!
探索。我們有足夠的東西讓它看起來像一個真正的 JSON 記錄器。
讓我們創建一個可以構建 JSON 對象的一 個JsonVisitor
,而不是一個PrintlnVisitor
。
struct JsonVisitor<'a>(&'a mut BTreeMap<String, serde_json::Value>);
impl<'a> tracing::field::Visit for JsonVisitor<'a> {
fn record_f64(&mut self, field: &tracing::field::Field, value: f64) {
self.0
.insert(field.name().to_string(), serde_json::json!(value));
}
// ...
}
examples/figure_3/custom_layer.rs[32]
在我們的事件處理程序中,讓我們構建一個 JSON 對象並在最後打印它。
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
// Covert the values into a JSON object
let mut fields = BTreeMap::new();
let mut visitor = JsonVisitor(&mut fields);
event.record(&mut visitor);
// Output the event in JSON
let output = serde_json::json!({
"target": event.metadata().target(),
"name": event.metadata().name(),
"level": format!("{:?}", event.metadata().level()),
"fields": fields,
});
println!("{}", serde_json::to_string_pretty(&output).unwrap());
}
examples/figure_3/custom_layer.rs[33]
運行它...
{
"fields": {
"a_bool": true,
"answer": 42,
"message": "first example"
},
"level": "Level(Info)",
"name": "event examples/figure_3/main.rs:10",
"target": "figure_3"
}
cargo run --example figure_3
我們終於得到它了。我們有一個 Layer
[34] 可以添加到 tracing-subscriber
[35] 讓它來記錄結構化事件。在下篇文章,我們將看到我們如何能夠利用 tracing
[36] 的_時期 - 時間_的跨度,給我們的日誌提供更豐富的環境信息。
原文鏈接:https://burgers.io/custom-logging-in-rust-using-tracing
參考資料
[1]
tracing
: https://docs.rs/tracing/0.1
[2]
rust 編譯器使用 tracing: https://github.com/rust-lang/rust/pull/74726
[3]
用它做很酷的事情: https://tokio.rs/blog/2021-09-console-dev-diary-1
[4]
與跟蹤集成: https://docs.rs/async-graphql/2.10.3/async_graphql/extensions/struct.Tracing.html
[5]
Zenlist: https://zenlist.com/
[6]
tracing: https://docs.rs/tracing/0.1
[7]
log: https://docs.rs/log
[8]
tracing: https://docs.rs/tracing/0.1
[9]
tracing
: https://docs.rs/tracing/0.1
[10]
tracing
: https://docs.rs/tracing/0.1
[11]
tracing-subscriber
: https://docs.rs/tracing-subscriber/0.3
[12]
serde_json
: https://docs.rs/serde_json/1
[13]
Cargo.toml: https://github.com/bryanburgers/tracing-blog-post/blob/main/Cargo.toml
[14]
github.com/bryanburgers/tracing-blog-post: https://github.com/bryanburgers/tracing-blog-post
[15]
examples/figure_0/main.rs: https://github.com/bryanburgers/tracing-blog-post/blob/main/examples/figure_0/main.rs
[16]
tracing-subscriber
: https://docs.rs/tracing-subscriber/0.3
[17]
Layer
: https://docs.rs/tracing-subscriber/0.3/tracing_subscriber/layer/trait.Layer.html
[18]
examples/figure_0/custom_layer.rs: https://github.com/bryanburgers/tracing-blog-post/blob/main/examples/figure_0/custom_layer.rs
[19]
Layer
: https://docs.rs/tracing-subscriber/0.3/tracing_subscriber/layer/trait.Layer.html
[20]
或其他宏: https://docs.rs/tracing/0.1.29/tracing/index.html#using-the-macros
[21]
Event: https://docs.rs/tracing/0.1/tracing/event/struct.Event.html
[22]
Layer : https://docs.rs/tracing-subscriber/0.3/tracing_subscriber/layer/trait.Layer.html
[23]
on_event
: https://docs.rs/tracing-subscriber/0.3/tracing_subscriber/layer/trait.Layer.html#method.on_event
[24]
examples/figure_1/custom_layer.rs: https://github.com/bryanburgers/tracing-blog-post/blob/main/examples/figure_1/custom_layer.rs
[25]
是創建事件的模塊路徑: https://docs.rs/tracing/0.1/tracing/struct.Metadata.html#method.target
[26]
Event::fields()
: https://docs.rs/tracing-core/0.1/tracing_core/event/struct.Event.html#method.fields
[27]
tracing
: https://docs.rs/tracing/0.1
[28]
Visit
: https://docs.rs/tracing/0.1/tracing/field/trait.Visit.html
[29]
Visit
: https://docs.rs/tracing/0.1/tracing/field/trait.Visit.html
[30]
examples/figure_2/custom_layer.rs: https://github.com/bryanburgers/tracing-blog-post/blob/main/examples/figure_2/custom_layer.rs
[31]
examples/figure_2/custom_layer.rs: https://github.com/bryanburgers/tracing-blog-post/blob/main/examples/figure_2/custom_layer.rs
[32]
examples/figure_3/custom_layer.rs: https://github.com/bryanburgers/tracing-blog-post/blob/main/examples/figure_3/custom_layer.rs
[33]
examples/figure_3/custom_layer.rs: https://github.com/bryanburgers/tracing-blog-post/blob/main/examples/figure_3/custom_layer.rs
[34]
Layer
: https://docs.rs/tracing-subscriber/0.3/tracing_subscriber/layer/trait.Layer.html
[35]
tracing-subscriber
: https://docs.rs/tracing-subscriber/0.3
[36]
tracing
: https://docs.rs/tracing/0.1
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/hEBFIY-I0toUGxh45tTEPQ