tracing+tracing_subscriber日志库

tarcing流程

  • SubscriberLayer不是串行的两步,而是组合在一起的一个整体
graph TD A["tracing::info!()"] --> B[Event 数据] C["tracing::span!()"] --> D[Span 数据] B --> E[全局 Subscriber<br/>(Registry + Layers 组合体)] D --> E E --> F[内部 Layer 依次处理<br/>过滤、格式化、采样...] F --> G["输出到 stdout / 文件 / JSON / OTLP 等"]

tarcing日志级别

输出Event

  • debug!:级别为 DEBUG,用于调试目的的信息,比如变量值、中间状态、流程分支等。
  • trace!:级别为 TRACE,是最细粒度的超详细跟踪,通常记录非常底层的步骤(如进入/离开函数、循环体内部状态、协议交互细节),平时一般不会开启。
ERROR > WARN > INFO > DEBUG > TRACE

tarcing用法

默认启用info级别输出, 可以通过RUST_LOG=debug cargo run调整输出级别

use tracing::{info, error, debug, warn, trace};

fn main() {
    // 需要创建Subscriber和Layer(默认的)
    tracing_subscriber::fmt::init();
    debug!("debug message");
    trace!("trace message");
    info!("server started");
    error!("something wrong");
    warn!("something wrong");
}
2026-08-05T13:20:32.362457Z  INFO test_tracing: server started
2026-08-05T13:20:32.362576Z ERROR test_tracing: something wrong
2026-08-05T13:20:32.362588Z  WARN test_tracing: something wrong

创建Subscriber和Layer

tracing_subscriber::fmt::init()

这是一个便捷函数,内部等价于默认配置的tracing_subscriber::fmt().init()

  • 使用默认配置初始化subscriber
  • 读取RUST_LOG 环境变量作为日志级别过滤器(如果启用了 env-filter这个feature
  • 无法自定义任何格式、输出目标、过滤规则等
  • 代码最简洁,适合快速原型或简单场景
// 内部等价于
tracing_subscriber::fmt()
		// 设置日志输出级别
    .with_env_filter(
      // 从环境变量RUST_LOG读取日志级别,没有默认info级别
      tracing_subscriber::EnvFilter::from_default_env()
		)
    .init();

tracing_subscriber::fmt().init()

  • tracing_subscriber::fmt()构建了SubscriberBuilder
tracing_subscriber::fmt()
		// 设置日志输出级别
    .with_env_filter(
      // 永远不会读 RUST_LOG, 默认info级别
      tracing_subscriber::EnvFilter::default()
		)
    .init();
tracing_subscriber::fmt()
# 创建SubscriberBuilder对象
SubscriberBuilder::default()
# SubscriberBuilder下可以设置不同的参数
SubscriberBuilder {
    filter,
    fmt,
    writer,
    timer,
    ansi,
    event_format,
    span_events,
}

# 默认设置
inner
 |
 +-- writer = stdout # 输出
 |
 +-- timer = SystemTime # 时间格式utc
 |
 +-- formatter = Full # 输出格式
 |
 +-- ansi = true # 开启颜色
 |
 +-- fields formatter # 输出格式

# 变为subscriber
## builder <= SubscriberBuilder
let subscriber = builder.finish();
# 设置为全局subscriber
tracing::subscriber::set_global_default(
    subscriber
);
fmt()
 |
 |
SubscriberBuilder::default()
 |
 |
配置 Builder
 |
 |
.init()
 |
 |
finish()
 |
 |
fmt::Subscriber
 |
 |
set_global_default()
 |
 |
tracing::info!()
 |
 |
Event进入Subscriber

SubscriberBuilder配置

SubscriberBuilder
│
├── 过滤(Filter)
│
├── 输出目标(Writer)
│
├── 格式化(Format)
│
├── 时间(Timer)
│
├── Span行为
│
├── Event行为
│
└── 其他显示选项

日志过滤级别-Filter

方法 类型参数变化 效果
.with_max_level(level) F = LevelFilter 全局最大级别,如 Level::DEBUG 只输出 DEBUG 及以上
.with_env_filter(filter) F = EnvFilter 复杂过滤规则,支持模块/span/field 分级,可读取 RUST_LOG

with_max_level

use tracing::{info, error, debug, warn, trace, Level};

fn main() {
    tracing_subscriber::fmt()
  			// 简单固定级别
        .with_max_level(tracing::Level::TRACE)
        .init();

    trace!("trace message");
    debug!("debug message");
    info!("server started");
    error!("something wrong");
    warn!("something wrong");
}
2026-08-05T15:17:15.909030Z TRACE test_tracing: trace message
2026-08-05T15:17:15.909136Z DEBUG test_tracing: debug message
2026-08-05T15:17:15.909143Z  INFO test_tracing: server started
2026-08-05T15:17:15.909147Z ERROR test_tracing: something wrong
2026-08-05T15:17:15.909151Z  WARN test_tracing: something wrong

with_env_filter

复杂动态规则

use tracing::{info, error, debug, warn, trace};

fn main() {
    tracing_subscriber::fmt()
        .with_env_filter("trace")
        .init();

    trace!("trace message");
    debug!("debug message");
    info!("server started");
    error!("something wrong");
    warn!("something wrong");
}
2026-08-05T15:18:09.674623Z TRACE test_tracing: trace message
2026-08-05T15:18:09.674803Z DEBUG test_tracing: debug message
2026-08-05T15:18:09.674816Z  INFO test_tracing: server started
2026-08-05T15:18:09.674826Z ERROR test_tracing: something wrong
2026-08-05T15:18:09.674876Z  WARN test_tracing: something wrong
案例
use tracing::{info, error, debug, warn, trace};
use tracing_subscriber::filter::EnvFilter;
fn main() {
		// 从环境变量读取RUST_LOG,没有的话默认设置debug级别输出
    let filter = EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| EnvFilter::new("debug"));
    tracing_subscriber::fmt()
        .with_env_filter(filter)
        .init();

    trace!("trace message");
    debug!("debug message");
    info!("server started");
    error!("something wrong");
    warn!("something wrong");
}
2026-08-05T15:19:43.994212Z DEBUG test_tracing: debug message
2026-08-05T15:19:43.994314Z  INFO test_tracing: server started
2026-08-05T15:19:43.994319Z ERROR test_tracing: something wrong
2026-08-05T15:19:43.994324Z  WARN test_tracing: something wrong

格式化配置-Format

  • compact需要在span数据中才会有效果
方法 默认值 效果
.with_target(true/false) true 是否显示模块路径,如 my_crate::module
.with_file(true/false) false 是否显示源文件名
.with_line_number(true/false) false 是否显示行号
.with_level(true/false) true 是否显示日志级别(INFO/DEBUG 等)
.with_thread_ids(true/false) false 是否显示线程 ID(如 thread 12345
.with_thread_names(true/false) false 是否显示线程名称
.with_ansi(true/false) true 是否启用 ANSI 颜色码(终端彩色输出)
.compact() 紧凑单行格式,减少空格
.pretty() 美化多行格式,适合人类阅读
.json() 输出 JSON 结构化格式,features需要开启json
# 默认
2026-08-05T15:23:49.817095Z  INFO test_tracing: server started

# with_target=false
2026-08-05T15:23:40.895205Z  INFO server started

# with_file和with_line_number = true
2026-08-05T15:24:53.634480Z  INFO test_tracing: src/main.rs:13: server started

# with_thread_ids和with_thread_names = true
2026-08-05T15:26:23.328404Z  INFO main ThreadId(01) test_tracing: server started # true

# compact
2026-08-05T15:30:28.033478Z  INFO test_tracing: server started

# pretty
  2026-08-05T15:30:12.561473Z  INFO test_tracing: server started
    at src/main.rs:12

# json
{"timestamp":"2026-08-05T15:29:18.159783Z","level":"INFO","fields":{"message":"server started"},"target":"test_tracing"}

时间-Timer

方法 效果
.without_time() 不输出时间戳
.with_timer(timer) 自定义时间格式化器

tracing_subscriber设置时间

  • tracing-subscriberfeatures需要开启local-time
use tracing::{info, warn, error, debug};
use tracing_subscriber::{self, EnvFilter};

fn main() {
    // 1.设置输出级别
    let filter = EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| EnvFilter::new("debug"));

    // 2. 初始化:这是最关键的一步,没有它日志不会输出
    tracing_subscriber::fmt()
        .with_env_filter(filter)
        .with_timer(tracing_subscriber::fmt::time::LocalTime::rfc_3339())  // 设置时区
        .init();

    // 3. 使用宏记录日志
    info!("info信息");
}
2026-08-05T23:35:39.716393+08:00  INFO test_tracing: info信息

chrono设置时间

  • cargo add chrono添加chrono
use std::fmt;
use tracing::info;
use tracing_subscriber::{
    EnvFilter,
    fmt::format::Writer,
    fmt::time::FormatTime
};

// 需要实现自定义时间格式器
struct ChronoLocalTimer;

impl FormatTime for ChronoLocalTimer {
    fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result {
        write!(w, "{}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S"))
    }
}

fn main() {
    // 设置输出级别
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"));

    tracing_subscriber::fmt()
        .with_env_filter(filter) // 绑定输出级别
        .with_timer(ChronoLocalTimer)
        .init();

    info!("info信息");
}
2026-08-05 23:36:49  INFO test_tracing: info信息

输出-Writer

方法 效果
.with_writer(writer) 自定义 MakeWriter,默认是 Stdout
.with_writer(move || file) 常见闭包写法,返回一个 io::Write

写入文件

use tracing::info;
use tracing_subscriber::EnvFilter;
use std::fs::OpenOptions;

fn main() {
    // 设置输出级别
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"));
  	// 创建文件
    let file = OpenOptions::new()
        .create(true).append(true).open("app.log")
        .unwrap();

    tracing_subscriber::fmt()
        .with_env_filter(filter) // 绑定输出级别
        .with_writer(move || file.try_clone().unwrap())
        .with_ansi(false)
        .init();

    info!("info信息");
}

日志滚动

  • 安装cargo add tracing-appender
use tracing::info;
use tracing_subscriber::EnvFilter;
// 导入tracing_appender
use tracing_appender::rolling::{RollingFileAppender, Rotation};

fn main() {
    // 设置输出级别
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"));
  	// RollingFileAppender
    let file_appender = RollingFileAppender::new(Rotation::DAILY, "./logs", "app.log");

    tracing_subscriber::fmt()
        .with_env_filter(filter)
        .with_writer(file_appender) // 绑定输出级别
        .with_ansi(false)
        .init();

    info!("info信息");
}

Span 生命周期事件-FmtSpan

配置 效果
.with_span_events(FmtSpan::NONE) 不输出 span 生命周期事件(默认)
.with_span_events(FmtSpan::NEW) span 创建时输出
.with_span_events(FmtSpan::ENTER) span 进入时输出
.with_span_events(FmtSpan::EXIT) span 退出时输出
.with_span_events(FmtSpan::CLOSE) span 关闭时输出
.with_span_events(FmtSpan::ACTIVE) 进入 + 退出
.with_span_events(FmtSpan::FULL) 创建 + 进入 + 退出 + 关闭

其他配置

方法 默认值 说明
.with_current_span(true/false) false JSON 输出时,默认不附加当前 span 信息
.with_span_list(true/false) false JSON 输出时,默认不附加完整 span 链列表
.log_internal_errors(true/false) false 默认不记录 subscriber 内部错误
.finish() - 构建 Subscriber,但不注册
.init() - 构建 + 设置为全局 Subscriber

with_current_span

  • 影响span字段,记录当前span信息
{
  "level":"INFO",
  "message":"query user"
}
// 区别
{
  "level":"INFO",
  "message":"query user",
  "span":{
      "name":"request"
  }
}

with_span_list

  • 影响spans字段,记录每个span的内容信息
{
  "level":"INFO",
  "message":"query user"
}
// 区别
{
  "level":"INFO",
  "message":"query user",
  "spans":[
    {
      "name":"request",
      "id":100
    },
    {
      "name":"database"
    }
  ]
}

案例

// 都开启
.with_current_span(true)
.with_span_list(true)
{
  "level": "INFO",
  "message": "query finish",

  "span": {
    "name": "database",
    "sql": "select user",
    "table": "users"
  },

  "spans": [
    {
      "name": "request",
      "request_id": 100,
      "user_id": 200
    },
    {
      "name": "database",
      "sql": "select user",
      "table": "users"
    }
  ]
}

tracing_subscriber::registry()

  • registry + layer + layer + init
// 你写的:
tracing_subscriber::fmt().init();

// 内部实际做的:
let layer = tracing_subscriber::fmt::layer();  // 创建格式化 Layer
tracing_subscriber::registry()                  // 创建 Registry
    .with(layer)                               // 挂载 Layer
    .init();                                   // 设为全局默认值

Layer格式化输出层

use tracing_subscriber::fmt;

// 创建一个 fmt::Layer,默认配置
let layer = fmt::layer();

tracing_subscriber::fmt::layer

  • tracing_subscriber::fmt::layer和SubscriberBuilder是不是一样?
let layer = tracing_subscriber::fmt::layer()
    .with_target(true)          // 显示模块路径
    .with_file(true)            // 显示文件名
    .with_line_number(true)     // 显示行号
    .with_thread_names(true)    // 显示线程名
    .with_ansi(false)           // 禁用颜色
    .json();                    // 输出 JSON 格式(而不是人类可读文本)

它和 fmt().init() 的关系

// 这两者是等价的
tracing_subscriber::fmt().init();

tracing_subscriber::registry()
    .with(tracing_subscriber::fmt::layer())
    .init();

默认配置

配置 默认值
writer stdout
timer SystemTime(本地时间格式)
target true
level true
ansi 自动判断终端
thread id false
thread name false
span events NONE
json false
compact false
let stdout_layer = tracing_subscriber::fmt::layer();

FmtLayer {
    writer: stdout,
    timer: 默认时间,
    formatter: 默认格式,
    ansi: 自动判断,
    target: true,
    level: true,
}
配置 SubscriberBuilder fmt::Layer
输出 writer .with_writer() .with_writer()
ANSI颜色 .with_ansi() .with_ansi()
时间 .with_timer() .with_timer()
target显示 .with_target() .with_target()
level显示 .with_level() .with_level()
线程ID .with_thread_ids() .with_thread_ids()
线程名 .with_thread_names() .with_thread_names()
Span生命周期 .with_span_events() .with_span_events()
JSON格式 .json() .json()

配置多个layer

// 同时输出到 stdout 和文件
let stdout_layer = tracing_subscriber::fmt::layer();
let file_layer = tracing_subscriber::fmt::layer()
    .with_writer(file_appender)
    .with_ansi(false);

tracing_subscriber::registry()
    .with(stdout_layer)   // Layer 1: 控制台
    .with(file_layer)     // Layer 2: 文件
    .init();

配置不同的过滤级别

let stdout_layer =
    tracing_subscriber::fmt::layer()
        .with_filter(LevelFilter::INFO); // info
				...

let file_layer =
    tracing_subscriber::fmt::layer()
        .with_filter(LevelFilter::DEBUG); // debug
				...

let otel_layer =
    tracing_subscriber::fmt::layer()
        .with_filter(LevelFilter::ERROR); // error
				...

registry()
		// 设置日志输出级别(这个过滤完才会到Layer的日志级别过滤)
    .with(
        EnvFilter::from_default_env()
    )
    .with(stdout_layer)
    .with(file_layer)
    .with(otel_layer)

Event

级别 用途
trace!() TRACE 最详细,如进入函数、变量值
debug!() DEBUG 开发调试,如 SQL 语句、请求参数
info!() INFO 正常流程,如服务启动、请求完成
warn!() WARN 警告,如重试、降级、配置缺失
error!() ERROR 错误,如 panic 捕获、业务异常

Event 的字段(Fields)格式

  • 字段前缀规则
前缀 效果 示例
无前缀 调用 Value::record,默认行为 user_id = 42
? 使用 std::fmt::Debug 输出 ?errorerror = CustomError { code: 500 }
% 使用 std::fmt::Display 输出 %idid = "abc-123"
info!(
    target = "my_crate::auth",  // 可选:覆盖默认 target
    user_id = 42,               // 整数字段
    username = "alice",         // 字符串字段
    is_admin = true,            // bool 字段
    latency_ms = 12.5,          // 浮点字段
    ?err,                       // 用 ? 前缀:使用 Debug 格式化
    %request_id,                // 用 % 前缀:使用 Display 格式化
    "用户登录成功"               // 消息(必须放在最后,是格式化字符串)
);

不同输出格式下的 Event 样子

  • <时间> <级别> <target>: <消息> <key=value> <key=value>
info!(user_id = 42, username = "alice", "用户登录");
# 默认
2024-08-06T00:15:30.123456Z  INFO my_crate::auth: 用户登录 user_id=42 username=alice

# .compact()
2024-08-06T00:15:30.123Z INFO my_crate::auth: 用户登录 user_id=42 username=alice

# .pretty()
  2024-08-06T00:15:30.123456Z  INFO  my_crate::auth
    at src/auth.rs:25
    用户登录
      user_id=42
      username=alice

# .json()
{
  "timestamp": "2024-08-06T00:15:30.123456Z",
  "level": "INFO",
  "fields": {
    "message": "用户登录",
    "user_id": 42,
    "username": "alice"
  },
  "target": "my_crate::auth"
}

Event

用法

use tracing::info;
info!(k=v,k=v,k=v..., message)

use std::fmt;

use tracing::info;
use tracing_subscriber::filter::EnvFilter;

#[derive(Debug)]
struct User {
    name: String,
    age: i32
}

struct User1 {
    name: String,
    age: i32
}

impl fmt::Display for User1 {
    // 这个 trait 要求 `fmt` 使用与下面的函数完全一致的函数签名
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // 仅将 self 的第一个元素写入到给定的输出流 `f`。返回 `fmt:Result`,此
        // 结果表明操作成功或失败。注意 `write!` 的用法和 `println!` 很相似。
        write!(f, "Display: {} {}", self.name, self.age)
    }
}


fn main() {
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"));
    tracing_subscriber::fmt()
        .with_env_filter(filter)
        .init();
    
    // 类型显示
    info!(
        user_id = 42,
        user_name = "alice",
        success = true,
        latency_ms = 12.5,
        "aaa"
    );

    let user = User {
        name: "Tom".to_string(),
        age: 33
    };
    // ?显示debug
    info!(user=?user, "aaa");


    let user1: User1 = User1 {
        name: "Bob".to_string(),
        age: 22
    };
    // %显示display
    info!(user1=%user1, "aaa");
}
2026-08-06T02:01:50.803728Z  INFO test_tracing: aaa user_id=42 user_name="alice" success=true latency_ms=12.5
2026-08-06T02:01:50.803777Z  INFO test_tracing: aaa user=User { name: "Tom", age: 33 }
2026-08-06T02:01:50.803792Z  INFO test_tracing: aaa user1=Display: Bob 22

简写形式

如果变量名和字段名一致,可以简写

use std::fmt;

use tracing::info;
use tracing_subscriber::filter::EnvFilter;

#[derive(Debug)]
struct User {
    name: String,
    age: i32
}

struct User1 {
    name: String,
    age: i32
}

impl fmt::Display for User1 {
    // 这个 trait 要求 `fmt` 使用与下面的函数完全一致的函数签名
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // 仅将 self 的第一个元素写入到给定的输出流 `f`。返回 `fmt:Result`,此
        // 结果表明操作成功或失败。注意 `write!` 的用法和 `println!` 很相似。
        write!(f, "Display: {} {}", self.name, self.age)
    }
}


fn main() {
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"));
    tracing_subscriber::fmt()
        .with_env_filter(filter)
        .init();
    
    let user_id = 42;
    let user_name = "alice";
    let success = true;
    let latency_ms = 12.5;

    // 类型显示(简写)
    info!(
        user_id,
        user_name,
        success,
        latency_ms,
        "aaa"
    );

    let user = User {
        name: "Tom".to_string(),
        age: 33
    };
    // ?显示debug(简写)
    info!(?user, "aaa");


    let user1: User1 = User1 {
        name: "Bob".to_string(),
        age: 22
    };
    // %显示display(简写)
    info!(%user1, "aaa");
}
2026-08-06T02:03:20.513412Z  INFO test_tracing: aaa user_id=42 user_name="alice" success=true latency_ms=12.5
2026-08-06T02:03:20.513461Z  INFO test_tracing: aaa user=User { name: "Tom", age: 33 }
2026-08-06T02:03:20.513475Z  INFO test_tracing: aaa user1=Display: Bob 22

特殊字段

use tracing::info;
use tracing_subscriber::filter::EnvFilter;

fn main() {
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"));
    tracing_subscriber::fmt()
        .with_env_filter(filter)
        .init();
    
    // key使用字符串
    tracing::info!(
        "http.method" = "GET",
        "http.status_code" = 200,
        "user.id" = 42,
        "request completed"
    );

}
2026-08-06T02:04:58.838273Z  INFO test_tracing: request completed http.method="GET" http.status_code=200 user.id=42

错误信息

use tracing::info;
use tracing_subscriber::filter::EnvFilter;

fn main() {
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"));
    tracing_subscriber::fmt()
        .with_env_filter(filter)
        .init();

    let e = std::io::Error::new(std::io::ErrorKind::Other, "oh no!");
    
    // debug错误显示
    tracing::info!(
        err_info=?e,
        "err msg1"
    );

    // 简洁错误
    tracing::info!(
        err_info=%e,
        "err msg2"
    );

}
2026-08-06T02:08:18.257614Z  INFO test_tracing: err msg1 err_info=Custom { kind: Other, error: "oh no!" }
2026-08-06T02:08:18.257646Z  INFO test_tracing: err msg2 err_info=oh no!

输出Json对象

使用serde_json::Value

use tracing::info;
use tracing_subscriber::filter::EnvFilter;

fn main() {
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"));
    tracing_subscriber::fmt()
        .with_env_filter(filter)
        // .json()
        .init();
    
    let payload = serde_json::json!({
        "user": {
            "id": 42,
            "name": "Alice"
        },
        "roles": ["admin", "user"]
    });
    tracing::info!(
        payload = %payload,
        "payload created"
    );

}
2026-08-06T02:12:44.540848Z  INFO test_tracing: payload created payload={"roles":["admin","user"],"user":{"id":42,"name":"Alice"}}
{"timestamp":"2026-08-06T02:12:38.609437Z","level":"INFO","fields":{"message":"payload created","payload":"{\"roles\":[\"admin\",\"user\"],\"user\":{\"id\":42,\"name\":\"Alice\"}}"},"target":"test_tracing"}

Span

use tracing::info_span;
info_span!(message, k=v, k=v, ...)

  • 时间范围

用法

日志输出代理span信息

use tracing::info;
use tracing_subscriber::filter::EnvFilter;

fn main() {
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"));
    tracing_subscriber::fmt()
        .with_env_filter(filter)
        .init();

    // 创建span
    let span = tracing::info_span!("handle_request", request_id = "req-123", abs=123);
    // 进入span
    let _enter = span.enter();

    tracing::info!("start handling");
    tracing::info!("query database");
    tracing::info!("finish handling");
}
2026-08-06T02:22:05.576511Z  INFO handle_request{request_id="req-123" abs=123}: test_tracing: start handling
2026-08-06T02:22:05.576548Z  INFO handle_request{request_id="req-123" abs=123}: test_tracing: query database
2026-08-06T02:22:05.576566Z  INFO handle_request{request_id="req-123" abs=123}: test_tracing: finish handling
{"timestamp":"2026-08-06T02:23:02.719962Z","level":"INFO","fields":{"message":"start handling"},"target":"test_tracing","span":{"abs":123,"request_id":"req-123","name":"handle_request"},"spans":[{"abs":123,"request_id":"req-123","name":"handle_request"}]}
{"timestamp":"2026-08-06T02:23:02.720053Z","level":"INFO","fields":{"message":"query database"},"target":"test_tracing","span":{"abs":123,"request_id":"req-123","name":"handle_request"},"spans":[{"abs":123,"request_id":"req-123","name":"handle_request"}]}
{"timestamp":"2026-08-06T02:23:02.720102Z","level":"INFO","fields":{"message":"finish handling"},"target":"test_tracing","span":{"abs":123,"request_id":"req-123","name":"handle_request"},"spans":[{"abs":123,"request_id":"req-123","name":"handle_request"}]}

函数级 span

使用#[tracing::instrument]装饰函数

use tracing_subscriber::filter::EnvFilter;

#[tracing::instrument]
fn a(user_id:i32) {
    tracing::info!("a fun");
}

#[tracing::instrument]
fn b(name: &str, age: i32) {
    tracing::info!("b fun");
}

fn main() {
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"));
    tracing_subscriber::fmt()
        .with_env_filter(filter)
        .init();
    a(33);
    b("tom", 22);
}
2026-08-06T02:28:01.319224Z  INFO a{user_id=33}: test_tracing: a fun
2026-08-06T02:28:01.319287Z  INFO b{name="tom" age=22}: test_tracing: b fun
{"timestamp":"2026-08-06T02:34:40.588592Z","level":"INFO","fields":{"message":"a fun"},"target":"test_tracing","span":{"user_id":33,"name":"a"},"spans":[{"user_id":33,"name":"a"}]}
{"timestamp":"2026-08-06T02:34:40.588717Z","level":"INFO","fields":{"message":"b fun"},"target":"test_tracing","span":{"age":22,"name":"tom","name":"b"},"spans":[{"age":22,"name":"tom","name":"b"}]}

设置字段

可以设置namefields内容

use tracing_subscriber::filter::EnvFilter;

#[derive(Debug)]
struct User {
    name: String,
    age: i32
}


#[tracing::instrument(
    name = "handle_aaa"
)]
fn a(user_id:i32) {
    tracing::info!("a fun");
}

#[tracing::instrument(
    name = "bbb",
    fields(name=name, a=age)
)]
fn b(name: &str, age: i32) {
    tracing::info!("b fun");
}


#[tracing::instrument(
    name="ccccc",
    fields(u=?user)
)]
fn c(user: User) {
    tracing::info!("b fun");
}

fn main() {
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"));
    tracing_subscriber::fmt()
        .with_env_filter(filter)
        // .json()
        .init();
    a(33);
    b("tom", 22);

    let u = User{
        name: "ABC".to_string(),
        age: 18
    };
    c(u);
}
2026-08-06T02:33:11.151490Z  INFO handle_aaa{user_id=33}: test_tracing: a fun
2026-08-06T02:33:11.151548Z  INFO bbb{age=22 name="tom" a=22}: test_tracing: b fun
2026-08-06T02:33:11.151586Z  INFO ccccc{user=User { name: "ABC", age: 18 } u=User { name: "ABC", age: 18 }}: test_tracing: b fun
{"timestamp":"2026-08-06T02:33:49.475561Z","level":"INFO","fields":{"message":"a fun"},"target":"test_tracing","span":{"user_id":33,"name":"handle_aaa"},"spans":[{"user_id":33,"name":"handle_aaa"}]}
{"timestamp":"2026-08-06T02:33:49.475683Z","level":"INFO","fields":{"message":"b fun"},"target":"test_tracing","span":{"a":22,"age":22,"name":"tom","name":"bbb"},"spans":[{"a":22,"age":22,"name":"tom","name":"bbb"}]}
{"timestamp":"2026-08-06T02:33:49.475768Z","level":"INFO","fields":{"message":"b fun"},"target":"test_tracing","span":{"u":"User { name: \"ABC\", age: 18 }","user":"User { name: \"ABC\", age: 18 }","name":"ccccc"},"spans":[{"u":"User { name: \"ABC\", age: 18 }","user":"User { name: \"ABC\", age: 18 }","name":"ccccc"}]}

跳过字段

use tracing_subscriber::filter::EnvFilter;

#[derive(Debug)]
struct User {
    name: String,
    age: i32
}


#[tracing::instrument(
    name = "handle_aaa"
)]
fn a(user_id:i32) {
    tracing::info!("a fun");
}

#[tracing::instrument(
    name = "bbb",
    skip(age) // 跳过age
)]
fn b(name: &str, age: i32) {
    tracing::info!("b fun");
}


#[tracing::instrument(
    name="ccccc",
    fields(un=user.name), // 设置显示user.name
    skip(user)  // 跳过user显示
)]
fn c(user: User) {
    tracing::info!("b fun");
}

fn main() {
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"));
    tracing_subscriber::fmt()
        .with_env_filter(filter)
        // .json()
        .init();
    a(33);
    b("tom", 22);

    let u = User{
        name: "ABC".to_string(),
        age: 18
    };
    c(u);
}
2026-08-06T02:38:46.791306Z  INFO handle_aaa{user_id=33}: test_tracing: a fun
2026-08-06T02:38:46.791373Z  INFO bbb{name="tom"}: test_tracing: b fun
2026-08-06T02:38:46.791407Z  INFO ccccc{un="ABC"}: test_tracing: b fun
{"timestamp":"2026-08-06T02:38:58.662685Z","level":"INFO","fields":{"message":"a fun"},"target":"test_tracing","span":{"user_id":33,"name":"handle_aaa"},"spans":[{"user_id":33,"name":"handle_aaa"}]}
{"timestamp":"2026-08-06T02:38:58.662786Z","level":"INFO","fields":{"message":"b fun"},"target":"test_tracing","span":{"name":"tom","name":"bbb"},"spans":[{"name":"tom","name":"bbb"}]}
{"timestamp":"2026-08-06T02:38:58.662847Z","level":"INFO","fields":{"message":"b fun"},"target":"test_tracing","span":{"un":"ABC","name":"ccccc"},"spans":[{"un":"ABC","name":"ccccc"}]}

异步使用

使用tracing::instrument

use tracing_subscriber::filter::EnvFilter;

#[derive(Debug)]
struct User {
    name: String,
    age: i32
}


#[tracing::instrument(
    name = "handle_aaa"
)]
async fn a(user_id:i32) {
    tracing::info!("a fun");
}

#[tracing::instrument(
    name = "bbb",
    skip(age) // 跳过age
)]
async fn b(name: &str, age: i32) {
    tracing::info!("b fun");
}


#[tracing::instrument(
    name="ccccc",
    fields(un=user.name), // 设置显示user.name
    skip(user)  // 跳过user显示
)]
async fn c(user: User) {
    tracing::info!("b fun");
}

#[tokio::main]
async fn main() {
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"));
    tracing_subscriber::fmt()
        .with_env_filter(filter)
        // .json()
        .init();

    a(33).await;
    b("tom", 22).await;

    let u = User{
        name: "ABC".to_string(),
        age: 18
    };
    c(u).await;
}
2026-08-06T02:50:51.703696Z  INFO handle_aaa{user_id=33}: test_tracing: a fun
2026-08-06T02:50:51.703757Z  INFO bbb{name="tom"}: test_tracing: b fun
2026-08-06T02:50:51.703793Z  INFO ccccc{un="ABC"}: test_tracing: b fun
{"timestamp":"2026-08-06T02:51:37.680037Z","level":"INFO","fields":{"message":"a fun"},"target":"test_tracing","span":{"user_id":33,"name":"handle_aaa"},"spans":[{"user_id":33,"name":"handle_aaa"}]}
{"timestamp":"2026-08-06T02:51:37.680159Z","level":"INFO","fields":{"message":"b fun"},"target":"test_tracing","span":{"name":"tom","name":"bbb"},"spans":[{"name":"tom","name":"bbb"}]}
{"timestamp":"2026-08-06T02:51:37.680223Z","level":"INFO","fields":{"message":"b fun"},"target":"test_tracing","span":{"un":"ABC","name":"ccccc"},"spans":[{"un":"ABC","name":"ccccc"}]}

手动设置

  • 不会自动抓取函数参数,需要自己手动设置到info_span
use tracing::Instrument;
use tracing_subscriber::filter::EnvFilter;


#[derive(Debug)]
struct User {
    name: String,
    age: i32
}


async fn a(user_id:i32) {
    tracing::info!("a fun");
}

async fn b(name: &str, age: i32) {
    tracing::info!("b fun");
}

async fn c(user: User) {
    tracing::info!("b fun");
}

#[tokio::main]
async fn main() {
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"));
    tracing_subscriber::fmt()
        .with_env_filter(filter)
        // .json()
        .init();

    let span_a = tracing::info_span!("aaa", test=222);

    a(33).instrument(span_a).await;

    let span_b = tracing::info_span!("bbb", ak=47);

    b("tom", 22).instrument(span_b).await;

    let u = User{
        name: "ABC".to_string(),
        age: 18
    };
    let span_c = tracing::info_span!("ccc", name="222");

    c(u).instrument(span_c).await;
}
2026-08-06T02:55:51.894599Z  INFO aaa{test=222}: test_tracing: a fun
2026-08-06T02:55:51.894675Z  INFO bbb{ak=47}: test_tracing: b fun
2026-08-06T02:55:51.894712Z  INFO ccc{name="222"}: test_tracing: b fun
{"timestamp":"2026-08-06T02:56:29.462345Z","level":"INFO","fields":{"message":"a fun"},"target":"test_tracing","span":{"test":222,"name":"aaa"},"spans":[{"test":222,"name":"aaa"}]}
{"timestamp":"2026-08-06T02:56:29.462436Z","level":"INFO","fields":{"message":"b fun"},"target":"test_tracing","span":{"ak":47,"name":"bbb"},"spans":[{"ak":47,"name":"bbb"}]}
{"timestamp":"2026-08-06T02:56:29.462498Z","level":"INFO","fields":{"message":"b fun"},"target":"test_tracing","span":{"name":"222","name":"ccc"},"spans":[{"name":"222","name":"ccc"}]}

span嵌套

use tracing_subscriber::filter::EnvFilter;

#[tracing::instrument]
fn a(user_id: i32) {
    tracing::info!("a fun");
    a1(1)
}

#[tracing::instrument]
fn a1(user_id: i32) {
    tracing::info!("a1 fun");
    a2(2);
}

#[tracing::instrument]
fn a2(user_id: i32) {
    tracing::info!("a2 fun");
    a3(3);
}

#[tracing::instrument]
fn a3(user_id: i32) {
    tracing::info!("a3 fun");
}

fn main() {
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"));
    tracing_subscriber::fmt()
        .with_env_filter(filter)
        .init();
    a(33);
}
2026-08-06T03:00:18.999276Z  INFO a{user_id=33}: test_tracing: a fun
2026-08-06T03:00:18.999327Z  INFO a{user_id=33}:a1{user_id=1}: test_tracing: a1 fun
2026-08-06T03:00:18.999362Z  INFO a{user_id=33}:a1{user_id=1}:a2{user_id=2}: test_tracing: a2 fun
2026-08-06T03:00:18.999396Z  INFO a{user_id=33}:a1{user_id=1}:a2{user_id=2}:a3{user_id=3}: test_tracing: a3 fun
{"timestamp":"2026-08-06T03:01:05.710359Z","level":"INFO","fields":{"message":"a fun"},"target":"test_tracing","span":{"user_id":33,"name":"a"},"spans":[{"user_id":33,"name":"a"}]}
{"timestamp":"2026-08-06T03:01:05.710460Z","level":"INFO","fields":{"message":"a1 fun"},"target":"test_tracing","span":{"user_id":1,"name":"a1"},"spans":[{"user_id":33,"name":"a"},{"user_id":1,"name":"a1"}]}
{"timestamp":"2026-08-06T03:01:05.710521Z","level":"INFO","fields":{"message":"a2 fun"},"target":"test_tracing","span":{"user_id":2,"name":"a2"},"spans":[{"user_id":33,"name":"a"},{"user_id":1,"name":"a1"},{"user_id":2,"name":"a2"}]}
{"timestamp":"2026-08-06T03:01:05.710585Z","level":"INFO","fields":{"message":"a3 fun"},"target":"test_tracing","span":{"user_id":3,"name":"a3"},"spans":[{"user_id":33,"name":"a"},{"user_id":1,"name":"a1"},{"user_id":2,"name":"a2"},{"user_id":3,"name":"a3"}]}

Span生命周期

FmtSpan::NEW

use tracing_subscriber::filter::EnvFilter;
use tracing_subscriber::fmt::format::FmtSpan;

#[tracing::instrument]
fn a(user_id: i32) {
    tracing::info!("a fun");
    a1(1)
}

#[tracing::instrument]
fn a1(user_id: i32) {
    tracing::info!("a1 fun");
}

fn main() {
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"));
    tracing_subscriber::fmt()
        .with_env_filter(filter)
        .with_span_events(FmtSpan::NEW)
        // .json()
        .init();
    a(33);
}
2026-08-06T03:09:37.552051Z  INFO a{user_id=33}: test_tracing: new
2026-08-06T03:09:37.552098Z  INFO a{user_id=33}: test_tracing: a fun
2026-08-06T03:09:37.552128Z  INFO a{user_id=33}:a1{user_id=1}: test_tracing: new
2026-08-06T03:09:37.552150Z  INFO a{user_id=33}:a1{user_id=1}: test_tracing: a1 fun
{"timestamp":"2026-08-06T03:09:48.849292Z","level":"INFO","fields":{"message":"new"},"target":"test_tracing","span":{"user_id":33,"name":"a"},"spans":[]}
{"timestamp":"2026-08-06T03:09:48.849371Z","level":"INFO","fields":{"message":"a fun"},"target":"test_tracing","span":{"user_id":33,"name":"a"},"spans":[{"user_id":33,"name":"a"}]}
{"timestamp":"2026-08-06T03:09:48.849432Z","level":"INFO","fields":{"message":"new"},"target":"test_tracing","span":{"user_id":1,"name":"a1"},"spans":[{"user_id":33,"name":"a"}]}
{"timestamp":"2026-08-06T03:09:48.849475Z","level":"INFO","fields":{"message":"a1 fun"},"target":"test_tracing","span":{"user_id":1,"name":"a1"},"spans":[{"user_id":33,"name":"a"},{"user_id":1,"name":"a1"}]}

FmtSpan::ENTER

2026-08-06T03:10:25.065199Z  INFO a{user_id=33}: test_tracing: enter
2026-08-06T03:10:25.065237Z  INFO a{user_id=33}: test_tracing: a fun
2026-08-06T03:10:25.065271Z  INFO a{user_id=33}:a1{user_id=1}: test_tracing: enter
2026-08-06T03:10:25.065293Z  INFO a{user_id=33}:a1{user_id=1}: test_tracing: a1 fun
{"timestamp":"2026-08-06T03:10:51.254375Z","level":"INFO","fields":{"message":"enter"},"target":"test_tracing","span":{"user_id":33,"name":"a"},"spans":[{"user_id":33,"name":"a"}]}
{"timestamp":"2026-08-06T03:10:51.254445Z","level":"INFO","fields":{"message":"a fun"},"target":"test_tracing","span":{"user_id":33,"name":"a"},"spans":[{"user_id":33,"name":"a"}]}
{"timestamp":"2026-08-06T03:10:51.254501Z","level":"INFO","fields":{"message":"enter"},"target":"test_tracing","span":{"user_id":1,"name":"a1"},"spans":[{"user_id":33,"name":"a"},{"user_id":1,"name":"a1"}]}
{"timestamp":"2026-08-06T03:10:51.254546Z","level":"INFO","fields":{"message":"a1 fun"},"target":"test_tracing","span":{"user_id":1,"name":"a1"},"spans":[{"user_id":33,"name":"a"},{"user_id":1,"name":"a1"}]}

FmtSpan::EXIT

2026-08-06T03:11:19.317235Z  INFO a{user_id=33}: test_tracing: a fun
2026-08-06T03:11:19.317293Z  INFO a{user_id=33}:a1{user_id=1}: test_tracing: a1 fun
2026-08-06T03:11:19.317319Z  INFO a{user_id=33}:a1{user_id=1}: test_tracing: exit
2026-08-06T03:11:19.317351Z  INFO a{user_id=33}: test_tracing: exit
{"timestamp":"2026-08-06T03:11:39.022204Z","level":"INFO","fields":{"message":"a fun"},"target":"test_tracing","span":{"user_id":33,"name":"a"},"spans":[{"user_id":33,"name":"a"}]}
{"timestamp":"2026-08-06T03:11:39.022304Z","level":"INFO","fields":{"message":"a1 fun"},"target":"test_tracing","span":{"user_id":1,"name":"a1"},"spans":[{"user_id":33,"name":"a"},{"user_id":1,"name":"a1"}]}
{"timestamp":"2026-08-06T03:11:39.022355Z","level":"INFO","fields":{"message":"exit"},"target":"test_tracing","span":{"user_id":1,"name":"a1"},"spans":[{"user_id":33,"name":"a"}]}
{"timestamp":"2026-08-06T03:11:39.022403Z","level":"INFO","fields":{"message":"exit"},"target":"test_tracing","span":{"user_id":33,"name":"a"},"spans":[]}

FmtSpan::CLOSE

  • time.busy: 表示任务进入span执行的时间
  • time.idle: 表示span存在,当前这个任务没有在这个span运行的时间(被挂起了)
    • 如果在同步代码中time.idle会很低
    • 在异步中,time.idle往往比time.busy大
2026-08-06T03:12:08.817597Z  INFO a{user_id=33}: test_tracing: a fun
2026-08-06T03:12:08.817650Z  INFO a{user_id=33}:a1{user_id=1}: test_tracing: a1 fun
2026-08-06T03:12:08.817686Z  INFO a{user_id=33}:a1{user_id=1}: test_tracing: close time.busy=33.3µs time.idle=6.05µs
2026-08-06T03:12:08.817731Z  INFO a{user_id=33}: test_tracing: close time.busy=140µs time.idle=14.2µs
{"timestamp":"2026-08-06T03:12:19.395366Z","level":"INFO","fields":{"message":"a fun"},"target":"test_tracing","span":{"user_id":33,"name":"a"},"spans":[{"user_id":33,"name":"a"}]}
{"timestamp":"2026-08-06T03:12:19.395462Z","level":"INFO","fields":{"message":"a1 fun"},"target":"test_tracing","span":{"user_id":1,"name":"a1"},"spans":[{"user_id":33,"name":"a"},{"user_id":1,"name":"a1"}]}
{"timestamp":"2026-08-06T03:12:19.395514Z","level":"INFO","fields":{"message":"close","time.busy":"50.5µs","time.idle":"6.48µs"},"target":"test_tracing","span":{"user_id":1,"name":"a1"},"spans":[{"user_id":33,"name":"a"}]}
{"timestamp":"2026-08-06T03:12:19.395575Z","level":"INFO","fields":{"message":"close","time.busy":"211µs","time.idle":"14.3µs"},"target":"test_tracing","span":{"user_id":33,"name":"a"},"spans":[]}

FmtSpan::FULL

2026-08-06T03:12:58.567771Z  INFO a{user_id=33}: test_tracing: new
2026-08-06T03:12:58.567813Z  INFO a{user_id=33}: test_tracing: enter
2026-08-06T03:12:58.567834Z  INFO a{user_id=33}: test_tracing: a fun
2026-08-06T03:12:58.567863Z  INFO a{user_id=33}:a1{user_id=1}: test_tracing: new
2026-08-06T03:12:58.567885Z  INFO a{user_id=33}:a1{user_id=1}: test_tracing: enter
2026-08-06T03:12:58.567904Z  INFO a{user_id=33}:a1{user_id=1}: test_tracing: a1 fun
2026-08-06T03:12:58.567926Z  INFO a{user_id=33}:a1{user_id=1}: test_tracing: exit
2026-08-06T03:12:58.567947Z  INFO a{user_id=33}:a1{user_id=1}: test_tracing: close time.busy=41.0µs time.idle=43.8µs
2026-08-06T03:12:58.567987Z  INFO a{user_id=33}: test_tracing: exit
2026-08-06T03:12:58.568004Z  INFO a{user_id=33}: test_tracing: close time.busy=174µs time.idle=67.2µs
{"timestamp":"2026-08-06T03:13:07.706702Z","level":"INFO","fields":{"message":"new"},"target":"test_tracing","span":{"user_id":33,"name":"a"},"spans":[]}
{"timestamp":"2026-08-06T03:13:07.706780Z","level":"INFO","fields":{"message":"enter"},"target":"test_tracing","span":{"user_id":33,"name":"a"},"spans":[{"user_id":33,"name":"a"}]}
{"timestamp":"2026-08-06T03:13:07.706826Z","level":"INFO","fields":{"message":"a fun"},"target":"test_tracing","span":{"user_id":33,"name":"a"},"spans":[{"user_id":33,"name":"a"}]}
{"timestamp":"2026-08-06T03:13:07.706880Z","level":"INFO","fields":{"message":"new"},"target":"test_tracing","span":{"user_id":1,"name":"a1"},"spans":[{"user_id":33,"name":"a"}]}
{"timestamp":"2026-08-06T03:13:07.706920Z","level":"INFO","fields":{"message":"enter"},"target":"test_tracing","span":{"user_id":1,"name":"a1"},"spans":[{"user_id":33,"name":"a"},{"user_id":1,"name":"a1"}]}
{"timestamp":"2026-08-06T03:13:07.706962Z","level":"INFO","fields":{"message":"a1 fun"},"target":"test_tracing","span":{"user_id":1,"name":"a1"},"spans":[{"user_id":33,"name":"a"},{"user_id":1,"name":"a1"}]}
{"timestamp":"2026-08-06T03:13:07.707008Z","level":"INFO","fields":{"message":"exit"},"target":"test_tracing","span":{"user_id":1,"name":"a1"},"spans":[{"user_id":33,"name":"a"}]}
{"timestamp":"2026-08-06T03:13:07.707046Z","level":"INFO","fields":{"message":"close","time.busy":"87.4µs","time.idle":"79.1µs"},"target":"test_tracing","span":{"user_id":1,"name":"a1"},"spans":[{"user_id":33,"name":"a"}]}
{"timestamp":"2026-08-06T03:13:07.707103Z","level":"INFO","fields":{"message":"exit"},"target":"test_tracing","span":{"user_id":33,"name":"a"},"spans":[]}
{"timestamp":"2026-08-06T03:13:07.707133Z","level":"INFO","fields":{"message":"close","time.busy":"323µs","time.idle":"112µs"},"target":"test_tracing","span":{"user_id":33,"name":"a"},"spans":[]}

在axum中使用日志记录

use axum::{Router, extract::Path, routing::get};
use std::time::Duration;
use std::{fmt, fs::OpenOptions};
use tower_http::trace::TraceLayer;
use tracing::{debug, info, instrument};
use tracing_subscriber::{
    EnvFilter, Layer, fmt as s_fmt, layer::SubscriberExt, util::SubscriberInitExt,
};

// 需要实现自定义时间格式器
struct ChronoLocalTimer;

impl s_fmt::time::FormatTime for ChronoLocalTimer {
    fn format_time(&self, w: &mut s_fmt::format::Writer<'_>) -> fmt::Result {
        write!(
            w,
            "{}",
            chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f")
        )
    }
}

#[tokio::main]
async fn main() {
    init_tracing();

    let app = Router::new()
        .route("/", get(root))
        .route("/users/{id}", get(get_user))
        // TraceLayer 会为每个 HTTP 请求创建一个 request span
        .layer(TraceLayer::new_for_http());

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();

    info!("listening on http://127.0.0.1:3000");

    axum::serve(listener, app).await.unwrap();
}

// 创建日志记录build
fn init_tracing() {
    let env_filter = EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| EnvFilter::new("debug,tower_http=debug"));

    let file = OpenOptions::new()
        .create(true)
        .append(true)
        .open("app.log")
        .unwrap();

    tracing_subscriber::registry()
        .with(env_filter)
        .with(
            s_fmt::layer()
                .with_target(false)
                .with_file(true)
                .with_line_number(true)
                .with_timer(ChronoLocalTimer)
        )
        .with(
            s_fmt::layer()
                .compact()
                .with_file(true)
                .with_line_number(true)
                .with_timer(ChronoLocalTimer)
                .with_ansi(false)
                .with_writer(move || file.try_clone().unwrap())
                .with_filter(EnvFilter::new("info"))
        )
        .init();
}

async fn root() -> &'static str {
    info!("root handler called");
    "hello axum tracing"
}

#[instrument]
async fn get_user(Path(id): Path<u64>) -> String {
    info!(user_id = id, "start getting user");

    let user = query_user_from_db(id).await;

    info!(
        user_id = id,
        username = %user,
        "user loaded"
    );

    format!("user: {user}")
}

#[instrument]
async fn query_user_from_db(user_id: u64) -> String {
    debug!("query started");

    tokio::time::sleep(Duration::from_millis(100)).await;

    debug!("query finished");

    format!("user-{user_id}")
}

终端

2026-08-06 12:52:38.427  INFO src/main.rs:35: listening on http://127.0.0.1:3000
2026-08-06 12:52:44.408 DEBUG request{method=GET uri=/ version=HTTP/1.1}: /home/test/.cargo/registry/src/mirrors.tuna.tsinghua.edu.cn-4dc01642fd091eda/tower-http-0.7.0/src/trace/on_request.rs:80: started processing request
2026-08-06 12:52:44.408  INFO request{method=GET uri=/ version=HTTP/1.1}: src/main.rs:73: root handler called
2026-08-06 12:52:44.408 DEBUG request{method=GET uri=/ version=HTTP/1.1}: /home/test/.cargo/registry/src/mirrors.tuna.tsinghua.edu.cn-4dc01642fd091eda/tower-http-0.7.0/src/trace/on_response.rs:114: finished processing request latency=0 ms status=200
2026-08-06 12:52:44.409 DEBUG request{method=GET uri=/ version=HTTP/1.1}: /home/test/.cargo/registry/src/mirrors.tuna.tsinghua.edu.cn-4dc01642fd091eda/tower-http-0.7.0/src/trace/on_eos.rs:103: end of stream stream_duration=0 ms
2026-08-06 12:53:12.293 DEBUG request{method=GET uri=/users/12 version=HTTP/1.1}: /home/test/.cargo/registry/src/mirrors.tuna.tsinghua.edu.cn-4dc01642fd091eda/tower-http-0.7.0/src/trace/on_request.rs:80: started processing request
2026-08-06 12:53:12.293  INFO request{method=GET uri=/users/12 version=HTTP/1.1}:get_user{id=12}: src/main.rs:79: start getting user user_id=12
2026-08-06 12:53:12.294 DEBUG request{method=GET uri=/users/12 version=HTTP/1.1}:get_user{id=12}:query_user_from_db{user_id=12}: src/main.rs:94: query started
2026-08-06 12:53:12.395 DEBUG request{method=GET uri=/users/12 version=HTTP/1.1}:get_user{id=12}:query_user_from_db{user_id=12}: src/main.rs:98: query finished
2026-08-06 12:53:12.395  INFO request{method=GET uri=/users/12 version=HTTP/1.1}:get_user{id=12}: src/main.rs:83: user loaded user_id=12 username=user-12
2026-08-06 12:53:12.395 DEBUG request{method=GET uri=/users/12 version=HTTP/1.1}: /home/test/.cargo/registry/src/mirrors.tuna.tsinghua.edu.cn-4dc01642fd091eda/tower-http-0.7.0/src/trace/on_response.rs:114: finished processing request latency=101 ms status=200
2026-08-06 12:53:12.395 DEBUG request{method=GET uri=/users/12 version=HTTP/1.1}: /home/test/.cargo/registry/src/mirrors.tuna.tsinghua.edu.cn-4dc01642fd091eda/tower-http-0.7.0/src/trace/on_eos.rs:103: end of stream stream_duration=0 ms

app.log

  • 第一个span输出带了颜色,所以第二个复用缓存span的时候输出了颜色字符
2026-08-06 12:52:38.427  INFO test_tracing: src/main.rs:35: listening on http://127.0.0.1:3000
2026-08-06 12:52:44.408  INFO test_tracing: src/main.rs:73: root handler called
2026-08-06 12:53:12.294  INFO get_user: test_tracing: src/main.rs:79: start getting user user_id=12 id=12
2026-08-06 12:53:12.395  INFO get_user: test_tracing: src/main.rs:83: user loaded user_id=12 username=user-12 id=12

修复缓存一下Layer

  • file_fields格式化span和event的结构化数据
use axum::{Router, extract::Path, routing::get};
use std::time::Duration;
use std::{fmt, fs::OpenOptions};
use tower_http::trace::TraceLayer;
use tracing::{debug, info, instrument};
use tracing_subscriber::{
    EnvFilter, Layer, fmt as s_fmt, layer::SubscriberExt, util::SubscriberInitExt,
    field::MakeExt // 导入
};

// 需要实现自定义时间格式器
struct ChronoLocalTimer;

impl s_fmt::time::FormatTime for ChronoLocalTimer {
    fn format_time(&self, w: &mut s_fmt::format::Writer<'_>) -> fmt::Result {
        write!(
            w,
            "{}",
            chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f")
        )
    }
}

#[tokio::main]
async fn main() {
    init_tracing();

    let app = Router::new()
        .route("/", get(root))
        .route("/users/{id}", get(get_user))
        // TraceLayer 会为每个 HTTP 请求创建一个 request span
        .layer(TraceLayer::new_for_http());

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();

    info!("listening on http://127.0.0.1:3000");

    axum::serve(listener, app).await.unwrap();
}

fn init_tracing() {
    let env_filter = EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| EnvFilter::new("debug,tower_http=debug"));

    let file = OpenOptions::new()
        .create(true)
        .append(true)
        .open("app.log")
        .unwrap();

    // 设置结构化输出内容
    // file_fields 只影响 event 和 span 的结构化字段内容的格式化,不影响整条日志的其他元信息布局。
    let file_fields = s_fmt::format::debug_fn(|writer, field, value| {
          write!(writer, "{}={:?}", field, value)
      })
      .delimited(" ");

    tracing_subscriber::registry()
        .with(env_filter)
        .with(
            s_fmt::layer()
                .with_target(false)
                .with_file(true)
                .with_line_number(true)
                .with_timer(ChronoLocalTimer)
        )
        .with(
            s_fmt::layer()
                .fmt_fields(file_fields) // 配置结构化输出
                .compact()
                .with_file(true)
                .with_line_number(true)
                .with_timer(ChronoLocalTimer)
                .with_ansi(false)
                .with_writer(move || file.try_clone().unwrap())
                .with_filter(EnvFilter::new("info"))
        )
        .init();
}

async fn root() -> &'static str {
    info!("root handler called");
    "hello axum tracing"
}

#[instrument]
async fn get_user(Path(id): Path<u64>) -> String {
    info!(user_id = id, "start getting user");

    let user = query_user_from_db(id).await;

    info!(
        user_id = id,
        username = %user,
        "user loaded"
    );

    format!("user: {user}")
}

#[instrument]
async fn query_user_from_db(user_id: u64) -> String {
    debug!("query started");

    tokio::time::sleep(Duration::from_millis(100)).await;

    debug!("query finished");

    format!("user-{user_id}")
}
posted @ 2026-08-10 10:09  lxd670  阅读(6)  评论(0)    收藏  举报