【Rust】使用日誌記錄利器flexi_logger

VinciYan發表於2024-07-17

Flexi_logger簡介

flexi_logger​是一個功能強大且靈活的日誌記錄庫,用於Rust語言的應用程式。它提供了豐富的配置選項和功能,適用於各種日誌記錄需求,從簡單的控制檯輸出到複雜的檔案日誌管理。以下是對flexi_logger​的一些關鍵功能和特性的簡介:

主要功能

  • 多種日誌目標:支援將日誌輸出到控制檯、檔案或同時輸出到多個目標
  • 日誌檔案滾動:支援基於檔案大小或時間的日誌檔案滾動,可以按大小、時間(每天、每小時等)進行切分
  • 日誌檔案清理:可以配置保留的日誌檔案數量,自動刪除過期的日誌檔案
  • 日誌格式自定義:支援自定義日誌格式,可以包含時間戳、日誌級別、目標模組等資訊
  • 日誌級別過濾:支援設定不同的日誌級別(如info​、warn​、error​等),只記錄滿足級別要求的日誌訊息
  • 非同步日誌:支援非同步寫日誌,提高效能
  • 日誌複製:可以將日誌複製到標準輸出(stdout)或標準錯誤(stderr)

使用示例

建立專案

cargo new rs_demo

Cargo.toml

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

[dependencies]
log = "0.4"
flexi_logger = "0.22"
time = "0.3"

main.rs

use log::{trace, debug, info, warn, error};
use flexi_logger::{Duplicate, FileSpec, Logger, WriteMode, Criterion, Naming, Cleanup, detailed_format};

// 定義一個結構體 `Rectangle`
struct Rectangle {
    width: u32,
    height: u32,
}

// 為 `Rectangle` 實現方法
impl Rectangle {
    // 關聯函式(類似於靜態方法)
    fn new(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }
    }

    // 例項方法
    fn area(&self) -> u32 {
        self.width * self.height
    }

    // 例項方法
    fn perimeter(&self) -> u32 {
        2 * (self.width + self.height)
    }

    // 修改例項的方法
    fn set_width(&mut self, width: u32) {
        self.width = width;
    }
}

fn main() {
    // 初始化日誌記錄,配置輸出到檔案,設定檔案大小限制和滾動日誌
    let logger = Logger::try_with_str("trace")
        .unwrap()
        .log_to_file(
            FileSpec::default()
                .directory("logs") // 設定日誌檔案目錄
                .basename("app")   // 設定日誌檔案字首
                .suffix("log")     // 設定日誌檔案字尾
        )
        .rotate(
            Criterion::Size(10_000_000), // 設定日誌檔案大小限制為 10 MB
            Naming::Numbers,             // 使用數字序號進行檔案命名
            Cleanup::KeepLogFiles(3),    // 保留最近的 3 個日誌檔案
        )
        .write_mode(WriteMode::BufferAndFlush) // 設定日誌寫入模式
        .duplicate_to_stderr(Duplicate::Warn)  // 將警告級別的日誌複製到標準錯誤輸出
        .format_for_files(detailed_format)     // 使用詳細格式,包含時間戳
        .start()
        .unwrap();

    // 記錄不同級別的日誌訊息
    trace!("This is a trace message.");
    debug!("This is a debug message.");
    info!("This is an info message.");
    warn!("This is a warning message.");
    error!("This is an error message.");

    // 使用關聯函式建立一個 `Rectangle`
    let mut rect = Rectangle::new(30, 50);

    // 呼叫例項方法
    info!("The area of the rectangle is {} square pixels.", rect.area());
    info!("The perimeter of the rectangle is {} pixels.", rect.perimeter());

    // 修改例項的欄位
    rect.set_width(40);
    info!("After resizing, the area of the rectangle is {} square pixels.", rect.area());

    // 強制重新整理日誌緩衝區
    logger.flush();
}

這裡的配置包括:

  • 日誌檔案目錄:日誌檔案將被儲存到logs目錄中
  • 日誌檔案字首:日誌檔案將以app作為字首
  • 日誌檔案字尾:日誌檔案將以.log作為字尾
  • 日誌檔案滾動策略:當日志檔案大小達到10 MB時,日誌檔案將滾動,新的日誌檔案將使用時間戳命名
  • 日誌檔案保留策略:只保留最近的3個日誌檔案
  • 日誌寫入模式:使用緩衝並在寫入時重新整理
  • 日誌複製到標準錯誤輸出:將警告級別的日誌訊息複製到標準錯誤輸出
  • 日誌格式:使用詳細格式,包含時間戳

在Rust的log庫中,不同的日誌宏可以記錄不同級別的日誌訊息。這些日誌級別分別是:

  • trace!:最詳細的日誌資訊,通常用於跟蹤程式的細節流程
  • debug!:用於除錯時的資訊,比 trace! 略少,但仍然非常詳細
  • info!:一般資訊,不含除錯細節,適合普通執行時的資訊
  • warn!:警告資訊,表明某些事情可能會出問題,但程式可以繼續執行
  • error!:錯誤資訊,表明發生了嚴重的問題,程式可能無法繼續執行

日誌檔案資料:

[2024-07-17 21:08:07.689886 +08:00] TRACE [rs_demo] src/main.rs:55: This is a trace message.
[2024-07-17 21:08:07.691157 +08:00] DEBUG [rs_demo] src/main.rs:56: This is a debug message.
[2024-07-17 21:08:07.691170 +08:00] INFO [rs_demo] src/main.rs:57: This is an info message.
[2024-07-17 21:08:07.697314 +08:00] WARN [rs_demo] src/main.rs:58: This is a warning message.
[2024-07-17 21:08:07.707785 +08:00] ERROR [rs_demo] src/main.rs:59: This is an error message.
[2024-07-17 21:08:07.707804 +08:00] INFO [rs_demo] src/main.rs:65: The area of the rectangle is 1500 square pixels.
[2024-07-17 21:08:07.707809 +08:00] INFO [rs_demo] src/main.rs:66: The perimeter of the rectangle is 160 pixels.
[2024-07-17 21:08:07.707814 +08:00] INFO [rs_demo] src/main.rs:70: After resizing, the area of the rectangle is 2000 square pixels.

自定義日誌的輸出格式

使用format_for_files函式可以用於自定義日誌的輸出格式。定義一個名為custom_format的函式,並將其傳遞給format_for_files以使用自定義格式記錄日誌

custom_format中:

  • w:一個實現了Write trait的可寫物件,日誌訊息將被寫入這個物件
  • record:一個Record物件,包含了日誌記錄的詳細資訊
use log::{info};
use flexi_logger::{DeferredNow, Duplicate, FileSpec, Logger, Record, WriteMode, Criterion, Naming, Cleanup};
use std::io::Write;
use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime;

// 定義一個結構體 `Rectangle`
struct Rectangle {
    width: u32,
    height: u32,
}

// 為 `Rectangle` 實現方法
impl Rectangle {
    // 關聯函式(類似於靜態方法)
    fn new(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }
    }

    // 例項方法
    fn area(&self) -> u32 {
        self.width * self.height
    }

    // 例項方法
    fn perimeter(&self) -> u32 {
        2 * (self.width + self.height)
    }

    // 修改例項的方法
    fn set_width(&mut self, width: u32) {
        self.width = width;
    }
}

fn custom_format(
    w: &mut dyn Write,
    _now: &mut DeferredNow,
    record: &Record,
) -> std::io::Result<()> {
    let now = OffsetDateTime::now_utc().format(&Rfc3339).unwrap();
    write!(
        w,
        "{} [{}] - {} - {}",
        now,
        record.level(),
        record.target(),
        record.args()
    )
}

fn main() {
    // 初始化日誌記錄,配置輸出到檔案,設定檔案大小限制和滾動日誌
    let logger = Logger::try_with_str("info")
        .unwrap()
        .log_to_file(
            FileSpec::default()
                .directory("logs") // 設定日誌檔案目錄
                .basename("app")   // 設定日誌檔案字首
                .suffix("log")     // 設定日誌檔案字尾
        )
        .rotate(
            Criterion::Size(10_000_000), // 設定日誌檔案大小限制為 10 MB
            Naming::Numbers,             // 使用數字序號進行檔案命名
            Cleanup::KeepLogFiles(3),    // 保留最近的 3 個日誌檔案
        )
        .write_mode(WriteMode::BufferAndFlush) // 設定日誌寫入模式
        .duplicate_to_stderr(Duplicate::Warn)  // 將警告級別的日誌複製到標準錯誤輸出
        .format_for_files(custom_format)       // 使用自定義格式
        .start()
        .unwrap();

    // 記錄不同級別的日誌訊息
    info!("This is an info message.");

    // 使用關聯函式建立一個 `Rectangle`
    let mut rect = Rectangle::new(30, 50);

    // 呼叫例項方法
    info!("The area of the rectangle is {} square pixels.", rect.area());
    info!("The perimeter of the rectangle is {} pixels.", rect.perimeter());

    // 修改例項的欄位
    rect.set_width(40);
    info!("After resizing, the area of the rectangle is {} square pixels.", rect.area());

    // 強制重新整理日誌緩衝區
    logger.flush();
}
2024-07-17T12:28:34.701362Z [INFO] - rs_demo - This is an info message.
2024-07-17T12:28:34.7024068Z [INFO] - rs_demo - The area of the rectangle is 1500 square pixels.
2024-07-17T12:28:34.7024153Z [INFO] - rs_demo - The perimeter of the rectangle is 160 pixels.
2024-07-17T12:28:34.7024181Z [INFO] - rs_demo - After resizing, the area of the rectangle is 2000 square pixels.

驗證日誌檔案大小和保留日誌檔案個數

下面透過一個無限迴圈不斷地寫入日誌訊息,從而測試日誌檔案的滾動和清理功能。這種設定在實際應用中可能用於持續記錄應用程式的執行狀態或除錯資訊

use log::info;
use flexi_logger::{Logger, FileSpec, Criterion, Naming, Cleanup, WriteMode, Duplicate, detailed_format};
use std::thread;
use std::time::Duration;

fn main() {
    // 初始化日誌記錄,配置輸出到檔案,設定檔案大小限制和滾動日誌
    let _logger = Logger::try_with_str("info")
        .unwrap()
        .log_to_file(
            FileSpec::default()
                .directory("logs") // 設定日誌檔案目錄
                .basename("app") // 設定日誌檔案字首
                .suffix("log")     // 設定日誌檔案字尾
        )
        .rotate(
            Criterion::Size(5_000), // 設定日誌檔案大小限制為 5 KB
            Naming::Timestamps,         // 使用時間戳進行檔案命名
            Cleanup::KeepLogFiles(3),   // 保留最近的 3 個日誌檔案
        )
        .write_mode(WriteMode::BufferAndFlush) // 設定日誌寫入模式
        .duplicate_to_stderr(Duplicate::Warn)  // 將警告級別的日誌複製到標準錯誤輸出
        .format_for_files(detailed_format)     // 使用詳細格式,包含時間戳
        .start()
        .unwrap();

    // 無限迴圈,不斷寫入日誌
    loop {
        info!("This is an info message.");
      
        // 模擬一些工作,等待一段時間
        thread::sleep(Duration::from_secs(1));
    }
}

執行程式數分鐘,檢視日誌資料夾,資料夾目錄結構如下,“rCURRENT.log”代表當前程式正在寫入的日誌檔案,剩餘的三個日誌檔案為程式保留的三個日誌檔案,日誌檔案都含有時間戳,並且不會隨著程式執行的時間延長而新增檔案個數

├─logs
│      app_r2024-07-17_20-44-24.log
│      app_r2024-07-17_20-45-19.log
│      app_r2024-07-17_20-46-15.log
│      app_rCURRENT.log

日誌輸出到控制檯和檔案

使用duplicate_to_stdout(Duplicate::All)方法,將所有級別的日誌複製到標準輸出(控制檯)

.duplicate_to_stdout(Duplicate::All)   // 將所有級別的日誌複製到標準輸出

這樣,控制檯能看到所有輸出到日誌檔案的日誌資訊

相關文章