tracing簡要說明

  • tracing可以記錄結構化的日誌,可以按區間(span)記錄日誌,例如一個函數可以作為一個區間單元,也可以自行指定何時進入(span.enter())區間單元
  • tracing有TRACE DEBUG INFO WARN ERROR共5個日誌級別,其中TRACE是最詳細的級別
  • tracing crate提供了最基本的核心功能:
    • span:區間單元,具有區間的起始時間和區間的結束位置,是一個有時間跨度的區間
    • span!()創建一個span區間,span.enter()表示進入span區間,drop span的時候退出span區間
    • event: 每一次事件,都是一條記錄,也可以看作是一條日誌
    • event!()記錄某個指定日誌級別的日誌信息,event!(Level::INFO, "something happened!");
    • trace!() debug!() info!() warn!() error!(),是event!()的語法糖,可以無需再指定日誌級別
  • 記錄日誌時,可以記錄結構化數據,以key=value的方式提供和記錄。例如:trace!(num = 33, "hello world"),將記錄為"num = 33 hello worl"。支持哪些格式,參考https://docs.rs/tracing/latest/tracing/index.html#recording-fields
  • tracing crate自身不會記錄日誌,它只是發出event!()或類似宏記錄的日誌,發出日誌後,還需要通過tracing subscriber來收集
  • 在可執行程序(例如main函數)中,需要初始化subscriber,而在其它地方(如庫或函數中),只需使用那些宏來發出日誌即可。發日誌和收集記錄日誌分開,使得日誌的處理邏輯非常簡潔
  • 初始化subscriber的時候,可篩選收集到的日誌(例如指定過濾哪些級別的日誌)、格式化收集到的日誌(例如修改時間格式)、指定日誌的輸出位置,等等
  • 默認清空下,subscribe的默認輸出位置是標準輸出,但可以在初始化時改變目標位置。如果需要寫入文件,可使用tracing_appender crate

示例

Cargo.toml:

[package]
name = "log"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
chrono = "0.4.19"
tracing = "0.1.29"
tracing-appender = "0.2.0"
tracing-subscriber = { version = "0.3.3" }

src/main.rs:

use chrono::Local;
use std::io;
use tracing::*;
use tracing_subscriber::fmt::format::Writer;
use tracing_subscriber::{self, fmt::time::FormatTime};

// 用來格式化日誌的輸出時間格式
struct LocalTimer;

impl FormatTime for LocalTimer {
    fn format_time(&self, w: &mut Writer<'_>) -> std::fmt::Result {
        write!(w, "{}", Local::now().format("%FT%T%.3f"))
    }
}

// 通過instrument屬性,直接讓整個函數或方法進入span區間,且適用於異步函數async fn fn_name(){}
// 參考:https://docs.rs/tracing/latest/tracing/attr.instrument.html
// #[tracing::instrument(level = "info")]
#[instrument]
fn test_trace(n: i32) {
    // #[instrument]屬性表示函數整體在一個span區間內,因此函數內的每一個event信息中都會額外帶有函數參數
    // 在函數中,只需發出日誌即可
    event!(Level::TRACE, answer = 42, "trace2: test_trace");
    trace!(answer = 42, "trace1: test_trace");
    info!(answer = 42, "info1: test_trace");
}

// 在可執行程序中,需初始化tracing subscriber來收集、篩選並按照指定格式來記錄日誌
fn main() {
    // 直接初始化,採用默認的Subscriber,默認只輸出INFO、WARN、ERROR級別的日誌
    // tracing_subscriber::fmt::init();

    // 使用tracing_appender,指定日誌的輸出目標位置
    // 參考: https://docs.rs/tracing-appender/0.2.0/tracing_appender/
    let file_appender = tracing_appender::rolling::daily("/tmp", "tracing.log");
    let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);

    // 設置日誌輸出時的格式,例如,是否包含日誌級別、是否包含日誌來源位置、設置日誌的時間格式
    // 參考: https://docs.rs/tracing-subscriber/0.3.3/tracing_subscriber/fmt/struct.SubscriberBuilder.html#method.with_timer
    let format = tracing_subscriber::fmt::format()
        .with_level(true)
        .with_target(true)
        .with_timer(LocalTimer);

    // 初始化並設置日誌格式(定製和篩選日誌)
    tracing_subscriber::fmt()
        .with_max_level(Level::TRACE)
        .with_writer(io::stdout) // 寫入標準輸出
        .with_writer(non_blocking) // 寫入文件,將覆蓋上面的標準輸出
        .with_ansi(false)  // 如果日誌是寫入文件,應將ansi的顏色輸出功能關掉
        .event_format(format)
        .init();

    test_trace(33);
    trace!("tracing-trace");
    debug!("tracing-debug");
    info!("tracing-info");
    warn!("tracing-warn");
    error!("tracing-error");
}

輸出(上面設置了輸出到文件/tmp/tracing.log):

// 其中出現的log,是target
2021-12-01T15:09:55.797 TRACE test_trace{n=33}: log: trace1: test_trace answer=42
2021-12-01T15:09:55.797 TRACE test_trace{n=33}: log: trace2: test_trace answer=42
2021-12-01T15:09:55.797 TRACE log: tracing-trace
2021-12-01T15:09:55.797 DEBUG log: tracing-debug
2021-12-01T15:09:55.797  INFO log: tracing-info
2021-12-01T15:09:55.797  WARN log: tracing-warn
2021-12-01T15:09:55.797 ERROR log: tracing-error