Rust使用tracing记录日志

依赖介绍:

markdown 复制代码
1. tracing:核心API的定义
2. tracing-subscriber: 中间层,订阅event,span,然后根据其中的属性做一些定制操作
3. tracing-appender: 负责实际的日志写入
4. chrono(可选):tracing默认配置的时区为UTC,通过这个包自定义到本地时区

概念介绍:

csharp 复制代码
5. event:某个时间点上的某个事件,简单来说一行日志就是一个event
6. span:一个过程,比如某个函数的执行过程

tracing配置:

rust 复制代码
use std::path::PathBuf;
use chrono::Local;
use tracing::{Level, Subscriber};
use tracing_appender::non_blocking::{NonBlocking, WorkerGuard};
use tracing_appender::rolling::daily;
use tracing_subscriber::{fmt, Layer, Registry};
use tracing_subscriber::fmt::format::Writer;
use tracing_subscriber::fmt::time::FormatTime;
use tracing_subscriber::layer::SubscriberExt;

struct LocalTimer;

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


pub fn setup_logging() -> (
    impl Subscriber + Send + Sync,
    Vec<tracing_appender::non_blocking::WorkerGuard>,
) {
    // 创建 app.log 的按天滚动日志写入器
    let app_log_dir = PathBuf::from("./log");
    let app_rolling_appender = daily(app_log_dir.clone(), "tracing_test.log");
    let (non_blocking_appender, guard_appender) = NonBlocking::new(app_rolling_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);

    // 创建 fmt 层
    let app_layer = fmt::layer()
        .with_writer(non_blocking_appender)
        .with_ansi(false)
        .event_format(format)
        .with_target(false)
        .with_filter(tracing_subscriber::filter::filter_fn(|metadata| {
            metadata.target() != "detail"
        }));

    // 将两个层组合在一起
    let subscriber = Registry::default()
        .with(app_layer);

    (subscriber, vec![guard_appender])
}

main函数初始化配置

rust 复制代码
use tracing::{info, span, Level, Span};
use tracing::subscriber::set_global_default;
use crate::tracing_config::setup_logging;

mod tracing_config;

fn main() {

    let (subscriber, guards) = setup_logging();
    set_global_default(subscriber).expect("setting default subscriber failed");

    // main函数中需要持有 guard, 否则日志可能会丢失
    let _guard = guards;
    info!("Hello, Tracing");
}

使用span:

同步方法使用span:

rust 复制代码
use tracing::{info, span, Level, Span};
use tracing::subscriber::set_global_default;
use crate::tracing_config::setup_logging;

mod tracing_config;

fn main() {

    let (subscriber, guards) = setup_logging();
    set_global_default(subscriber).expect("setting default subscriber failed");

    // main函数中需要持有 guard, 否则日志可能会丢失
    let _guard = guards;
    info!("Hello, Tracing");

    let span = span!(Level::INFO, "test_span1");

    // 进入span,
    let _enter = span.enter();
    sync_test_span();
}

pub fn sync_test_span(){
    let r = Span::current();
    println!("sync_test_span: {:?}", r);
}

输出:

rust 复制代码
sync_test_span: Span { name: "test_span1", level: Level(Info), target: "tracing_test", id: Id(1), module_path: "tracing_test", line: 16, file: "tracing_test\\src/main.rs" }

异步方法中使用span:

async修饰的函数/方法不能手动 span.enter(),因为任务可能进行切换,从而导致span上下文出错

两种调用方式:

markdown 复制代码
    1. 手动调用:
rust 复制代码
use crate::tracing_config::setup_logging;
use tracing::subscriber::set_global_default;
use tracing::{info, span, Instrument, Level, Span};

mod tracing_config;

#[tokio::main]
async  fn main() {
    let (subscriber, guards) = setup_logging();
    set_global_default(subscriber).expect("setting default subscriber failed");

    // main函数中需要持有 guard, 否则日志可能会丢失
    let _guard = guards;
    info!("Hello, Tracing");

    // let span = span!(Level::INFO, "test_span1");
    //
    // // 进入span,
    // let _enter = span.enter();
    // sync_test_span();



    async_test_span().await;
}

pub fn sync_test_span() {
    let r = Span::current();
    println!("sync_test_span: {:?}", r);
}

async fn async_test_span() {
    let span = span!(Level::INFO, "async_test_span");
    async_test_span2().instrument(span).await;
}

async fn async_test_span2() {
    let r = Span::current();
    println!("async_test_span2: {:?}", r);
}

输出:

rust 复制代码
async_test_span2: Span { name: "async_test_span", level: Level(Info), target: "tracing_test", id: Id(1), module_path: "tracing_test", line: 37, file: "tracing_test\\src/main.rs" }
markdown 复制代码
    2. 自动调用
rust 复制代码
use crate::tracing_config::setup_logging;
use tracing::subscriber::set_global_default;
use tracing::{info, instrument, span, Instrument, Level, Span};

mod tracing_config;

#[tokio::main]
async  fn main() {
    let (subscriber, guards) = setup_logging();
    set_global_default(subscriber).expect("setting default subscriber failed");

    // main函数中需要持有 guard, 否则日志可能会丢失
    let _guard = guards;
    info!("Hello, Tracing");

    // let span = span!(Level::INFO, "test_span1");
    //
    // // 进入span,
    // let _enter = span.enter();
    // sync_test_span();

    async_test_span3().await
}

#[instrument]
pub async  fn async_test_span3(){
    let r = Span::current();
    println!("async_test_span3: {:?}", r);
}

输出:

rust 复制代码
async_test_span3: Span { name: "async_test_span3", level: Level(Info), target: "tracing_test", id: Id(1), module_path: "tracing_test", line: 28, file: "tracing_test\\src/main.rs" }

span中添加属性

:::info 1. span使用record写入的属性需要提前声明

:::

测试代码:

rust 复制代码
use tracing::{field, info, instrument, span, Level, Span};
use tracing::subscriber::set_global_default;

pub mod tracing_config;
fn main() {
    let (subscriber, guards) = crate::tracing_config::setup_logging();
    set_global_default(subscriber).expect("setting default subscriber failed");

    // main函数中需要持有 guard, 否则日志可能会丢失
    let _guard = guards;
    info!("Hello, Tracing");

    test_record1();
}

pub fn test_record1(){
    let span = span!(Level::INFO, "test_record1");

    let _enter = span.enter();

    info!("before:{:?}",span);

    span.record("trace_id", "456");

    info!("after:{:?}",span);
}

输出:

rust 复制代码
2024-12-25T23:45:12.404  INFO test_record1: before:Span { name: "test_record1", level: Level(Info), target: "span_record", id: Id(1), module_path: "span_record", line: 17, file: "tracing_test\\src/span_record.rs" }
2024-12-25T23:45:12.404  INFO test_record1: after:Span { name: "test_record1", level: Level(Info), target: "span_record", id: Id(1), module_path: "span_record", line: 17, file: "tracing_test\\src/span_record.rs" }

:::info b. 同一个span中同一个key写入只会追加,而不是覆盖

:::

测试代码:

rust 复制代码
use tracing::{field, info, instrument, span, Level, Span};
use tracing::subscriber::set_global_default;

pub mod tracing_config;
fn main() {
    let (subscriber, guards) = crate::tracing_config::setup_logging();
    set_global_default(subscriber).expect("setting default subscriber failed");

    // main函数中需要持有 guard, 否则日志可能会丢失
    let _guard = guards;
    info!("Hello, Tracing");

    // test_record1();

    test_record2();
}

pub fn test_record1(){
    let span = span!(Level::INFO, "test_record1");

    let _enter = span.enter();

    info!("before:{:?}",span);

    span.record("trace_id", "456");

    info!("after:{:?}",span);
}


pub fn test_record2(){
    let span = span!(Level::INFO, "test_record2", trace_id="123");

    let _enter = span.enter();

    info!("before_test_record_2:{:?}",span);

    test_record3();

    info!("after_test_record_2:{:?}",span)
}


pub fn test_record3(){
    let span  = Span::current();
    span.record("trace_id", "456");
}

输出:

rust 复制代码
2024-12-25T23:48:20.087  INFO test_record2{trace_id="123"}: before_test_record_2:Span { name: "test_record2", level: Level(Info), target: "span_record", id: Id(1), module_path: "span_record", line: 32, file: "tracing_test\\src/span_record.rs" }
2024-12-25T23:48:20.087  INFO test_record2{trace_id="123" trace_id="456"}: after_test_record_2:Span { name: "test_record2", level: Level(Info), target: "span_record", id: Id(1), module_path: "span_record", line: 32, file: "tracing_test\\src/span_record.rs" }

读取span中写入的属性

除了显式透传之外,查阅官方文档和资料,并没有找到tracing开放访问写入属性的API,只有一个自定义Layer的方式

代码如下:

customer_layer:

rust 复制代码
use std::collections::HashMap;
use tracing::span::{Attributes, Record};
use tracing::{info, Id, Subscriber};
use tracing::field::{Field, Visit};
use tracing_subscriber::Layer;
use tracing_subscriber::layer::Context;

pub struct CustomAttrLayer;

impl<S> Layer<S> for CustomAttrLayer
where
    S: Subscriber,
{
    fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
        let mut visitor = TraceCtxVisitor::new();
        attrs.record(&mut visitor);

        info!("on_new_span: {:?}", visitor.ctx);
    }

    fn on_record(&self, _span: &Id, _values: &Record<'_>, _ctx: Context<'_, S>) {
        let mut visitor = TraceCtxVisitor::new();
        _values.record(&mut visitor);

        info!("on_record: {:?}", visitor.ctx);
    }

    fn on_exit(&self, _id: &Id, _ctx: Context<'_, S>) {
        info!("on_exit");
    }

    fn on_close(&self, _id: Id, _ctx: Context<'_, S>) {
        info!("on_close");
    }
}




#[derive(Debug)]
pub struct TraceCtxVisitor {
    pub ctx: HashMap<String, String>,
}

impl TraceCtxVisitor {
    pub fn new() -> Self {
        TraceCtxVisitor { ctx: HashMap::new() }
    }
}


impl Visit for TraceCtxVisitor {
    fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
        self.ctx.insert(field.name().to_string(), format!("{:?}", value));
    }
}

tracing_subscriber配置中添加自定义layer

rust 复制代码
pub fn setup_logging() -> (
    impl Subscriber + Send + Sync,
    Vec<tracing_appender::non_blocking::WorkerGuard>,
) {
    // 创建 app.log 的按天滚动日志写入器
    let app_log_dir = PathBuf::from("./log");
    let app_rolling_appender = daily(app_log_dir.clone(), "tracing_test.log");
    let (non_blocking_appender, guard_appender) = NonBlocking::new(app_rolling_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(tracing_config::LocalTimer);

    // 创建 fmt 层
    let app_layer = fmt::layer()
        .with_writer(non_blocking_appender)
        .with_ansi(false)
        .event_format(format)
        .with_target(false)
        .with_filter(tracing_subscriber::filter::filter_fn(|metadata| {
            metadata.target() != "detail"
        }));

    // 将两个层组合在一起
    let subscriber = Registry::default()
        .with(app_layer)
        .with(CustomAttrLayer);


    (subscriber, vec![guard_appender])
}

测试代码:

rust 复制代码
fn main() {

    let (subscriber, guards) = setup_logging();
    set_global_default(subscriber).expect("setting default subscriber failed");

    // main函数中需要持有 guard, 否则日志可能会丢失
    let _guard = guards;
    info!("Hello, Tracing");


    test_span();

}

fn test_span(){
    let span = span!(Level::INFO, "test_span", trace_id="123");

    let _enter = span.enter();

    span.record("trace_id", "456");
}

输出:

rust 复制代码
2024-12-25T23:48:20.087  INFO test_record2{trace_id="123"}: before_test_record_2:Span { name: "test_record2", level: Level(Info), target: "span_record", id: Id(1), module_path: "span_record", line: 32, file: "tracing_test\\src/span_record.rs" }
2024-12-25T23:48:20.087  INFO test_record2{trace_id="123" trace_id="456"}: after_test_record_2:Span { name: "test_record2", level: Level(Info), target: "span_record", id: Id(1), module_path: "span_record", line: 32, file: "tracing_test\\src/span_record.rs" }
2024-12-25T23:59:51.446  INFO Hello, Tracing
2024-12-25T23:59:51.446  INFO on_new_span: {"trace_id": "\"123\""}
2024-12-25T23:59:51.446  INFO test_span{trace_id="123" trace_id="456"}: on_record: {"trace_id": "\"456\""}
2024-12-25T23:59:51.446  INFO on_exit
2024-12-25T23:59:51.446  INFO on_close

仓库地址:github.com/xtoshii/rus...

相关推荐
许野平1 小时前
Rust: enum 和 i32 的区别和互换
python·算法·rust·enum·i32
前端小魔女4 小时前
2024-我赚到自媒体第一桶金
前端·rust
编码浪子14 小时前
构建一个rust生产应用读书笔记7-确认邮件2
开发语言·后端·rust
songroom20 小时前
Rust: offset祼指针操作
开发语言·算法·rust
唐 城1 天前
curl 放弃对 Hyper Rust HTTP 后端的支持
开发语言·http·rust
从善若水1 天前
【2024】Merry Christmas!一起用Rust绘制一颗圣诞树吧
开发语言·后端·rust
gerrylon0071 天前
rust学习: 有用的命令
rust
brrdg_sefg2 天前
Rust 在前端基建中的使用
前端·rust·状态模式
m0_748230942 天前
Rust赋能前端: 纯血前端将 Table 导出 Excel
前端·rust·excel