iT邦幫忙

2023 iThome 鐵人賽

DAY 17
0
Software Development

Rust Web API 從零開始系列 第 17

Day17 - 發佈之前,先加個Log吧(2)

  • 分享至 

  • xImage
  •  

介紹完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了

Registry

不知道大家是不是跟我一樣第一次看使用範例的時候完全搞不清楚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逐層疊加上去,最後在初始化時一次設定為全域變數。


上一篇
Day16 - 發佈之前,先加個Log吧(1)
下一篇
Day18 - 佈署到Fly.io
系列文
Rust Web API 從零開始30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言