在 Rust 中使用 tracing 和 tracing-subscriber 自定義日誌 - 2
在上一篇文章中,我們瞭解瞭如何構建一個自定義 layer,用來記錄事件。在這篇文章中我們介紹 tracing 生態系統的一個重要部分:跨度 (Span)。
基礎
我們要對跨度進行探索,我們需要創建一個外部 span 和一個內部 span。從概念上講,跨度和事件好像嵌套在一起,如:
-
Outer span is entered
-
Inner span is entered
-
Event is created with the inner span as the parent and the outer span as the grandparent
-
Inner span is exited
-
Outer span is exited
讓我們更新 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