docs.rs failed to build pi_logger-0.8.1
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: pi_logger-0.6.3

pi_logger

pi_logger 是一个 Rust 日志与可观测性库。当前项目同时保留旧的 log4rs HTTP/SLS appender 能力,并新增了基于 tracingobservability 统一接口。

Observability 初始化后同时支持 tracing::...! 和标准日志门面 log::...!

新接口重点支持:

  • 控制台日志;
  • 本地 rolling 日志;
  • 远端 OpenTelemetry logs 上传;
  • OpenTelemetry traces 上传;
  • OpenTelemetry metrics 上传;
  • 默认等级 + override 覆盖规则;
  • target/span 模糊匹配;
  • event 字段过滤;
  • 运行时动态修改过滤规则;
  • 按 override name 动态启用/禁用规则;
  • 本地/控制台与远端 logs 可使用不同过滤配置。

1. 模块概览

src/
  observability/        # 新 tracing/OTel 可观测性接口
  ali_sls.rs            # 旧 log4rs 阿里云 SLS appender
  http.rs               # 旧 log4rs HTTP appender
  encode_json.rs        # 旧 JsonEncoder
  encode_json_str.rs    # 旧 JsonStrEncoder
  encode_sls.rs         # 旧 SLSEncoder
  opentelemetry.rs      # 旧 OpenTelemetry 初始化接口

推荐新项目优先使用:

pi_logger::observability

旧接口仍保留,避免破坏现有调用。

2. 快速开始

2.1 Cargo.toml

[dependencies]
anyhow = "1"
pi_logger = { path = "../pi_logger" }
toml = "0.8"
tracing = "0.1"

如果使用当前仓库内的 demo,可参考:

../pi_logger_demo

3. observability.toml 示例

[log]
format = "json"

[log.filter]
default_level = "info"

[[log.filter.overrides]]
name = "decoder_trace_by_id"
enabled = true
level = "trace"
priority = 100
fuzzy_rules = [
    { kind = "target", match_type = "contains", pattern = "decoder" },
]
field_rules = [
    { field = "id", op = "eq", value = 100 },
    { field = "msg", op = "contains", value = "decode" },
    { field = "protocol", op = "in", value = ["ton", "eth"] },
    { field = "payload_len", op = "lte", value = 4096 },
    { field = "sampled", op = "eq", value = true },
]

[[log.filter.overrides]]
name = "noisy_db_warn"
enabled = true
level = "warn"
priority = 50
fuzzy_rules = [
    { kind = "target", match_type = "contains", pattern = "db" },
]

[log.console]
enabled = true

[log.local]
enabled = true
file_dir = "logs"
file_name = "app.log"

[log.remote]
enabled = true
endpoint = "http://127.0.0.1:4318"

[log.remote.filter]
default_level = "off"

[[log.remote.filter.overrides]]
name = "remote_decoder_id_100"
enabled = true
level = "info"
priority = 100
fuzzy_rules = [
    { kind = "target", match_type = "contains", pattern = "decoder" },
]
field_rules = [
    { field = "id", op = "eq", value = 100 },
    { field = "test_kind", op = "contains", value = "remote" },
    { field = "module", op = "starts_with", value = "remote" },
    { field = "status", op = "in", value = ["ok", "retry"] },
    { field = "retry_count", op = "lte", value = 3 },
    { field = "sampled", op = "eq", value = true },
]

[log.dynamic]
enabled = true

[trace]
enabled = true
exporter = "otlp"
endpoint = "http://127.0.0.1:4318"
service_name = "my-rust-service"
service_version = "0.1.0"

[trace.filter]
default_level = "off"

[[trace.filter.overrides]]
name = "trace_test_on"
enabled = true
level = "info"
priority = 100
fuzzy_rules = [
    { kind = "target", match_type = "contains", pattern = "trace_test" },
]

[metrics]
enabled = true
exporter = "otlp"
endpoint = "http://127.0.0.1:4318"
service_name = "my-rust-service"
service_version = "0.1.0"

4. 初始化

use anyhow::Result;
use pi_logger::observability::init_observability_from_optional_path;
use std::path::Path;

fn main() -> Result<()> {
    let config_path = Some(Path::new("observability.toml"));
    let (reload, guards) = init_observability_from_optional_path(config_path)?;

    tracing::info!(target: "my_app", "application started");

    guards.shutdown();
    Ok(())
}

注意:

  • guards 必须保活到进程退出前,否则 non-blocking 本地日志可能丢失。
  • guards.shutdown() 会刷新并关闭 OpenTelemetry logs/traces/metrics provider。
  • 不要重复调用 init_observability 初始化全局 subscriber。

配置路径行为:

  • 路径存在:读取配置,并启动文件监听;
  • 文件修改并稳定 10 秒后,自动 reload local、remote、trace 过滤规则;
  • 文件解析或过滤规则校验失败时,旧规则继续生效;
  • 路径为 None 或文件不存在:使用 RUST_LOG 创建仅控制台日志配置;
  • console/local/remote/trace/metrics enabled、endpoint、格式和文件路径不支持热更新,需要重启。
  • remote filter 在“复用 local”和“独立 [log.remote.filter]”之间切换需要重启。

RUST_LOG 支持转换为 ordered filter:

RUST_LOG=info,my_app=debug,hyper=warn

default_level = info
my_app target prefix override = debug
hyper target prefix override = warn

外部系统可以在应用配置前调用检查接口:

use pi_logger::observability::validate_observability_config_path;

let config = validate_observability_config_path("observability.toml")?;

该接口只读取、解析和校验配置,不连接 Collector,也不会修改当前日志状态。

4.1 使用 pi_config 初始化

启用可选 feature:

[dependencies]
pi_logger = { version = "0.7", features = ["pi-config"] }
pi_config = "0.6"

应用总配置可以将日志配置放在 observability 子树中。pi_logger 使用 Config::sub_value("observability") 一次读取合并后的完整子树,因此 TOML、CLI、 ENV、DEFAULT 和 overlay 的优先级处理仍由 pi_config 负责。

use anyhow::Result;
use pi_config::ConfigBuilder;
use pi_logger::observability::init_observability_from_pi_config;

fn main() -> Result<()> {
    let mut config = ConfigBuilder::new()
        .add_toml_file("application.toml", None)?
        .build()?;

    let (reload, guards) =
        init_observability_from_pi_config(&mut config, "observability")?;

    tracing::info!(target: "my_app", "application started");

    guards.shutdown();
    Ok(())
}

pi_config 更新通知到达后,配置管理器将最新配置交给日志库:

use pi_config::Config;
use pi_logger::observability::{
    reload_observability_filters_from_pi_config, ObservabilityReloadHandle,
};

fn on_config_changed(
    config: &mut Config,
    reload: &ObservabilityReloadHandle,
) -> anyhow::Result<()> {
    reload_observability_filters_from_pi_config(reload, config, "observability")
}

更新接口会先读取并校验完整 observability 子树,再更新 local、remote、trace 过滤规则。校验失败时旧规则继续生效。enabled、endpoint、格式、文件路径和 metrics 配置仍然需要重启后生效。

5. 过滤规则模型

完整规则说明见:

Observability 过滤规则使用说明

核心模型:

default_level + overrides
  • 没有命中 override 时使用 default_level
  • 多条 override 命中时选择 priority 最大的一条。
  • override 内的 fuzzy_rules 是 OR 关系。
  • override 内的 field_rules 是 AND 关系。
  • 最高优先级 override 一旦选中,字段不匹配不会回退默认等级或较低优先级规则。

6. default_level

支持 tracedebuginfowarnerroroff。生产环境通常使用 info,排查问题时通过 overrides 临时提升部分模块。

7. overrides

Override 按 target/span 选择一批日志并覆盖默认等级。name 必须唯一, priority 越大优先级越高,enabled = false 时规则不参与匹配。

8. Fuzzy Rules

支持 target/span 的 exactprefixcontainsglobregex 匹配。 高流量场景优先使用 target exactprefix

9. Field Rules

支持 event fields 的相等、字符串、集合、存在性和数值比较。字段规则只读取 tracing event fields,不读取 #[tracing::instrument(fields(...))] 的 span fields。

10. 本地与远端过滤

本地/控制台使用:

[log.filter]
default_level = "info"

远端 OpenTelemetry logs 可以使用独立过滤:

[log.remote]
enabled = true
endpoint = "http://127.0.0.1:4318"

[log.remote.filter]
default_level = "off"

[[log.remote.filter.overrides]]
name = "remote_decoder_id_100"
enabled = true
level = "info"
priority = 100
fuzzy_rules = [
    { kind = "target", match_type = "contains", pattern = "decoder" },
]
field_rules = [
    { field = "id", op = "eq", value = 100 },
    { field = "test_kind", op = "contains", value = "remote" },
    { field = "module", op = "starts_with", value = "remote" },
    { field = "status", op = "in", value = ["ok", "retry"] },
    { field = "retry_count", op = "lte", value = 3 },
    { field = "sampled", op = "eq", value = true },
]

这样可以做到:

  • 本地默认保留所有模块 info 及以上;
  • 远端默认不上报 logs;
  • 远端只临时上传指定模块、指定字段的日志。

如果不配置 [log.remote.filter],远端 logs 会复用 [log.filter]

11. 输出格式

[log]
format = "json"

支持四种格式:

json     结构化 JSON,适合生产采集和机器解析
compact  单行紧凑文本,适合开发时控制台查看
full     完整文本格式,信息比 compact 更充分
pretty   多行美化文本,适合本地调试

format 影响 console 和 local file 输出;远端 OpenTelemetry logs 会转换为 OTel LogRecord,不受该字段影响。

12. 控制台日志

[log.console]
enabled = true

启用后,命中过滤规则的日志会输出到 stdout。实际格式由 [log].format 决定。

13. 本地 rolling 日志

[log.local]
enabled = true
file_dir = "logs"
file_name = "app.log"

输出文件示例:

logs/app.log.2026-06-05

format = "json" 时,本地日志包含:

  • target;
  • file;
  • line number;
  • thread id;
  • thread name;
  • current span;
  • span list。

14. 远端 OpenTelemetry logs

[log.remote]
enabled = true
endpoint = "http://127.0.0.1:4318"

实际上传地址会自动拼接为:

http://127.0.0.1:4318/v1/logs

远端日志使用 opentelemetry-appender-tracingtracing event 转换为 OpenTelemetry LogRecord。

15. OpenTelemetry traces

[trace]
enabled = true
exporter = "otlp"
endpoint = "http://127.0.0.1:4318"
service_name = "my-rust-service"
service_version = "0.1.0"

实际上传地址会自动拼接为:

http://127.0.0.1:4318/v1/traces

trace layer 基于 tracing-opentelemetry,兼容 #[tracing::instrument]

traces pipeline 与 logs pipeline 独立。关闭某个 target 的日志 event,不会阻止 #[tracing::instrument] 创建的 span 上传到 traces pipeline。

16. 业务埋点示例

#[tracing::instrument(
    name = "decode_transaction",
    level = "debug",
    skip_all,
    fields(module = "decoder", protocol = "ton", id = id)
)]
fn decode_transaction(id: u64, payload: &[u8]) -> anyhow::Result<()> {
    tracing::debug!(
        target: "my_app::decoder",
        id = id,
        msg = "start decode transaction",
        payload_len = payload.len(),
        "decode transaction"
    );

    tracing::info!(
        target: "my_app::decoder",
        id = id,
        msg = "decode transaction finished",
        "decode done"
    );

    Ok(())
}

16.1 使用 log 宏

Observability 初始化会安装 tracing-log 兼容桥接,以下日志进入相同的 console、local 和 remote 日志层:

log::info!("application started");
log::debug!(target: "my_app::payment", "payment debug");
log::warn!(target: "my_app::payment", "payment warning");

log target 可以使用现有 fuzzy target 规则过滤。未显式指定 target 时,默认使用 Rust 模块路径。

桥接后的事件在格式化输出中顶层 tracing target 显示为 log,原始 target 保存在 log.target 字段。Observability target 过滤会自动使用原始 log.target

普通 log::...! 主要提供等级、target 和 message,不支持 tracing::info! 的结构化 业务字段语法。需要按 idstatus 等字段过滤时,应使用 tracing::...!

Observability 会把 log 门面的最大等级设置为 trace,最终过滤交给 observability 规则,因此运行时 reload 到 debug/trace 后,log::debug!log::trace! 可以立即生效。

Rust 进程只能安装一个全局 log logger。Observability 不能与已初始化的 log4rs、 env_logger 或其他全局 logger 同时使用;发生冲突时初始化会返回明确错误。

说明:

  • target 可用于 target fuzzy rule;
  • decode_transaction span 名可用于 span fuzzy rule;
  • idmsgpayload_len 是 event fields,可用于 field_rules
  • #[tracing::instrument(fields(...))] 中的字段是 span 字段,主要用于 trace/span 上下文。

17. 动态 reload

初始化后会返回 ObservabilityReloadHandle

let (reload, guards) = init_observability(config)?;

reload 分为三个过滤作用域:

reload.local();  // console 和本地文件日志
reload.remote()?; // 远端 OTLP logs
reload.trace()?;  // 远端 OTLP traces

metrics 暂不支持动态 reload。

17.1 修改默认等级

reload.local().reload_default_level("debug")?;
reload.remote()?.reload_default_level("info")?;
reload.trace()?.reload_default_level("trace")?;

旧的无作用域方法仍然保留,并代理到 local:

reload.reload_default_level("debug")?;

17.2 替换全部 overrides

use pi_logger::observability::{
    FuzzyMatchType, FuzzyRuleConfig, FuzzyRuleKind, LogFilterOverrideConfig,
};

reload.trace()?.reload_overrides(vec![LogFilterOverrideConfig {
    name: "decoder_trace".to_string(),
    enabled: Some(true),
    level: "trace".to_string(),
    priority: 100,
    fuzzy_rules: vec![FuzzyRuleConfig {
        kind: FuzzyRuleKind::Target,
        match_type: FuzzyMatchType::Contains,
        pattern: "decoder".to_string(),
        level: None,
    }],
    field_rules: Vec::new(),
}])?;

17.3 按 name 启用或禁用 override

reload.local().set_override_enabled("decoder_trace", false)?;
reload.remote()?.set_override_enabled("remote_decoder", true)?;
reload.trace()?.set_override_enabled("trace_test_on", true)?;

name 不存在会返回错误。更新失败时,旧规则继续生效。

17.4 替换完整 filter

reload.local().reload_filter(config.log.filter.clone())?;
reload.remote()?.reload_filter(config.log.remote.filter.clone().unwrap())?;
reload.trace()?.reload_filter(config.trace.filter.clone().unwrap())?;

reload 行为:

  • 新规则会先解析、校验、预编译 regex;
  • override name 必须唯一;
  • 成功后才替换当前规则;
  • 失败时旧规则继续生效;
  • 错误链会尽量带上具体 override name
  • 配置文件监听使用 10 秒防抖,连续修改会重新计时;
  • 文件监听自动 reload 不受手动 API 的 log.dynamic.enabled 限制;
  • log.dynamic.enabled = false 只禁止外部代码主动调用 reload API。
  • reload_observability_filtersreload_observability_filters_from_pi_config 用于配置文件监听或配置中心通知,只更新 local、remote、trace 过滤规则;

作用域说明:

  • remote logs 未配置独立 [log.remote.filter] 时,会复用 local filter,因此 local 和 remote reload 指向同一过滤状态;
  • trace 未配置 [trace.filter] 时默认使用 default_level = "trace",保持全部 spans/events 上传;
  • trace filter 与日志 filter 独立,开启日志 trace 等级不会自动开启 traces,反之亦然。

18. 多进程动态调整

reload 只影响当前进程内的 subscriber。多进程场景下,推荐使用共享配置文件或配置中心:

配置文件/配置中心更新
  -> 每个进程各自监听
  -> 每个进程读取并校验新配置
  -> 每个进程调用 reload_filter/reload_overrides

不要假设一个进程的 reload 会自动影响其他进程。

19. 验证方式

当前项目建议使用:

cargo +nightly-2025-03-20 check
cargo +nightly-2025-03-20 test observability

observability 过滤器基准测试参考 tracing / tracing-subscriber 官方 benchmark:

cargo +nightly-2025-03-20 bench --bench observability_filter

只编译 benchmark,不执行:

cargo +nightly-2025-03-20 bench --bench observability_filter --no-run

当前 benchmark 使用 no-op layer 隔离文件和网络 IO,主要覆盖:

  • tracing no-op subscriber baseline;
  • 默认等级通过/拒绝;
  • 1 / 10 / 100 条 override 的命中与未命中;
  • 单字段和多字段过滤;
  • 默认等级 reload;
  • 按 name 动态开关 override;
  • 重新加载 100 条 overrides。

默认工具链 nightly-2026-06-01 在当前依赖 pi_share 0.5.2 上可能失败,这是既有依赖与最新 nightly API 的兼容问题,不是 observability 代码导致。

20. observability 示例

当前 crate 内置 observability 示例:

examples/observability_demo.rs
examples/observability.toml

运行:

PI_LOGGER_OBSERVABILITY_CONFIG=examples/observability.toml \
  cargo +nightly-2025-03-20 run --example observability_demo

不传配置路径时使用 RUST_LOG,并只启用控制台日志:

RUST_LOG=info,pi_logger_example::trace_test=debug \
  cargo +nightly-2025-03-20 run --example observability_demo

示例演示:

  • 本地/控制台默认等级;
  • 远端 logs 独立过滤;
  • 多种 field_rules 字段过滤:数字、字符串、数组、布尔;
  • #[tracing::instrument] 调用链上传 traces;
  • 独立 [trace.filter] 控制应用跟踪上传;
  • counter、histogram、up_down_counter 指标上传 metrics;
  • logs pipeline、traces pipeline 与 metrics pipeline 独立。

21. 旧接口说明

旧接口仍然保留:

  • pi_logger::http::HttpAppender
  • pi_logger::ali_sls::SLSAppender
  • pi_logger::ali_sls::SLBatchConfig
  • pi_logger::encode_json::JsonEncoder
  • pi_logger::encode_json_str::JsonStrEncoder
  • pi_logger::encode_sls::SLSEncoder
  • pi_logger::opentelemetry::{init, is_init, reload_trace_level, create_baggage}

这些接口主要服务于已有 log4rs 接入方式。新项目建议优先使用 pi_logger::observability

22. 注意事项

  • override name 必须唯一。
  • enabled = false 的 override 不参与匹配。
  • 多条 override 命中时,最高 priority 生效。
  • 同一 override 内,fuzzy_rules 是 OR 语义。
  • 同一 override 内,field_rules 是 AND 语义。
  • regex/glob 规则会预编译,并有数量和长度限制。
  • field_rules 会读取 event fields,只建议挂在需要临时收敛的 override 上。
  • remote logs/traces 需要 OpenTelemetry Collector。
  • 本地日志使用 non-blocking writer,必须持有 ObservabilityGuards
  • demo 中固定 time = "=0.3.45" 是为了兼容 nightly-2025-03-20