依赖介绍:
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