在 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