1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
# pi_logger Observability 程序员使用指南

本文面向接入和维护 `pi_logger` 的 Rust 程序员,介绍新版
`pi_logger::observability` 的能力、与旧版日志接口的差异、过滤规则语法,以及日志、
指标和应用跟踪的常用写法。

完整过滤边界和动态 reload 行为参考
[Observability 过滤规则使用说明](observability_filter_rules.md)。

## 1. 新版本解决了什么问题

新版 observability 将普通日志、应用调用链和指标统一到一套初始化与生命周期中:

```text
tracing::event / log::Record  -> console / local file / OTLP logs
tracing span                  -> OTLP traces
OpenTelemetry instruments     -> OTLP metrics
```

主要优势:

- **统一初始化**:一次初始化 logs、traces 和 metrics,不重复注册全局 subscriber。
- **结构化日志**`tracing::...!` 可以直接记录 `id``status``duration_ms` 等字段。
- **灵活过滤**:默认等级配合 target/span override 和 event field 规则。
- **输出独立控制**:console/local、remote logs 和 traces 可以使用不同过滤规则。
- **动态调整**:local、remote 和 trace 过滤规则支持运行时 reload。
- **安全更新**:新配置先解析、校验并编译;失败时继续使用旧规则。
- **标准日志兼容**:已有 `log::info!``log::warn!` 等调用可进入同一日志管道。
- **可观测性完整**:同时支持普通日志、完整调用链和指标。
- **OTLP 传输可选**:支持 OTLP HTTP Binary 和 gRPC。
- **同步应用可用 gRPC**:启用 gRPC 时,库内部创建并持有专用 Tokio runtime。
- **跨语言预过滤**:可以向 JS 层导出当前日志过滤配置,减少无效跨语言调用。

## 2. 新旧版本差异

| 能力 | 旧版日志接口 | 新版 `observability` |
|---|---|---|
| 主要技术栈 | `log4rs` appender、旧 HTTP/SLS 实现 | `tracing`、OpenTelemetry |
| 初始化 | 分散配置不同 appender | 一次初始化 logs/traces/metrics |
| 日志 API | 主要使用 `log::...!` | 推荐 `tracing::...!`,兼容 `log::...!` |
| 结构化字段 | 通常拼接到消息字符串 | event fields 原生记录 |
| 过滤 | 等级、模块配置为主 | 默认等级、target/span、字段规则、优先级 |
| 本地和远端过滤 | 通常与 appender 配置耦合 | local、remote、trace 作用域独立 |
| 应用跟踪 | 需要独立旧接口 | 使用 `#[tracing::instrument]` 构建调用链 |
| 指标 | 不属于日志初始化流程 | OpenTelemetry metrics 统一初始化 |
| 远端协议 | 旧 HTTP/SLS appender | 标准 OTLP HTTP Binary 或 gRPC |
| 动态更新 | 依赖旧配置机制 | 支持过滤规则 reload 和配置文件监听 |
| 关闭流程 | appender 各自处理 | `ObservabilityGuards::shutdown()` 统一刷新关闭 |

新项目应优先使用 `pi_logger::observability`。旧 appender 仍为历史项目保留,但不建议作为
新功能的基础。

### 2.1 `tracing::...!``log::...!`

推荐使用 `tracing::...!`:

```rust
tracing::info!(
    target: "my_app::order",
    order_id = 10001_u64,
    status = "paid",
    amount = 99.5_f64,
    "order paid"
);
```

它同时提供等级、target、message 和结构化 event fields,字段可以用于远端检索和
`field_rules`。

已有代码可以继续使用标准日志门面:

```rust
log::info!(target: "my_app::legacy", "legacy module started");
```

`log::...!` 适合等级、target 和 message 过滤,但不支持 `tracing::...!` 的结构化业务
字段语法。需要按业务字段过滤时应使用 `tracing::...!`。

### 2.2 新版日志结构

新版普通日志由以下信息组成:

| 内容 | 来源 | 作用 |
|---|---|---|
| level | `tracing::info!` 等宏 | 等级过滤和告警 |
| target | `target: "my_app::order"` | 标识稳定模块,用于路由和过滤 |
| message | 日志宏最后的描述文本 | 供人阅读的事件描述 |
| event fields | `order_id = 10001` 等字段 | 结构化检索和 `field_rules` |
| source metadata | Rust 调用位置 | 本地排障;具体输出取决于日志来源和格式 |
| span context | 当前 `#[tracing::instrument]` 调用链 | 关联调用链上下文 |
| resource | `service.name``service.version` | 标识远端 OTLP 数据所属服务 |

例如:

```rust
tracing::info!(
    target: "my_app::order",
    order_id = 10001_u64,
    status = "paid",
    "order paid"
);
```

使用本地 JSON 格式时,输出结构类似:

```json
{
  "timestamp": "2026-06-15T10:00:00Z",
  "level": "INFO",
  "target": "my_app::order",
  "fields": {
    "message": "order paid",
    "order_id": 10001,
    "status": "paid"
  },
  "filename": "src/order.rs",
  "line_number": 42,
  "threadName": "main"
}
```

远端 logs 会转换为标准 OpenTelemetry LogRecord。业务 event fields 会作为 LogRecord
attributes 上报,最终在日志平台中的展示结构由 Collector exporter 和后端决定。
`pi_logger` 不保证远端平台中的动态业务字段成为最终 JSON 最外层字段。

建议将稳定模块名写入 target,将动态业务值写入 event fields。不要把订单号、用户 ID
等动态值拼接到 target 中。

## 3. 初始化与生命周期

通过可选配置路径初始化:

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

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

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

    // reload 用于运行时读取或修改过滤规则。
    let revision = reload.current_filter_revision();
    tracing::debug!(revision, "current filter revision");

    // 应用退出时刷新并关闭 logs、traces 和 metrics provider。
    guards.shutdown();
    Ok(())
}
```

必须注意:

- 一个进程只初始化一次全局 observability。
- 应用运行期间必须持续持有 `ObservabilityGuards`- 应用退出时调用 `guards.shutdown()`- 配置文件不存在时,`init_observability_from_optional_path` 使用 `RUST_LOG` 创建最小
  console 配置。
- `enabled`、endpoint、protocol、格式和文件路径修改后需要重启。

## 4. 使用 pi_config 初始化

应用已经使用 `pi_config` 管理总配置时,可以直接从合并后的配置子树初始化
observability,不需要单独读取 `observability.toml`。

### 4.1 启用 feature

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

`pi-config` 是可选 feature。未启用时,`init_observability_from_pi_config` 等适配接口不会
参与编译。

### 4.2 配置子树

应用总配置将 observability 配置放在 `observability` 子树中:

```toml
[observability.log]
format = "json"

[observability.log.filter]
default_level = "info"

[observability.log.console]
enabled = true

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

[observability.log.remote]
enabled = true
protocol = "grpc"
endpoint = "http://127.0.0.1:4317"

[observability.log.dynamic]
enabled = true

[observability.trace]
enabled = false
exporter = "otlp"
protocol = "grpc"
endpoint = "http://127.0.0.1:4317"
service_name = "my-service"

[observability.metrics]
enabled = false
exporter = "otlp"
protocol = "grpc"
endpoint = "http://127.0.0.1:4317"
service_name = "my-service"
```

传给 pi_logger 的路径是 `"observability"`。路径指向的子树内部结构与独立
`observability.toml` 相同,仍然从 `log`、`trace` 和可选 `metrics` 开始。

### 4.3 从合并配置初始化

```rust
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");

    // 应用运行期间继续持有 reload 和 guards。
    let _ = reload.current_filter_revision();

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

`pi_logger` 使用 `Config::sub_value("observability")` 一次读取合并后的完整子树。因此
TOML、CLI、环境变量、DEFAULT 和 overlay 的合并优先级仍由 `pi_config` 负责。

只读取和校验配置、不初始化全局 observability:

```rust
use pi_logger::observability::observability_config_from_pi_config;

let observability =
    observability_config_from_pi_config(&mut config, "observability")?;
```

### 4.4 接收配置更新通知

配置中心或文件更新通知到达后,上层配置管理器应先生成最新的合并 `Config`,再调用:

```rust
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")
}
```

更新过程会:

1. 读取完整 `observability` 子树。
2. 解析并校验全部配置和过滤规则。
3. 校验成功后更新 local、remote 和 trace 过滤规则。
4. 校验失败时返回错误,并继续使用旧过滤规则。

支持通过 `pi_config` 通知热更新:

- `[observability.log.filter]`
- `[observability.log.remote.filter]`
- `[observability.trace.filter]`

需要重启后生效:

- console/local/remote/trace/metrics 的 `enabled`
- endpoint 和 protocol
- 日志格式和本地文件路径
- metrics 配置
- remote 在“复用 local filter”和“独立 remote filter”之间切换

`log.dynamic.enabled = false` 只禁止
`reload.local().reload_*`、`reload.remote()?.reload_*` 等手动作用域 API。
`reload_observability_filters_from_pi_config` 属于配置中心通知入口,不受该开关限制。
应用的配置更新处理代码应记录 reload 错误,但不要因为一次错误更新丢弃当前有效配置。

## 5. 最小完整配置

```toml
[log]
format = "json"

[log.filter]
default_level = "info"

[log.console]
enabled = true

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

[log.remote]
enabled = true
protocol = "grpc"
endpoint = "http://127.0.0.1:4317"

[log.dynamic]
enabled = true

[trace]
enabled = true
exporter = "otlp"
protocol = "grpc"
endpoint = "http://127.0.0.1:4317"
service_name = "my-service"
service_version = "1.0.0"

[trace.filter]
default_level = "info"

[metrics]
enabled = true
exporter = "otlp"
protocol = "grpc"
endpoint = "http://127.0.0.1:4317"
service_name = "my-service"
service_version = "1.0.0"
```

协议说明:

| protocol | 常用端口 | endpoint 行为 |
|---|---:|---|
| `http_binary` | 4318 | 自动追加 `/v1/logs``/v1/traces``/v1/metrics` |
| `grpc` | 4317 | endpoint 原样传给 tonic,不追加 HTTP 路径 |

## 6. 新版本过滤语法

每个过滤作用域使用相同模型:

```text
default_level + overrides
```

可配置作用域:

- `[log.filter]`:console 和 local 文件日志。
- `[log.remote.filter]`:远端 OTLP logs;未配置时复用 `[log.filter]`- `[trace.filter]`:远端 OTLP traces。

### 6.1 默认等级

```toml
[log.filter]
default_level = "info"
```

支持 `trace`、`debug`、`info`、`warn`、`error`、`off`。

`default_level = "info"` 表示没有命中 override 时,放行 info、warn 和 error。

### 6.2 Override 覆盖规则

排查 decoder 模块时临时开启 debug:

```toml
[[log.filter.overrides]]
name = "decoder_debug"
enabled = true
level = "debug"
priority = 100
fuzzy_rules = [
    { kind = "target", match_type = "prefix", pattern = "my_app::decoder" },
]
```

字段含义:

| 字段 | 含义 |
|---|---|
| `name` | 规则唯一名称,用于诊断、reload 和动态开关 |
| `enabled` | 是否启用,缺省为 `true` |
| `level` | 命中规则后使用的最低等级 |
| `priority` | 多条规则命中时,数值更大的规则优先 |
| `fuzzy_rules` | 选择 target 或 span,同一 override 内为 OR |
| `field_rules` | 过滤 event fields,同一 override 内为 AND |

完整执行顺序:

```text
1. 按 priority 从高到低选择第一条 fuzzy_rules 命中的 override。
2. 没有命中 override 时使用 default_level。
3. 命中 override 时使用 override.level。
4. 等级通过后检查该 override 的全部 field_rules。
5. 字段不匹配时直接拒绝,不回退默认等级或低优先级规则。
```

### 6.3 Target 和 Span 匹配

```toml
fuzzy_rules = [
    { kind = "target", match_type = "prefix", pattern = "my_app::rpc" },
    { kind = "span", match_type = "exact", pattern = "handle_request" },
]
```

支持:

| match_type | 行为 |
|---|---|
| `exact` | 完全相等 |
| `prefix` | 前缀匹配 |
| `contains` | 包含子字符串 |
| `glob` | `*``?` 通配符 |
| `regex` | 正则表达式 |

高频规则建议优先使用 `exact` 和 `prefix`,它们可以走 target 索引路径。

### 6.4 字段过滤

只上传 `order_id = 10001` 且状态为失败的 order 日志:

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

[[log.remote.filter.overrides]]
name = "failed_order_10001"
enabled = true
level = "debug"
priority = 200
fuzzy_rules = [
    { kind = "target", match_type = "exact", pattern = "my_app::order" },
]
field_rules = [
    { field = "order_id", op = "eq", value = 10001 },
    { field = "status", op = "in", value = ["failed", "timeout"] },
]
```

对应日志必须将字段写在 event 上:

```rust
tracing::debug!(
    target: "my_app::order",
    order_id = 10001_u64,
    status = "failed",
    "order processing failed"
);
```

支持的字段操作符:

| 类型 | op |
|---|---|
| 相等 | `eq``ne` |
| 字符串 | `contains``starts_with``ends_with``regex` |
| 集合 | `in` |
| 存在性 | `exists``not_exists` |
| 数值比较 | `gt``gte``lt``lte` |

示例:

```toml
field_rules = [
    { field = "id", op = "eq", value = 100 },
    { field = "status", op = "in", value = ["ok", "retry"] },
    { field = "retry_count", op = "lte", value = 3 },
    { field = "module", op = "starts_with", value = "remote" },
    { field = "sampled", op = "eq", value = true },
    { field = "request_id", op = "exists" },
]
```

### 6.5 常见过滤场景

仅临时开启模块 trace:

```toml
[[log.filter.overrides]]
name = "rpc_trace"
level = "trace"
priority = 100
fuzzy_rules = [
    { kind = "target", match_type = "prefix", pattern = "my_app::rpc" },
]
```

关闭本地 BI 日志,但继续通过独立 remote 规则上传:

```toml
[[log.filter.overrides]]
name = "local_bi_off"
level = "off"
priority = 100
fuzzy_rules = [
    { kind = "target", match_type = "exact", pattern = "pi_serv_lib::bi" },
]

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

[[log.remote.filter.overrides]]
name = "remote_bi"
level = "info"
priority = 100
fuzzy_rules = [
    { kind = "target", match_type = "exact", pattern = "pi_serv_lib::bi" },
]
```

关闭某类普通日志不会关闭 traces。日志过滤和 trace 过滤是两套独立规则。

## 7. 常用日志写法

### 7.1 基础结构化日志

```rust
tracing::info!(
    target: "my_app::user",
    user_id = 10001_u64,
    action = "login",
    success = true,
    "user login"
);
```

建议:

- target 使用稳定的模块命名,例如 `my_app::order`- message 描述事件,业务值放入结构化字段。
- 数字和布尔值使用原始类型,不要预先格式化为字符串。
- 不要将敏感值直接写入日志。

### 7.2 错误日志

```rust
fn load_user(user_id: u64) -> anyhow::Result<()> {
    let result = do_load_user(user_id);
    if let Err(error) = &result {
        tracing::error!(
            target: "my_app::user",
            user_id,
            error = %error,
            "failed to load user"
        );
    }
    result
}
```

### 7.3 标准 `log` 兼容写法

```rust
log::info!(target: "my_app::legacy", "legacy component started");
log::warn!(target: "my_app::legacy", "legacy component is slow");
```

它们会进入 observability 日志管道,并可按原始 target 过滤。新代码需要结构化字段时应使用
`tracing`。

## 8. 常用指标写法

```rust
use opentelemetry::{global, KeyValue};
use std::time::Instant;

fn record_request() {
    let meter = global::meter("my_app");
    let requests = meter
        .u64_counter("requests_total")
        .with_description("Total handled requests")
        .build();
    let duration = meter
        .f64_histogram("request_duration_ms")
        .with_unit("ms")
        .build();
    let in_flight = meter
        .i64_up_down_counter("requests_in_flight")
        .build();

    let attributes = [
        KeyValue::new("module", "gateway"),
        KeyValue::new("method", "login"),
        KeyValue::new("status", "ok"),
    ];

    in_flight.add(1, &attributes);
    let started = Instant::now();

    // 处理业务请求。

    requests.add(1, &attributes);
    duration.record(started.elapsed().as_secs_f64() * 1000.0, &attributes);
    in_flight.add(-1, &attributes);
}
```

常用类型:

- Counter:只增不减的累计次数,例如请求数。
- Histogram:数值分布,例如耗时和响应大小。
- UpDownCounter:可增可减的当前值,例如处理中请求数。

metrics 不使用日志过滤规则,也不支持动态 reload。

## 9. 常用应用跟踪写法

应用跟踪不是一条日志,而是包含父子 span 的完整调用链。

```rust
use anyhow::Result;

#[tracing::instrument(
    name = "handle_request",
    target = "my_app::request",
    level = "info",
    skip_all,
    fields(request_id = request_id, module = "gateway")
)]
fn handle_request(request_id: u64) -> Result<()> {
    tracing::info!(request_id, "start handling request");
    decode_request(request_id)?;
    persist_request(request_id)?;
    Ok(())
}

#[tracing::instrument(
    name = "decode_request",
    target = "my_app::request",
    level = "info",
    skip_all,
    fields(request_id = request_id, module = "decoder")
)]
fn decode_request(request_id: u64) -> Result<()> {
    tracing::info!(request_id, "request decoded");
    Ok(())
}

#[tracing::instrument(
    name = "persist_request",
    target = "my_app::request",
    level = "info",
    skip_all,
    fields(request_id = request_id, module = "storage")
)]
fn persist_request(request_id: u64) -> Result<()> {
    tracing::info!(request_id, "request persisted");
    Ok(())
}
```

调用一次 `handle_request` 会产生类似调用链:

```text
handle_request
  -> decode_request
  -> persist_request
```

必须区分:

- `#[tracing::instrument(fields(...))]` 中的字段属于 span fields,用于调用链上下文。
- `tracing::info!(id = ...)` 中的字段属于 event fields,可以被 `field_rules` 读取。
- 开启普通日志的 `trace` 等级不会自动开启 traces。
- 开启 `[trace.filter]` 不会自动输出相同 target 的普通日志。

## 10. 动态调整过滤规则

初始化返回的 reload handle 可以按作用域修改规则:

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

reload
    .local()
    .set_override_enabled("decoder_debug", false)?;
```

配置文件监听和 `pi_config` 更新也只会 reload local、remote 和 trace 过滤规则。
endpoint、protocol、enabled、格式和文件路径需要重启。

## 11. 配置检查

启动前检查配置文件:

```rust
use pi_logger::observability::validate_observability_config_path;

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

校验覆盖 TOML 结构、日志等级、重复 rule name、regex/glob、字段规则、exporter 和 endpoint。

## 12. JS 层日志使用

JS 层通过 `pi_pt_logger` 接入新版日志系统,最终统一写入 Rust
`pi_logger::observability` 管道,并使用相同的等级、target 和输出规则。

`pi_pt_logger` 提供两类常用接入方式:

- **接管 console**:接管 `console.log/info/debug/warn/error`,使常规 console 输出进入
  底层日志系统。
- **适配 JS Logger**:通过 RustAppender 适配现有 JS Logger,保留模块来源作为日志
  target,支持按模块配置过滤规则。

业务代码继续使用熟悉的 console 或 JS Logger API,无需直接调用 Rust 日志接口。JS 层
会进行前置过滤,Rust observability 负责最终过滤、格式化以及本地或远端输出。

## 13. 实践建议

- 生产默认等级使用 `info`,排障时通过 override 临时开启 `debug``trace`- 优先使用稳定 target,不要将动态 ID 拼入 target。
- 优先使用 `exact``prefix`,只在必要时使用 contains、glob 和 regex。
- 日志量仍然过大时,再给特定 override 增加 `field_rules`- field_rules 应绑定到明确 target,避免全局事件字段扫描。
- 结构化业务日志使用 `tracing::...!`,旧模块可以继续使用 `log::...!`- traces 用于调用链,logs 用于事件,metrics 用于聚合数值,不要互相替代。
- 多进程环境中,每个进程都需要接收配置更新并调用 reload。
- 应用退出时始终调用 `guards.shutdown()`
可运行示例:

```text
examples/observability_demo.rs
examples/observability.toml
examples/observability_grpc.toml
examples/run_observability_demo.bat
examples/run_observability_demo_grpc.bat
```