在 Rust 中使用 tracing 和 tracing-subscriber 自定義日誌 - 2

在上一篇文章中,我們瞭解瞭如何構建一個自定義 layer,用來記錄事件。在這篇文章中我們介紹 tracing 生態系統的一個重要部分:跨度 (Span)。

基礎

我們要對跨度進行探索,我們需要創建一個外部 span 和一個內部 span。從概念上講,跨度和事件好像嵌套在一起,如:

讓我們更新 main.rs,span.enter() 使用保護模式持續輸入 span,直到超出範圍,然後退出。在 main 中,我們依次退出 inner,然後在函數結束時退出 outer。

 1use tracing::{info, info_span, debug_span};
 2use tracing_subscriber::prelude::*;
 3
 4mod custom_layer;
 5use custom_layer::CustomLayer;
 6
 7fn main() {
 8    tracing_subscriber::registry().with(CustomLayer).init();
 9
10    let outer_span = info_span!("outer"level = 0);
11    let _outer_entered = outer_span.enter();
12
13    let inner_span = debug_span!("inner"level = 1);
14    let _inner_entered = inner_span.enter();
15
16    info!(a_bool = true, answer = 42, message = "first example");
17}

回到事件處理程序中,我們能夠獲得上下文。ctx.event_span(event)將給出事件的父跨度 (如果存在的話)。我們可以使用它,但還有更好的方法:ctx.event_scope(event) 將給出包含所有 span 的迭代器。

要使用 ctx.event_scope(),我們的訂閱者需要實現 lookkupref。

 1impl<S> Layer<S> for CustomLayer
 2where
 3    S: tracing::Subscriber,
 4    // Scary! But there's no need to even understand it. We just need it.
 5    S: for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
 6{
 7    fn on_event(&self, event: &tracing::Event<'_>, ctx: tracing_subscriber::layer::Context<'_, S>) {
 8        // What's the parent span look like?
 9        let parent_span = ctx.event_span(event).unwrap();
10        println!("parent span");
11        println!("  name={}", parent_span.name());
12        println!("  target={}", parent_span.metadata().target());
13
14        println!();
15
16        // What's about all of the spans that are in scope?
17        let scope = ctx.event_scope(event).unwrap();
18        for span in scope.from_root() {
19            println!("an ancestor span");
20            println!("  name={}", span.name());
21            println!("  target={}", span.metadata().target());
22        }
23    }
24}

運行 cargo run,結果如下:

parent span
  name=inner
  target=tracing_log
an ancestor span
  name=outer
  target=tracing_log
an ancestor span
  name=inner
  target=tracing_log

Span 數據在哪裏?

是時候開始探索 Layer 的其他 trait 方法了。具體來說,讓我們看看 new_span,當創建一個新 span 時調用它 (正如你可能從名稱猜到的那樣)。

 1impl<S> Layer<S> for CustomLayer
 2where
 3    S: tracing::Subscriber,
 4    S: for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
 5{
 6    fn new_span(
 7        &self,
 8        attrs: &tracing::span::Attributes<'_>,
 9        id: &tracing::span::Id,
10        ctx: tracing_subscriber::layer::Context<'_, S>,
11    ) {
12        let span = ctx.span(id).unwrap();
13        println!("Got new_span!");
14        println!("  level={:?}", span.metadata().level());
15        println!("  target={:?}", span.metadata().target());
16        println!("  name={:?}", span.metadata().name());
17
18        // Our old friend, `println!` exploration.
19        let mut visitor = PrintlnVisitor;
20        attrs.record(&mut visitor);
21    }
22}

執行 cargo run,結果如下:

Got new_span!
  level=Level(Info)
  target="tracing_log"
  
  field=level value=0
Got new_span!
  level=Level(Debug)
  target="tracing_log"
  
  field=level value=1

這就是我們的數據。現在問題來了:我們只有一次機會訪問 Span 中的數據—在它被創建時。tracing 不會保留它以便我們以後訪問。如果我們稍後在記錄事件時試圖獲取 span 數據就會獲取不到,因爲 span 數據已經不存在了。

自己存儲 Span 數據

我們真正想做的是在看到 span 數據時捕獲它,然後將它存儲在某個地方以便我們以後可以使用它。

雖然 tracing 拒絕存儲數據本身,但 tracing-subscriber 可以非常容易地存儲我們的數據。它稱之爲 “擴展”,我們看到每個 span 都有一種方法將擴展附加到它上面。

我們創建一個新的包裝器類型:

1#[derive(Debug)]
2struct CustomFieldStorage(BTreeMap<String, serde_json::Value>);

每當我們看到一個新的 span 時,我們就構建 JSON 字段對象並將其存儲在擴展數據中。

 1fn new_span(
 2    &self,
 3    attrs: &tracing::span::Attributes<'_>,
 4    id: &tracing::span::Id,
 5    ctx: tracing_subscriber::layer::Context<'_, S>,
 6) {
 7    // Build our json object from the field values like we have been
 8    let mut fields = BTreeMap::new();
 9    let mut visitor = JsonVisitor(&mut fields);
10    attrs.record(&mut visitor);
11
12    // And stuff it in our newtype.
13    let storage = CustomFieldStorage(fields);
14
15    // Get a reference to the internal span data
16    let span = ctx.span(id).unwrap();
17    // Get the special place where tracing stores custom data
18    let mut extensions = span.extensions_mut();
19    // And store our data
20    extensions.insert::<CustomFieldStorage>(storage);
21}

現在,只要有一個 span(比如在 on_event 方法中),我們就可以獲得所存儲的數據。

1// Get the special place where tracing stores custom data
2let extensions = span.extensions();
3// And get the custom data we stored out of it
4let storage: &CustomFieldStorage = extensions.get::<CustomFieldStorage>().unwrap();
5// And because `CustomFieldStorage` is just a newtype around our JSON object, we
6// can get the JSON object that we stored.
7let field_data: &BTreeMap<String, serde_json::Value> = &storage.0;

JSON 日誌記錄器

我們將目前學到的知識與我們已有的 JSON 日誌記錄器混合在一起,我們應該能夠將上下文的 Span 數據添加到日誌記錄器中。

 1fn on_event(&self, event: &tracing::Event<'_>, ctx: tracing_subscriber::layer::Context<'_, S>) {
 2    // All of the span context
 3    let scope = ctx.event_scope(event).unwrap();
 4    let mut spans = vec![];
 5    for span in scope.from_root() {
 6        let extensions = span.extensions();
 7        let storage = extensions.get::<CustomFieldStorage>().unwrap();
 8        let field_data: &BTreeMap<String, serde_json::Value> = &storage.0;
 9        spans.push(serde_json::json!({
10            "target": span.metadata().target(),
11            "name": span.name(),
12            "level": format!("{:?}", span.metadata().level()),
13            "fields": field_data,
14        }));
15    }
16
17    // The fields of the event
18    let mut fields = BTreeMap::new();
19    let mut visitor = JsonVisitor(&mut fields);
20    event.record(&mut visitor);
21
22    // And create our output
23    let output = serde_json::json!({
24        "target": event.metadata().target(),
25        "name": event.metadata().name(),
26        "level": format!("{:?}", event.metadata().level()),
27        "fields": fields,
28        "spans": spans,
29    });
30    println!("{}", serde_json::to_string_pretty(&output).unwrap());
31}
 1fn on_record(
 2    &self,
 3    id: &tracing::span::Id,
 4    values: &tracing::span::Record<'_>,
 5    ctx: tracing_subscriber::layer::Context<'_, S>,
 6) {
 7    // Get the span whose data is being recorded
 8    let span = ctx.span(id).unwrap();
 9
10    // Get a mutable reference to the data we created in new_span
11    let mut extensions_mut = span.extensions_mut();
12    let custom_field_storage: &mut CustomFieldStorage =
13        extensions_mut.get_mut::<CustomFieldStorage>().unwrap();
14    let json_data: &mut BTreeMap<String, serde_json::Value> = &mut custom_field_storage.0;
15
16    // And add to using our old friend the visitor!
17    let mut visitor = JsonVisitor(json_data);
18    values.record(&mut visitor);
19}

執行 cargo run,結果如下:

{
  "fields": {
    "a_bool": true,
    "answer": 42,
    "message": "first example"
  },
  "level": "Level(Info)",
  "name": "event src/main.rs:16",
  "spans": [
    {
      "fields": {
        "level": 0
      },
      "level": "Level(Info)",
      "name": "outer",
      "target": "tracing_log"
    },
    {
      "fields": {
        "level": 1
      },
      "level": "Level(Debug)",
      "name": "inner",
      "target": "tracing_log"
    }
  ],
  "target": "tracing_log_2"
}

現在,在兩篇文章之後,我們有了一個功能完整的自定義 JSON 日誌記錄器。

本文翻譯自:

https://burgers.io/custom-logging-in-rust-using-tracing-part-2

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Pzt1n8u5CVVOg4_nkulb4w