介紹完Tracing後,就要來看看怎麼加入專案裡面,我們簡單的把套件設定加上去。
首先可以先看一下telemetry.rs這個檔案,這裡面設定了subscriber
:
//// telemetry.rs
pub fn get_subscriber(setting: &Settings)
-> impl SubscriberInitExt {
let filter = get_filter(&setting.application.logging_levels);
//// 產生SubscriberInitExt的物件
tracing_subscriber::registry()
//// 附加上log層級設定
.with(filter)
//// 設定log的為json的格式
.with(tracing_subscriber::fmt::layer().json())
}
//// 設定log的預設層級
fn get_filter(modules: &[String]) -> EnvFilter {
let filter_settings = modules.join(",");
EnvFilter::builder()
//// 預設情況下只收集info層級以上的log
.with_default_directive(LevelFilter::INFO.into())
//// 讀取設定檔中的層級設定
.parse_lossy(filter_settings)
}
接下來還要在設定Span
,但是在每個handler中設定太麻煩,官方提供了instrument
巨集來方便追蹤log:
//// helth_check.rs
//// 加上#[instrument]就可以得到追蹤功能
#[instrument]
pub async fn health_check() -> Result<StatusCode,StatusCode> {
StatusCode::OK
}
另外針對HTTP請求,tracing也提供了給tower
用的中介軟體:
let router = Router::new()
.route("/health_check", get(health_check))
.route("/subscriptions", post(subscribe))
.merge(SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", ApiDoc::openapi()))
//// 加上Tracing的中介軟體
.layer(TraceLayer::new_for_http())
.with_state(AppState {
database,
});
這樣就可以針對http請求產生更詳細的log了
不知道大家是不是跟我一樣第一次看使用範例的時候完全搞不清楚tracing
到底是怎麼一回事,接下來讓我們仔細研究一下,首先看一下telemetry.rs
:
//// telemetry.rs
pub fn get_subscriber(setting: &Settings)
-> impl SubscriberInitExt {
let filter = get_filter(&setting.application.logging_levels);
tracing_subscriber::registry()
.with(filter)
.with(tracing_subscriber::fmt::layer().json())
}
把焦點放在第6行的tracing_subscriber::registry()
,這一行的目的是產生一個Registry
,他的用途就是來放置subscriber的設定,依照文件上的介紹:
pub struct Registry { /* private fields */ }
根據原始碼的實做,內部用物件池的方式存放了Span
,接下來看一下with
,這個方法由CollectExt
trait提供:
pub trait CollectExt: Collect + crate::sealed::Sealed {
/// Wraps `self` with the provided `subscriber`.
fn with<S>(self, subscriber: S) -> Layered<S, Self>
where
S: Subscribe<Self>,
Self: Sized,
{
subscriber.with_collector(self)
}
}
有趣的是CollectExt
是對Collect
的全面實做,或者說是trait的繼承,rust中的資料無法繼承型態,但是trait卻可以繼承,這邊大致上就是把subscriber
的設定一層一層疊加上去。我們留意一下get_subscriber
方法的回傳值是實做了SubscriberInitExt
的trait物件,接下來回來看一下main.rs
//// main.rs
#[tokio::main]
async fn main() {
let config = get_configuration().expect("Failed to read configuration");
get_subscriber(&config).init();
Application::build(&config).await.unwrap().run().await;
}
第六行在取得SubscriberInitExt
後調用了init()
,那麼init
內部又做了什麼呢?
pub trait SubscriberInitExt
where
Self: Into<Dispatch>,
{
fn try_init(self) -> Result<(), TryInitError> {
dispatch::set_global_default(self.into()).map_err(TryInitError::new)?;
/// 省略
Ok(())
}
fn init(self) {
self.try_init()
.expect("failed to set global default subscriber")
}
}
這邊看到其實就是把Subscriber設定為一個全域的變數,這樣就可以跨越整個專案接收全部的Span
紀錄。
當我們初始化Tracing後就可以方便的蒐集log,但我自己一開始很困惑tracing到底透過什麼東西蒐集log,看起來它應該是一個隱藏在背景的程序。事實上,Tracing允許多個訂閱者接收log,每個訂閱者都可以設定不同的行為,再靠CollectExt
逐層疊加上去,最後在初始化時一次設定為全域變數。